@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 +21 -0
- package/README.md +83 -0
- package/binding.gyp +30 -0
- package/bindings/node/binding.cc +19 -0
- package/bindings/node/index.js +13 -0
- package/grammar.js +264 -0
- package/package.json +76 -0
- package/queries/folds.scm +18 -0
- package/queries/highlights.scm +51 -0
- package/queries/injections.scm +97 -0
- package/src/grammar.json +742 -0
- package/src/node-types.json +331 -0
- package/src/parser.c +2007 -0
- package/src/scanner.c +847 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +330 -0
- package/src/tree_sitter/parser.h +286 -0
- package/tree-sitter.json +45 -0
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
|