@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/LICENSE +21 -0
- package/README.md +194 -1
- package/common/common.js +132 -0
- package/common/html_entities.json +2234 -0
- package/package.json +66 -2
- package/tree-sitter-markdown/grammar.js +611 -0
- package/tree-sitter-markdown/queries/highlights.scm +47 -0
- package/tree-sitter-markdown/queries/injections.scm +25 -0
- package/tree-sitter-markdown/src/node-types.json +957 -0
- package/tree-sitter-markdown-inline/grammar.js +474 -0
- package/tree-sitter-markdown-inline/queries/highlights.scm +56 -0
- package/tree-sitter-markdown-inline/queries/injections.scm +5 -0
- package/tree-sitter-markdown-inline/src/node-types.json +870 -0
- package/tree-sitter-markdown.wasm +0 -0
- package/tree-sitter-markdown_inline.wasm +0 -0
- package/tree-sitter.json +55 -0
package/package.json
CHANGED
|
@@ -1,13 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willbooster/tree-sitter-markdown",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
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
|
+
}
|