@mrclrchtr/supi-tree-sitter 8.0.0 → 9.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/README.md +12 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +10 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +9 -4
- package/node_modules/@mrclrchtr/supi-core/src/session-capabilities.ts +55 -1
- package/node_modules/@mrclrchtr/supi-core/src/session-capability-profiles.ts +240 -0
- package/node_modules/@mrclrchtr/supi-core/src/session.ts +22 -0
- package/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +7 -7
- package/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +7 -7
- package/package.json +6 -4
- package/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/resources/grammars/latex/LICENSE +21 -0
- package/resources/grammars/latex/tree-sitter-latex.wasm +0 -0
- package/resources/grammars/latex/tree-sitter-latex.wasm.json +17 -0
- package/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- package/src/language.ts +5 -1
- package/src/operation-support.ts +6 -0
- package/src/provider/tree-sitter-provider.ts +2 -0
- package/src/session/structural-worker-client.ts +45 -34
- package/src/session/structural-worker-process.ts +122 -0
- package/src/session/structural-worker-protocol.ts +1 -1
- package/src/tool/callees.ts +1 -0
- package/src/tool/imports.ts +55 -1
- package/src/tool/outline-latex.ts +288 -0
- package/src/tool/outline-polyglot.ts +8 -1
- package/src/tool/outline.ts +18 -4
- package/src/types.ts +11 -3
- package/src/worker/process-host.mjs +92 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
2
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
3
|
+
import type { OutlineItem } from "../types.ts";
|
|
4
|
+
|
|
5
|
+
const UNSUPPORTED_TITLE_CHARACTERS = /[\\{}%$#&^~_[\]()=]/u;
|
|
6
|
+
|
|
7
|
+
const LATEX_SECTION_KINDS: Readonly<Record<string, string>> = {
|
|
8
|
+
part: "part",
|
|
9
|
+
chapter: "chapter",
|
|
10
|
+
section: "section",
|
|
11
|
+
subsection: "subsection",
|
|
12
|
+
subsubsection: "subsubsection",
|
|
13
|
+
paragraph: "paragraph",
|
|
14
|
+
subparagraph: "subparagraph",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const LATEX_DEFINITION_FIELDS: Readonly<Record<string, { field: string; kind: string }>> = {
|
|
18
|
+
new_command_definition: { field: "declaration", kind: "command" },
|
|
19
|
+
old_command_definition: { field: "declaration", kind: "command" },
|
|
20
|
+
let_command_definition: { field: "declaration", kind: "command" },
|
|
21
|
+
paired_delimiter_definition: { field: "declaration", kind: "command" },
|
|
22
|
+
environment_definition: { field: "name", kind: "environment" },
|
|
23
|
+
label_definition: { field: "name", kind: "label" },
|
|
24
|
+
acronym_definition: { field: "name", kind: "acronym" },
|
|
25
|
+
glossary_entry_definition: { field: "name", kind: "glossary-entry" },
|
|
26
|
+
color_definition: { field: "name", kind: "color" },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Extract LaTeX section headings and explicit definitions from grammar nodes. */
|
|
30
|
+
export function extractLatexOutlineItems(
|
|
31
|
+
node: SyntaxNodeLike,
|
|
32
|
+
source: string,
|
|
33
|
+
lineStarts: readonly number[],
|
|
34
|
+
): OutlineItem[] | undefined {
|
|
35
|
+
const sectionKind = LATEX_SECTION_KINDS[node.type];
|
|
36
|
+
if (sectionKind) return extractLatexSectionItem(node, source, lineStarts, sectionKind);
|
|
37
|
+
|
|
38
|
+
const definition = LATEX_DEFINITION_FIELDS[node.type];
|
|
39
|
+
if (!definition) return undefined;
|
|
40
|
+
const declaration = node.childForFieldName(definition.field);
|
|
41
|
+
const label = definition.kind === "label" ? extractLatexLabel(node, source, lineStarts) : null;
|
|
42
|
+
const name =
|
|
43
|
+
definition.kind === "label"
|
|
44
|
+
? (label?.name ?? "")
|
|
45
|
+
: declaration
|
|
46
|
+
? groupContent(declaration.text)
|
|
47
|
+
: "";
|
|
48
|
+
if (!name) return [];
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
name,
|
|
52
|
+
kind: definition.kind,
|
|
53
|
+
range: nodeToRange(node, source),
|
|
54
|
+
...(label ? { nameAnchor: label.nameAnchor } : {}),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractLatexSectionItem(
|
|
60
|
+
node: SyntaxNodeLike,
|
|
61
|
+
source: string,
|
|
62
|
+
lineStarts: readonly number[],
|
|
63
|
+
sectionKind: string,
|
|
64
|
+
): OutlineItem[] {
|
|
65
|
+
const title = node.childForFieldName("text");
|
|
66
|
+
const name = title ? groupContent(title.text) : "";
|
|
67
|
+
if (!name) return [];
|
|
68
|
+
const titleSpan = title ? findPlainTitleSpan(title, source, lineStarts, name) : null;
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
name,
|
|
72
|
+
kind: sectionKind,
|
|
73
|
+
range: nodeToRange(node, source),
|
|
74
|
+
...(titleSpan ? { nameAnchor: titleSpan.start, nameEndAnchor: titleSpan.end } : {}),
|
|
75
|
+
children: collectLatexMembers(node, source, lineStarts),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Read only label keys with one source-contiguous name span. */
|
|
81
|
+
function extractLatexLabel(
|
|
82
|
+
node: SyntaxNodeLike,
|
|
83
|
+
source: string,
|
|
84
|
+
lineStarts: readonly number[],
|
|
85
|
+
): { name: string; nameAnchor: { line: number; character: number } } | null {
|
|
86
|
+
const commandStart = sourceOffsetAt(
|
|
87
|
+
source,
|
|
88
|
+
node.startPosition.row,
|
|
89
|
+
node.startPosition.column,
|
|
90
|
+
lineStarts,
|
|
91
|
+
);
|
|
92
|
+
const group = findLatexLabelGroup(source, commandStart);
|
|
93
|
+
if (!group) return null;
|
|
94
|
+
const key = findContiguousLabelKey(source, group.openBrace, group.closeBrace);
|
|
95
|
+
if (!key) return null;
|
|
96
|
+
const prefixLines = source.slice(commandStart, key.start).split(/\r\n|\r|\n/u);
|
|
97
|
+
const lastPrefix = prefixLines[prefixLines.length - 1] ?? "";
|
|
98
|
+
return {
|
|
99
|
+
name: source.slice(key.start, key.end),
|
|
100
|
+
nameAnchor: {
|
|
101
|
+
line: node.startPosition.row + prefixLines.length,
|
|
102
|
+
character:
|
|
103
|
+
prefixLines.length === 1
|
|
104
|
+
? node.startPosition.column + lastPrefix.length + 1
|
|
105
|
+
: lastPrefix.length + 1,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findLatexLabelGroup(
|
|
111
|
+
source: string,
|
|
112
|
+
commandStart: number,
|
|
113
|
+
): { openBrace: number; closeBrace: number } | null {
|
|
114
|
+
if (!source.startsWith("\\label", commandStart)) return null;
|
|
115
|
+
const openBrace = skipLabelTrivia(source, commandStart + "\\label".length, source.length);
|
|
116
|
+
if (source[openBrace] !== "{") return null;
|
|
117
|
+
const closeBrace = findLabelGroupEnd(source, openBrace);
|
|
118
|
+
return closeBrace < 0 ? null : { openBrace, closeBrace };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function findContiguousLabelKey(
|
|
122
|
+
source: string,
|
|
123
|
+
openBrace: number,
|
|
124
|
+
closeBrace: number,
|
|
125
|
+
): { start: number; end: number } | null {
|
|
126
|
+
const start = skipLabelTrivia(source, openBrace + 1, closeBrace);
|
|
127
|
+
if (start >= closeBrace || isLabelGroupBrace(source, start)) return null;
|
|
128
|
+
let end = start;
|
|
129
|
+
while (end < closeBrace && !isLabelKeyBoundary(source, end)) end += 1;
|
|
130
|
+
return skipLabelTrivia(source, end, closeBrace) === closeBrace ? { start, end } : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function skipLabelTrivia(source: string, start: number, end: number): number {
|
|
134
|
+
let cursor = start;
|
|
135
|
+
while (cursor < end) {
|
|
136
|
+
if (isTexCommentStart(source, cursor)) {
|
|
137
|
+
cursor = skipTexComment(source, cursor, end);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const character = source[cursor];
|
|
141
|
+
if (character !== undefined && /\s/u.test(character)) {
|
|
142
|
+
cursor += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
return cursor;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isLabelKeyBoundary(source: string, index: number): boolean {
|
|
151
|
+
const character = source[index];
|
|
152
|
+
return (
|
|
153
|
+
character === undefined ||
|
|
154
|
+
isTexCommentStart(source, index) ||
|
|
155
|
+
(character !== undefined && /\s/u.test(character)) ||
|
|
156
|
+
(isLabelGroupBrace(source, index) && !isEscaped(source, index))
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isLabelGroupBrace(source: string, index: number): boolean {
|
|
161
|
+
return source[index] === "{" || source[index] === "}";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findLabelGroupEnd(text: string, openBrace: number): number {
|
|
165
|
+
let depth = 1;
|
|
166
|
+
for (let index = openBrace + 1; index < text.length; index += 1) {
|
|
167
|
+
if (isTexCommentStart(text, index)) {
|
|
168
|
+
index = skipTexComment(text, index, text.length) - 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (isEscaped(text, index)) continue;
|
|
172
|
+
if (text[index] === "{") depth += 1;
|
|
173
|
+
if (text[index] === "}") {
|
|
174
|
+
depth -= 1;
|
|
175
|
+
if (depth === 0) return index;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return -1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function sourceOffsetAt(
|
|
182
|
+
source: string,
|
|
183
|
+
row: number,
|
|
184
|
+
column: number,
|
|
185
|
+
lineStarts: readonly number[],
|
|
186
|
+
): number {
|
|
187
|
+
return Math.min(source.length, (lineStarts[row] ?? source.length) + column);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isTexCommentStart(text: string, index: number): boolean {
|
|
191
|
+
return text[index] === "%" && !isEscaped(text, index);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isEscaped(text: string, index: number): boolean {
|
|
195
|
+
let slashCount = 0;
|
|
196
|
+
for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor -= 1) {
|
|
197
|
+
slashCount += 1;
|
|
198
|
+
}
|
|
199
|
+
return slashCount % 2 === 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function skipTexComment(text: string, start: number, end: number): number {
|
|
203
|
+
const newline = text.indexOf("\n", start);
|
|
204
|
+
const carriageReturn = text.indexOf("\r", start);
|
|
205
|
+
const lineEnd =
|
|
206
|
+
newline < 0 ? carriageReturn : carriageReturn < 0 ? newline : Math.min(newline, carriageReturn);
|
|
207
|
+
if (lineEnd < 0 || lineEnd >= end) return end;
|
|
208
|
+
return lineEnd + (text[lineEnd] === "\r" && text[lineEnd + 1] === "\n" ? 2 : 1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function collectLatexMembers(
|
|
212
|
+
node: SyntaxNodeLike,
|
|
213
|
+
source: string,
|
|
214
|
+
lineStarts: readonly number[],
|
|
215
|
+
): OutlineItem[] {
|
|
216
|
+
const items: OutlineItem[] = [];
|
|
217
|
+
for (const child of node.children) {
|
|
218
|
+
const sectionKind = LATEX_SECTION_KINDS[child.type];
|
|
219
|
+
if (sectionKind) {
|
|
220
|
+
items.push(...(extractLatexOutlineItems(child, source, lineStarts) ?? []));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const definition = LATEX_DEFINITION_FIELDS[child.type];
|
|
225
|
+
if (definition) {
|
|
226
|
+
items.push(...(extractLatexOutlineItems(child, source, lineStarts) ?? []));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
items.push(...collectLatexMembers(child, source, lineStarts));
|
|
230
|
+
}
|
|
231
|
+
return items;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function findPlainTitleSpan(
|
|
235
|
+
node: SyntaxNodeLike,
|
|
236
|
+
source: string,
|
|
237
|
+
lineStarts: readonly number[],
|
|
238
|
+
name: string,
|
|
239
|
+
): { start: { line: number; character: number }; end: { line: number; character: number } } | null {
|
|
240
|
+
const groupStart = sourceOffsetAt(
|
|
241
|
+
source,
|
|
242
|
+
node.startPosition.row,
|
|
243
|
+
node.startPosition.column,
|
|
244
|
+
lineStarts,
|
|
245
|
+
);
|
|
246
|
+
const groupEnd = sourceOffsetAt(
|
|
247
|
+
source,
|
|
248
|
+
node.endPosition.row,
|
|
249
|
+
node.endPosition.column,
|
|
250
|
+
lineStarts,
|
|
251
|
+
);
|
|
252
|
+
if (source[groupStart] !== "{" || source[groupEnd - 1] !== "}") return null;
|
|
253
|
+
|
|
254
|
+
const rawText = source.slice(groupStart + 1, groupEnd - 1);
|
|
255
|
+
const text = rawText.trim();
|
|
256
|
+
if (!text || normalizeTitleWhitespace(text) !== name || UNSUPPORTED_TITLE_CHARACTERS.test(text)) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const startOffset = groupStart + 1 + (rawText.length - rawText.trimStart().length);
|
|
260
|
+
const endOffset = startOffset + text.length;
|
|
261
|
+
if (source.slice(startOffset, endOffset) !== text) return null;
|
|
262
|
+
return {
|
|
263
|
+
start: sourcePointAtOffset(startOffset, lineStarts),
|
|
264
|
+
end: sourcePointAtOffset(endOffset, lineStarts),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function sourcePointAtOffset(
|
|
269
|
+
offset: number,
|
|
270
|
+
lineStarts: readonly number[],
|
|
271
|
+
): { line: number; character: number } {
|
|
272
|
+
let lineIndex = 0;
|
|
273
|
+
while (lineIndex + 1 < lineStarts.length && (lineStarts[lineIndex + 1] ?? Infinity) <= offset) {
|
|
274
|
+
lineIndex += 1;
|
|
275
|
+
}
|
|
276
|
+
return { line: lineIndex + 1, character: offset - (lineStarts[lineIndex] ?? 0) + 1 };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizeTitleWhitespace(value: string): string {
|
|
280
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function groupContent(value: string): string {
|
|
284
|
+
const trimmed = value.trim();
|
|
285
|
+
const contents =
|
|
286
|
+
trimmed.startsWith("{") && trimmed.endsWith("}") ? trimmed.slice(1, -1) : trimmed;
|
|
287
|
+
return contents.replace(/\s+/g, " ").trim();
|
|
288
|
+
}
|
|
@@ -4,13 +4,18 @@ import type { OutlineItem } from "../types.ts";
|
|
|
4
4
|
import { extractCFamilyOutlineItems } from "./outline-c-family.ts";
|
|
5
5
|
import { extractHtmlSqlOutlineItems } from "./outline-html-sql.ts";
|
|
6
6
|
import { extractJvmOutlineItems } from "./outline-jvm.ts";
|
|
7
|
+
import { extractLatexOutlineItems } from "./outline-latex.ts";
|
|
7
8
|
import { extractScriptingOutlineItems } from "./outline-scripting.ts";
|
|
8
9
|
|
|
9
10
|
/** Extract declarations whose Tree-sitter node shapes are specific to non-JS/TS grammars. */
|
|
10
11
|
export function extractPolyglotOutlineItems(
|
|
11
12
|
node: SyntaxNodeLike,
|
|
12
13
|
source: string,
|
|
14
|
+
lineStarts: readonly number[],
|
|
13
15
|
): OutlineItem[] | undefined {
|
|
16
|
+
const latexItems = extractLatexOutlineItems(node, source, lineStarts);
|
|
17
|
+
if (latexItems) return latexItems;
|
|
18
|
+
|
|
14
19
|
const scriptingItems = extractScriptingOutlineItems(node, source);
|
|
15
20
|
if (scriptingItems) return scriptingItems;
|
|
16
21
|
|
|
@@ -35,7 +40,9 @@ export function extractPolyglotOutlineItems(
|
|
|
35
40
|
case "union_item":
|
|
36
41
|
return one(container(node, "union", source, rustFields));
|
|
37
42
|
case "enum_item":
|
|
38
|
-
return
|
|
43
|
+
return node.childForFieldName("name")
|
|
44
|
+
? one(container(node, "enum", source, rustEnumVariants))
|
|
45
|
+
: undefined;
|
|
39
46
|
case "trait_item":
|
|
40
47
|
return one(container(node, "interface", source, rustMethods));
|
|
41
48
|
case "impl_item":
|
package/src/tool/outline.ts
CHANGED
|
@@ -26,16 +26,30 @@ const OUTLINE_DECLARATION_NODE_TYPES = new Set([
|
|
|
26
26
|
"function_signature",
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
|
+
function sourceLineStarts(source: string): number[] {
|
|
30
|
+
const starts = [0];
|
|
31
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
32
|
+
if (source[index] === "\r" && source[index + 1] === "\n") index += 1;
|
|
33
|
+
else if (source[index] !== "\r" && source[index] !== "\n") continue;
|
|
34
|
+
starts.push(index + 1);
|
|
35
|
+
}
|
|
36
|
+
return starts;
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
/** Extract a structural outline from a parsed tree. */
|
|
30
40
|
export function collectOutline(rootNode: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
31
|
-
return collectItems(rootNode, source);
|
|
41
|
+
return collectItems(rootNode, source, sourceLineStarts(source));
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
function collectItems(
|
|
44
|
+
function collectItems(
|
|
45
|
+
node: SyntaxNodeLike,
|
|
46
|
+
source: string,
|
|
47
|
+
lineStarts: readonly number[],
|
|
48
|
+
): OutlineItem[] {
|
|
35
49
|
const items: OutlineItem[] = [];
|
|
36
50
|
|
|
37
51
|
for (const child of node.children) {
|
|
38
|
-
const polyglotItems = extractPolyglotOutlineItems(child, source);
|
|
52
|
+
const polyglotItems = extractPolyglotOutlineItems(child, source, lineStarts);
|
|
39
53
|
if (polyglotItems) {
|
|
40
54
|
items.push(...polyglotItems);
|
|
41
55
|
continue;
|
|
@@ -43,7 +57,7 @@ function collectItems(node: SyntaxNodeLike, source: string): OutlineItem[] {
|
|
|
43
57
|
|
|
44
58
|
const extractedItems = extractItems(child, source);
|
|
45
59
|
if (extractedItems) items.push(...extractedItems);
|
|
46
|
-
else items.push(...collectItems(child, source));
|
|
60
|
+
else items.push(...collectItems(child, source, lineStarts));
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
return items;
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface OutlineItem {
|
|
|
23
23
|
name: string;
|
|
24
24
|
kind: string;
|
|
25
25
|
range: SourceRange;
|
|
26
|
+
/** Exact 1-based start of the declaration name, when available. */
|
|
27
|
+
nameAnchor?: { line: number; character: number };
|
|
28
|
+
/** Exclusive 1-based end of a verified literal name span, when available. */
|
|
29
|
+
nameEndAnchor?: { line: number; character: number };
|
|
26
30
|
children?: OutlineItem[];
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -99,7 +103,7 @@ export interface TreeSitterService {
|
|
|
99
103
|
* HTML ids, and SQL schema members.
|
|
100
104
|
*/
|
|
101
105
|
outline(file: string, control?: CodeRequestControl): Promise<TreeSitterResult<OutlineItem[]>>;
|
|
102
|
-
/** Extract
|
|
106
|
+
/** Extract import records from JavaScript, TypeScript, TSX, and LaTeX files. */
|
|
103
107
|
imports(file: string, control?: CodeRequestControl): Promise<TreeSitterResult<ImportRecord[]>>;
|
|
104
108
|
/** Extract exported declarations, named exports, re-exports, and TS export assignments. */
|
|
105
109
|
exports(file: string, control?: CodeRequestControl): Promise<TreeSitterResult<ExportRecord[]>>;
|
|
@@ -146,7 +150,8 @@ export type GrammarId =
|
|
|
146
150
|
| "bash"
|
|
147
151
|
| "html"
|
|
148
152
|
| "r"
|
|
149
|
-
| "sql"
|
|
153
|
+
| "sql"
|
|
154
|
+
| "latex";
|
|
150
155
|
|
|
151
156
|
/** Supported file extension. */
|
|
152
157
|
export type SupportedExtension =
|
|
@@ -184,4 +189,7 @@ export type SupportedExtension =
|
|
|
184
189
|
| ".htm"
|
|
185
190
|
| ".xhtml"
|
|
186
191
|
| ".r"
|
|
187
|
-
| ".sql"
|
|
192
|
+
| ".sql"
|
|
193
|
+
| ".tex"
|
|
194
|
+
| ".sty"
|
|
195
|
+
| ".cls";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
const [cwd, generationValue] = process.argv.slice(2);
|
|
4
|
+
const generation = Number(generationValue);
|
|
5
|
+
if (!cwd || !Number.isSafeInteger(generation) || generation < 1) {
|
|
6
|
+
throw new Error("Structural Worker process received invalid startup data");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const worker = new Worker(new URL("./bootstrap.mjs", import.meta.url), {
|
|
10
|
+
execArgv: [],
|
|
11
|
+
workerData: { cwd, generation },
|
|
12
|
+
});
|
|
13
|
+
const cancellationFlags = new Map();
|
|
14
|
+
let closing = false;
|
|
15
|
+
let workerExited = false;
|
|
16
|
+
|
|
17
|
+
worker.on("message", (message) => {
|
|
18
|
+
if (message.kind === "terminal") cancellationFlags.delete(message.requestId);
|
|
19
|
+
send({ channel: "supi-tree-sitter-worker", message });
|
|
20
|
+
});
|
|
21
|
+
worker.on("error", (error) => {
|
|
22
|
+
send(
|
|
23
|
+
{
|
|
24
|
+
channel: "supi-tree-sitter-worker",
|
|
25
|
+
message: { kind: "worker-error", message: error.message },
|
|
26
|
+
},
|
|
27
|
+
() => stopWorker(1),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
worker.on("exit", (code) => {
|
|
31
|
+
workerExited = true;
|
|
32
|
+
cancellationFlags.clear();
|
|
33
|
+
process.exitCode = code === 0 ? 1 : code;
|
|
34
|
+
disconnect();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
process.on("message", (value) => {
|
|
38
|
+
if (!isParentChannel(value)) {
|
|
39
|
+
stopWorker(1);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
forwardToWorker(value.message);
|
|
43
|
+
});
|
|
44
|
+
process.on("disconnect", () => stopWorker());
|
|
45
|
+
if (!process.connected) stopWorker();
|
|
46
|
+
|
|
47
|
+
function forwardToWorker(message) {
|
|
48
|
+
if (!message || typeof message !== "object") {
|
|
49
|
+
throw new Error("Structural Worker process received an invalid message");
|
|
50
|
+
}
|
|
51
|
+
if (message.kind === "request") {
|
|
52
|
+
if (cancellationFlags.size !== 0) {
|
|
53
|
+
throw new Error("Structural Worker process received overlapping requests");
|
|
54
|
+
}
|
|
55
|
+
// The process host creates the flag that it can share with its Worker thread.
|
|
56
|
+
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
|
|
57
|
+
cancellationFlags.set(message.requestId, new Int32Array(buffer));
|
|
58
|
+
worker.postMessage({ ...message, cancellationFlag: buffer });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (message.kind === "cancel") {
|
|
62
|
+
const cancellationFlag = cancellationFlags.get(message.requestId);
|
|
63
|
+
if (cancellationFlag) Atomics.store(cancellationFlag, 0, 1);
|
|
64
|
+
}
|
|
65
|
+
worker.postMessage(message);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isParentChannel(value) {
|
|
69
|
+
return (
|
|
70
|
+
value !== null &&
|
|
71
|
+
typeof value === "object" &&
|
|
72
|
+
value.channel === "supi-tree-sitter-parent" &&
|
|
73
|
+
value.message !== null &&
|
|
74
|
+
typeof value.message === "object"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function send(message, callback) {
|
|
79
|
+
if (process.connected && process.send) process.send(message, callback);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function disconnect() {
|
|
83
|
+
if (process.connected) process.disconnect();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function stopWorker(exitCode) {
|
|
87
|
+
if (closing || workerExited) return;
|
|
88
|
+
closing = true;
|
|
89
|
+
if (exitCode !== undefined) process.exitCode = exitCode;
|
|
90
|
+
cancellationFlags.clear();
|
|
91
|
+
void worker.terminate().finally(disconnect);
|
|
92
|
+
}
|