@nvl/tree-sitter-sveltex 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 N. V. Lang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # tree-sitter-sveltex
2
+
3
+ > [!WARNING]
4
+ > **This package is in alpha.** It is brand new and under active development.
5
+ > Its API, behaviour, and configuration may change at any time, and breaking
6
+ > changes should be expected before version `1.0.0`.
7
+
8
+ A [tree-sitter] grammar for **SvelTeX** (`.sveltex`) documents.
9
+
10
+ A `.sveltex` file is a [Svelte](https://svelte.dev) component whose markup is
11
+ written in Markdown, with embedded LaTeX/math, fenced/inline code,
12
+ YAML/TOML/JSON frontmatter and SvelTeX "verbatim" environments. See the
13
+ [`@nvl/sveltex`](https://sveltex.dev) documentation for the language itself.
14
+
15
+ ## Design
16
+
17
+ This grammar deliberately **does not reimplement Markdown, Svelte or LaTeX**.
18
+ It parses only the `.sveltex` *top-level* structure — the constructs a plain
19
+ Markdown grammar would mis-tokenise — and delegates every embedded language to
20
+ an existing grammar through [tree-sitter injections](queries/injections.scm):
21
+
22
+ | `.sveltex` construct | Grammar node | Injected language |
23
+ | -------------------------------------------- | ---------------------------------------------- | ----------------- |
24
+ | YAML/TOML/JSON frontmatter | `frontmatter` / `frontmatter_content` | `yaml`/`toml`/`json` |
25
+ | Markdown prose (everything not below) | `markdown_chunk` | `markdown` (combined) |
26
+ | Inline/display math `$…$`, `$$…$$`, `\(…\)`, `\[…\]` | `inline_math` / `display_math` | `latex` |
27
+ | `<tex>` / `<latex>` / `<tikz>` environments | `verbatim_environment` → `tex_verbatim_body` | `latex` |
28
+ | `<verb>` / `<verbatim>` environments | `verbatim_environment` → `plain_verbatim_body` | *(none — opaque)* |
29
+
30
+ The `markdown` grammar that each `markdown_chunk` is delegated to in turn
31
+ injects `markdown_inline`, the fenced-code languages, and `html`/`svelte` — so
32
+ Svelte `<script>` blocks, `{#if}`/`{#each}`/… logic blocks and `{expr}`
33
+ mustache tags are handled downstream, exactly as in a plain `.svelte`/`.md`
34
+ setup.
35
+
36
+ The split into `frontmatter` / `markdown` / `math` / `verbatim` mirrors the
37
+ `RegionKind`s the SvelTeX language server computes in
38
+ `packages/sveltex-language-server/src/core/regions.ts`.
39
+
40
+ ### External scanner
41
+
42
+ `src/scanner.c` resolves the constructs an LR grammar cannot express:
43
+
44
+ - paired `$` / `$$` math fences (the same token opens and closes them);
45
+ - the matching `</tag>` of a verbatim environment, whose body is arbitrary
46
+ text spanning many lines;
47
+ - the `---` / `+++` fences and body of frontmatter;
48
+ - maximal opaque Markdown runs that stop right before the next
49
+ `.sveltex`-special token.
50
+
51
+ Crucially, the Markdown-chunk scanner **skips over fenced code blocks, inline
52
+ code spans, `<script>`/`<style>` blocks and `{ … }` mustache tags**, so a `$`
53
+ inside any of those (e.g. a `$state` rune, or `import x from '$lib/…'`) is
54
+ *not* mistaken for a math delimiter — matching SvelTeX's own escaper.
55
+
56
+ The scanner keeps no state between tokens, which makes it correct under
57
+ tree-sitter's speculative parsing.
58
+
59
+ ## Layout
60
+
61
+ ```
62
+ grammar.js the grammar definition
63
+ src/scanner.c the external scanner
64
+ src/parser.c the generated parser (committed; run `tree-sitter
65
+ generate` to regenerate)
66
+ queries/highlights.scm syntax highlighting for the structural delimiters
67
+ queries/injections.scm delegation to the embedded-language grammars
68
+ queries/folds.scm foldable regions
69
+ test/corpus/ tree-sitter test corpus
70
+ ```
71
+
72
+ ## Development
73
+
74
+ ```sh
75
+ npm install # installs tree-sitter-cli
76
+ npx tree-sitter generate
77
+ npx tree-sitter test
78
+ npx tree-sitter parse path/to/file.sveltex
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
package/binding.gyp ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "tree_sitter_sveltex_binding",
5
+ "dependencies": [
6
+ "<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
7
+ ],
8
+ "include_dirs": [
9
+ "src",
10
+ ],
11
+ "sources": [
12
+ "bindings/node/binding.cc",
13
+ "src/parser.c",
14
+ "src/scanner.c",
15
+ ],
16
+ "conditions": [
17
+ ["OS!='win'", {
18
+ "cflags_c": [
19
+ "-std=c11",
20
+ ],
21
+ }, {
22
+ "cflags_c": [
23
+ "/std:c11",
24
+ "/utf-8",
25
+ ],
26
+ }],
27
+ ],
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,19 @@
1
+ #include <napi.h>
2
+
3
+ typedef struct TSLanguage TSLanguage;
4
+
5
+ extern "C" TSLanguage *tree_sitter_sveltex();
6
+
7
+ // "tree-sitter", "language" hashed with BLAKE2
8
+ const napi_type_tag LANGUAGE_TYPE_TAG = {
9
+ 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16};
10
+
11
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
12
+ exports["name"] = Napi::String::New(env, "sveltex");
13
+ auto language = Napi::External<TSLanguage>::New(env, tree_sitter_sveltex());
14
+ language.TypeTag(&LANGUAGE_TYPE_TAG);
15
+ exports["language"] = language;
16
+ return exports;
17
+ }
18
+
19
+ NODE_API_MODULE(tree_sitter_sveltex_binding, Init)
@@ -0,0 +1,13 @@
1
+ const root = require("path").join(__dirname, "..", "..");
2
+
3
+ const binding = require("node-gyp-build")(root);
4
+
5
+ try {
6
+ module.exports = require("../../prebuilds/index.js");
7
+ } catch (_) {
8
+ module.exports = binding;
9
+ }
10
+
11
+ try {
12
+ module.exports.nodeTypeInfo = require("../../src/node-types.json");
13
+ } catch (_) {}
package/grammar.js ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * @file Tree-sitter grammar for SvelTeX (`.sveltex`) documents.
3
+ * @author N. V. Lang
4
+ * @license MIT
5
+ *
6
+ * A `.sveltex` file is a Svelte component whose markup is written in Markdown,
7
+ * with embedded LaTeX/math, fenced/inline code, YAML/TOML/JSON frontmatter and
8
+ * SvelTeX "verbatim" environments (`<tex>`, `<verbatim>`, ...).
9
+ *
10
+ * This grammar deliberately does NOT reimplement Markdown, Svelte or LaTeX.
11
+ * Instead it parses only the `.sveltex` top-level structure — the constructs
12
+ * the plain Markdown grammar would mis-tokenise — and leaves everything else as
13
+ * opaque spans. `queries/injections.scm` then delegates each span to an
14
+ * existing grammar:
15
+ *
16
+ * - `markdown` / `markdown_inline` for prose (which in turn injects Svelte
17
+ * and fenced-code languages),
18
+ * - `latex` for math and TeX verbatim environments,
19
+ * - `yaml` / `toml` / `json` for frontmatter.
20
+ *
21
+ * The split into `frontmatter`, `verbatim_environment`, `math` and
22
+ * `markdown_chunk` mirrors the `RegionKind`s the SvelTeX language server
23
+ * computes in `packages/sveltex-language-server/src/core/regions.ts`.
24
+ *
25
+ * Everything that needs to recognise a *closing* delimiter whose body may hold
26
+ * arbitrary text (paired `$`/`$$` fences, the matching `</tag>` of a verbatim
27
+ * environment, the `---`/`+++` end of frontmatter, and opaque Markdown runs)
28
+ * is handled by the external scanner in `src/scanner.c`.
29
+ */
30
+
31
+ /* eslint-disable no-undef */
32
+
33
+ // Verbatim environment tag names whose body is LaTeX. SvelTeX's default
34
+ // `verbatim` config registers `tex`, `latex` and `tikz` as TeX environments
35
+ // (see `defaultConfigSnapshot()` in the language server's `config.ts`).
36
+ const TEX_VERBATIM_TAGS = ['tex', 'latex', 'tikz', 'TeX', 'LaTeX', 'TikZ'];
37
+
38
+ // Verbatim environment tag names whose body is treated as opaque/escaped text
39
+ // (no embedded language). SvelTeX's defaults register `verb` and `verbatim`.
40
+ const PLAIN_VERBATIM_TAGS = ['verb', 'verbatim', 'Verb', 'Verbatim'];
41
+
42
+ module.exports = grammar({
43
+ name: 'sveltex',
44
+
45
+ // The external scanner resolves the constructs an LR grammar cannot:
46
+ // frontmatter fences and body, paired `$`/`$$` math fences, the matching
47
+ // close tag of a verbatim environment, and opaque Markdown runs that stop
48
+ // right before the next `.sveltex`-special token.
49
+ externals: ($) => [
50
+ $._frontmatter_start, // `---` / `+++` opening a frontmatter block
51
+ $._frontmatter_end, // `---` / `+++` closing a frontmatter block
52
+ $._frontmatter_body, // the lines between the frontmatter fences
53
+ $._verbatim_tex_content, // body of a <tex>/<latex>/<tikz> environment
54
+ $._verbatim_plain_content, // body of a <verb>/<verbatim> environment
55
+ $._inline_math_content, // body of `$ ... $`
56
+ $._display_math_content, // body of `$$ ... $$`
57
+ $._markdown_chunk, // a run of ordinary Markdown text
58
+ $._error_sentinel, // tree-sitter's invalid-input sentinel
59
+ ],
60
+
61
+ // `$` participates in math fences and must never be silently skipped; the
62
+ // scanner owns all whitespace handling, so nothing is `extras`.
63
+ extras: () => [],
64
+
65
+ // Keyword-extraction token. It makes the lexer treat the frontmatter
66
+ // language keywords (`yaml`/`toml`/`json`) and verbatim tag names as whole
67
+ // words, so `yamlx` is not tokenised as `yaml` + `x` and `<texx>` is not
68
+ // mistaken for a `<tex>` environment.
69
+ word: ($) => $._word,
70
+
71
+ conflicts: () => [],
72
+
73
+ rules: {
74
+ // A document is an optional frontmatter block followed by body content.
75
+ document: ($) => seq(optional($.frontmatter), repeat($._block)),
76
+
77
+ // ── Frontmatter ──────────────────────────────────────────────────
78
+ //
79
+ // Only valid as the very first thing in the file (the scanner only
80
+ // emits `_frontmatter_start` at offset 0). The optional language
81
+ // keyword right after the opening `---` selects the embedded language;
82
+ // `injections.scm` keys off the `language` child. `+++ … +++` is the
83
+ // TOML shorthand and carries no keyword.
84
+ frontmatter: ($) =>
85
+ seq(
86
+ field('open', alias($._frontmatter_start, $.frontmatter_fence)),
87
+ optional(field('language', $.frontmatter_language)),
88
+ optional(field('content', $.frontmatter_content)),
89
+ field('close', alias($._frontmatter_end, $.frontmatter_fence)),
90
+ ),
91
+
92
+ // `yaml` is the implicit default when no keyword is given. The keyword
93
+ // is glued to the opening fence (`---toml`), hence `token.immediate`.
94
+ frontmatter_language: () =>
95
+ token.immediate(choice('yaml', 'toml', 'json')),
96
+
97
+ // Everything between the fences, produced by the external scanner.
98
+ frontmatter_content: ($) => $._frontmatter_body,
99
+
100
+ // ── Body blocks ──────────────────────────────────────────────────
101
+ //
102
+ // Verbatim environments and math are recognised explicitly; anything
103
+ // else is an opaque `markdown_chunk` for the `markdown` grammar.
104
+ _block: ($) =>
105
+ choice(
106
+ $.verbatim_environment,
107
+ $.display_math,
108
+ $.inline_math,
109
+ $.markdown_chunk,
110
+ ),
111
+
112
+ // ── Verbatim environments ────────────────────────────────────────
113
+ //
114
+ // `<tex …>…</tex>`, `<verbatim>…</verbatim>`, etc. The body never
115
+ // participates in Markdown/Svelte parsing. The external scanner reads
116
+ // up to (but not including) the matching `</tag>`; for TeX tags the
117
+ // body is `latex`, otherwise it is opaque text.
118
+ // Two arms keep the TeX vs. plain distinction: the opening tag's name
119
+ // token (`_tex_tag_name` / `_plain_tag_name`) is distinct, so the
120
+ // parser commits to the right arm at the tag, and the body's external
121
+ // token (`_verbatim_tex_content` / `_verbatim_plain_content`) is
122
+ // therefore unambiguous.
123
+ verbatim_environment: ($) =>
124
+ choice($._verbatim_tex, $._verbatim_plain),
125
+
126
+ _verbatim_tex: ($) =>
127
+ seq(
128
+ field('open', $.verbatim_tex_open_tag),
129
+ optional(field('body', $.tex_verbatim_body)),
130
+ field('close', $.verbatim_close_tag),
131
+ ),
132
+
133
+ _verbatim_plain: ($) =>
134
+ seq(
135
+ field('open', $.verbatim_plain_open_tag),
136
+ optional(field('body', $.plain_verbatim_body)),
137
+ field('close', $.verbatim_close_tag),
138
+ ),
139
+
140
+ // Opening tags. The tag name is captured so `highlights.scm` can
141
+ // colour it; attributes are kept as one opaque blob (they may hold
142
+ // Svelte expressions, but precise attribute parsing is out of scope).
143
+ verbatim_tex_open_tag: ($) =>
144
+ seq(
145
+ '<',
146
+ field('name', alias($._tex_tag_name, $.tag_name)),
147
+ optional(field('attributes', $.verbatim_attributes)),
148
+ token.immediate('>'),
149
+ ),
150
+
151
+ verbatim_plain_open_tag: ($) =>
152
+ seq(
153
+ '<',
154
+ field('name', alias($._plain_tag_name, $.tag_name)),
155
+ optional(field('attributes', $.verbatim_attributes)),
156
+ token.immediate('>'),
157
+ ),
158
+
159
+ verbatim_close_tag: ($) =>
160
+ seq(
161
+ '</',
162
+ field('name', alias($._verbatim_tag_name, $.tag_name)),
163
+ token.immediate('>'),
164
+ ),
165
+
166
+ _tex_tag_name: () =>
167
+ token.immediate(choiceOfStrings(TEX_VERBATIM_TAGS)),
168
+ _plain_tag_name: () =>
169
+ token.immediate(choiceOfStrings(PLAIN_VERBATIM_TAGS)),
170
+ _verbatim_tag_name: () =>
171
+ token.immediate(
172
+ choiceOfStrings([
173
+ ...TEX_VERBATIM_TAGS,
174
+ ...PLAIN_VERBATIM_TAGS,
175
+ ]),
176
+ ),
177
+
178
+ // Attributes of a verbatim opening tag — opaque up to the closing `>`.
179
+ verbatim_attributes: () => token.immediate(/[ \t\r\n][^>]*/),
180
+
181
+ tex_verbatim_body: ($) => $._verbatim_tex_content,
182
+ plain_verbatim_body: ($) => $._verbatim_plain_content,
183
+
184
+ // ── Math ─────────────────────────────────────────────────────────
185
+ //
186
+ // Four delimiter styles. `$$…$$` and `\[…\]` are display math; `$…$`
187
+ // and `\(…\)` are inline. Dollar fences need the external scanner
188
+ // because the same token opens and closes them. The escaped-bracket
189
+ // styles are plain LR rules. The body is `latex` in every case.
190
+ //
191
+ // `$$…$$` is genuinely ambiguous with two empty `$…$` spans wrapping
192
+ // text, so `display_math` and `inline_math` carry `prec.dynamic`
193
+ // weights (and appear in `conflicts`): GLR explores both parses and
194
+ // keeps the higher-weighted display reading.
195
+ display_math: ($) =>
196
+ prec.dynamic(
197
+ 2,
198
+ choice(
199
+ seq(
200
+ alias($._dollar_dollar, $.math_delimiter),
201
+ optional(field('body', $.math_content_display)),
202
+ alias($._dollar_dollar, $.math_delimiter),
203
+ ),
204
+ seq(
205
+ alias('\\[', $.math_delimiter),
206
+ optional(field('body', $.math_content_bracket)),
207
+ alias('\\]', $.math_delimiter),
208
+ ),
209
+ ),
210
+ ),
211
+
212
+ inline_math: ($) =>
213
+ prec.dynamic(
214
+ 1,
215
+ choice(
216
+ seq(
217
+ alias($._dollar, $.math_delimiter),
218
+ optional(field('body', $.math_content_inline)),
219
+ alias($._dollar, $.math_delimiter),
220
+ ),
221
+ seq(
222
+ alias('\\(', $.math_delimiter),
223
+ optional(field('body', $.math_content_paren)),
224
+ alias('\\)', $.math_delimiter),
225
+ ),
226
+ ),
227
+ ),
228
+
229
+ _dollar: () => token(prec(1, '$')),
230
+ _dollar_dollar: () => token(prec(2, '$$')),
231
+
232
+ // Dollar-delimited math bodies come from the external scanner, which
233
+ // stops right before the matching closing fence.
234
+ math_content_display: ($) => $._display_math_content,
235
+ math_content_inline: ($) => $._inline_math_content,
236
+
237
+ // `\[ ... \]` body: anything up to the literal closing `\]`.
238
+ math_content_bracket: () => token(prec(-1, /([^\\]|\\[^\]])+/)),
239
+
240
+ // `\( ... \)` body: anything up to the literal closing `\)`.
241
+ math_content_paren: () => token(prec(-1, /([^\\]|\\[^)])+/)),
242
+
243
+ // ── Markdown ─────────────────────────────────────────────────────
244
+ //
245
+ // An opaque run of ordinary content. The external scanner produces it
246
+ // greedily, stopping just before the next `.sveltex`-special token
247
+ // (a verbatim open tag, a `$`/`$$`/`\(`/`\[` math opener, or EOF).
248
+ // `injections.scm` ships the whole run to the `markdown` grammar.
249
+ markdown_chunk: ($) => $._markdown_chunk,
250
+
251
+ // A bare identifier; only used to satisfy `word`.
252
+ _word: () => /[A-Za-z][A-Za-z0-9_-]*/,
253
+ },
254
+ });
255
+
256
+ /**
257
+ * Builds a `choice()` of string literals.
258
+ *
259
+ * @param {string[]} strings
260
+ * @returns {ChoiceRule}
261
+ */
262
+ function choiceOfStrings(strings) {
263
+ return choice(...strings.map((s) => s));
264
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@nvl/tree-sitter-sveltex",
3
+ "version": "0.2.0",
4
+ "description": "Tree-sitter grammar for SvelTeX (.sveltex) documents",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "N. V. Lang",
8
+ "email": "npm@nvlang.dev",
9
+ "url": "https://nvlang.dev/"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/nvlang/sveltex.git",
14
+ "directory": "packages/tree-sitter-sveltex"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/nvlang/sveltex/issues"
18
+ },
19
+ "homepage": "https://sveltex.dev",
20
+ "keywords": [
21
+ "tree-sitter",
22
+ "parser",
23
+ "sveltex",
24
+ "svelte",
25
+ "markdown",
26
+ "latex"
27
+ ],
28
+ "main": "bindings/node",
29
+ "files": [
30
+ "grammar.js",
31
+ "tree-sitter.json",
32
+ "binding.gyp",
33
+ "queries/*.scm",
34
+ "src/**",
35
+ "bindings/node/*"
36
+ ],
37
+ "tree-sitter": [
38
+ {
39
+ "scope": "source.sveltex",
40
+ "file-types": [
41
+ "sveltex"
42
+ ],
43
+ "highlights": [
44
+ "queries/highlights.scm"
45
+ ],
46
+ "injections": [
47
+ "queries/injections.scm"
48
+ ],
49
+ "folds": [
50
+ "queries/folds.scm"
51
+ ]
52
+ }
53
+ ],
54
+ "dependencies": {
55
+ "node-addon-api": "^8.0.0",
56
+ "node-gyp-build": "^4.8.0"
57
+ },
58
+ "peerDependencies": {
59
+ "tree-sitter": "^0.25.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "tree-sitter": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "tree-sitter": "0.25.0",
68
+ "tree-sitter-cli": "^0.26.8"
69
+ },
70
+ "scripts": {
71
+ "install": "node-gyp-build",
72
+ "generate": "tree-sitter generate",
73
+ "test": "tree-sitter generate && tree-sitter test",
74
+ "parse": "tree-sitter parse"
75
+ }
76
+ }
@@ -0,0 +1,18 @@
1
+ ; Code folding for the SvelTeX (`.sveltex`) grammar.
2
+ ;
3
+ ; Only the multi-line structural blocks the `.sveltex` grammar owns are made
4
+ ; foldable here; folding *within* embedded languages is contributed by the
5
+ ; injected grammars' own fold queries.
6
+ ;
7
+ ; The `@fold` capture marks a node as a foldable region (the tree-sitter and
8
+ ; Zed convention).
9
+
10
+ ; A frontmatter block folds to its opening fence.
11
+ (frontmatter) @fold
12
+
13
+ ; A verbatim environment (`<tex>…</tex>`, `<verbatim>…</verbatim>`, ...) folds
14
+ ; to its opening tag.
15
+ (verbatim_environment) @fold
16
+
17
+ ; A display-math block (`$$…$$` or `\[…\]`) folds to its first line.
18
+ (display_math) @fold
@@ -0,0 +1,51 @@
1
+ ; Syntax highlighting for the SvelTeX (`.sveltex`) grammar.
2
+ ;
3
+ ; This grammar parses only the top-level `.sveltex` structure; the bulk of a
4
+ ; document's highlighting comes from the injected grammars (see
5
+ ; `injections.scm`). The captures below colour just the structural delimiters
6
+ ; that the `.sveltex` grammar itself owns: frontmatter fences, math delimiters
7
+ ; and verbatim-environment tags.
8
+ ;
9
+ ; Capture names follow the standard tree-sitter highlight set so that the
10
+ ; query works unchanged across editors (Zed, Neovim, Helix, ...).
11
+
12
+ ; ── Frontmatter ──────────────────────────────────────────────────────────
13
+
14
+ ; The `---` / `+++` fences delimiting a frontmatter block.
15
+ (frontmatter_fence) @punctuation.delimiter
16
+
17
+ ; The optional `yaml` / `toml` / `json` keyword after the opening fence.
18
+ (frontmatter_language) @keyword
19
+
20
+ ; ── Math ─────────────────────────────────────────────────────────────────
21
+
22
+ ; The `$`, `$$`, `\(`, `\)`, `\[`, `\]` math delimiters.
23
+ (math_delimiter) @punctuation.special
24
+
25
+ ; The math body itself is highlighted by the injected `latex` grammar; tag it
26
+ ; as embedded so editors without that grammar still render it distinctly.
27
+ (math_content_display) @markup.math
28
+ (math_content_inline) @markup.math
29
+ (math_content_bracket) @markup.math
30
+ (math_content_paren) @markup.math
31
+
32
+ ; ── Verbatim environments ────────────────────────────────────────────────
33
+
34
+ ; The `<` / `>` / `</` punctuation of a verbatim environment's tags.
35
+ (verbatim_tex_open_tag ["<" ">"] @punctuation.bracket)
36
+ (verbatim_plain_open_tag ["<" ">"] @punctuation.bracket)
37
+ (verbatim_close_tag ["</" ">"] @punctuation.bracket)
38
+
39
+ ; The tag name (`tex`, `verbatim`, ...).
40
+ (verbatim_tex_open_tag (tag_name) @tag)
41
+ (verbatim_plain_open_tag (tag_name) @tag)
42
+ (verbatim_close_tag (tag_name) @tag)
43
+
44
+ ; A verbatim opening tag's attributes are kept opaque by the grammar; colour
45
+ ; the whole blob as an attribute.
46
+ (verbatim_attributes) @attribute
47
+
48
+ ; The body of a `<verb>` / `<verbatim>` environment is intentionally literal
49
+ ; (SvelTeX escapes rather than renders it) and has no injection, so highlight
50
+ ; it as plain raw text.
51
+ (plain_verbatim_body) @markup.raw