@opentui/core 0.1.31 → 0.1.32
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/3d.js +1 -1
- package/assets/markdown/highlights.scm +150 -0
- package/assets/markdown/injections.scm +27 -0
- package/assets/markdown/tree-sitter-markdown.wasm +0 -0
- package/assets/markdown_inline/highlights.scm +115 -0
- package/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
- package/editor-view.d.ts +2 -0
- package/{index-91qheh74.js → index-3f9h747j.js} +647 -133
- package/{index-91qheh74.js.map → index-3f9h747j.js.map} +15 -14
- package/index.js +180 -24
- package/index.js.map +13 -13
- package/lib/KeyHandler.d.ts +4 -1
- package/lib/index.d.ts +1 -0
- package/lib/parse.keypress.d.ts +1 -0
- package/lib/stdin-buffer.d.ts +42 -0
- package/lib/tree-sitter/client.d.ts +1 -0
- package/lib/tree-sitter/parsers-config.d.ts +38 -0
- package/lib/tree-sitter/types.d.ts +18 -1
- package/lib/tree-sitter-styled-text.d.ts +9 -2
- package/package.json +9 -9
- package/parser.worker.js +250 -27
- package/parser.worker.js.map +3 -3
- package/renderables/Box.d.ts +1 -0
- package/renderables/Code.d.ts +14 -0
- package/renderables/EditBufferRenderable.d.ts +10 -0
- package/renderables/TextBufferRenderable.d.ts +10 -0
- package/renderables/Textarea.d.ts +2 -1
- package/syntax-style.d.ts +2 -0
- package/testing/mock-keys.d.ts +2 -1
- package/testing.js +8 -6
- package/testing.js.map +3 -3
- package/text-buffer-view.d.ts +2 -0
- package/text-buffer.d.ts +3 -0
- package/zig.d.ts +7 -0
package/lib/KeyHandler.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class KeyEvent implements ParsedKey {
|
|
|
10
10
|
number: boolean;
|
|
11
11
|
raw: string;
|
|
12
12
|
eventType: KeyEventType;
|
|
13
|
+
source: "raw" | "kitty";
|
|
13
14
|
code?: string;
|
|
14
15
|
super?: boolean;
|
|
15
16
|
hyper?: boolean;
|
|
@@ -37,11 +38,13 @@ export type KeyHandlerEventMap = {
|
|
|
37
38
|
export declare class KeyHandler extends EventEmitter<KeyHandlerEventMap> {
|
|
38
39
|
protected stdin: NodeJS.ReadStream;
|
|
39
40
|
protected useKittyKeyboard: boolean;
|
|
40
|
-
protected listener: (key: Buffer) => void;
|
|
41
41
|
protected pasteMode: boolean;
|
|
42
42
|
protected pasteBuffer: string[];
|
|
43
43
|
private suspended;
|
|
44
|
+
private stdinBuffer;
|
|
45
|
+
private dataListener;
|
|
44
46
|
constructor(stdin?: NodeJS.ReadStream, useKittyKeyboard?: boolean);
|
|
47
|
+
private processSequence;
|
|
45
48
|
destroy(): void;
|
|
46
49
|
suspend(): void;
|
|
47
50
|
resume(): void;
|
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./hast-styled-text";
|
|
|
5
5
|
export * from "./RGBA";
|
|
6
6
|
export * from "./parse.keypress";
|
|
7
7
|
export * from "./scroll-acceleration";
|
|
8
|
+
export * from "./stdin-buffer";
|
|
8
9
|
export * from "./styled-text";
|
|
9
10
|
export * from "./yoga.options";
|
|
10
11
|
export * from "./parse.mouse";
|
package/lib/parse.keypress.d.ts
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StdinBuffer wraps a stdin stream and emits complete sequences.
|
|
3
|
+
*
|
|
4
|
+
* This is necessary because stdin data events can arrive in partial chunks,
|
|
5
|
+
* especially for escape sequences like mouse events. Without buffering,
|
|
6
|
+
* partial sequences can be misinterpreted as regular keypresses.
|
|
7
|
+
*
|
|
8
|
+
* For example, the mouse SGR sequence `\x1b[<35;20;5m` might arrive as:
|
|
9
|
+
* - Event 1: `\x1b`
|
|
10
|
+
* - Event 2: `[<35`
|
|
11
|
+
* - Event 3: `;20;5m`
|
|
12
|
+
*
|
|
13
|
+
* The buffer accumulates these until a complete sequence is detected.
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from "events";
|
|
16
|
+
export type StdinBufferOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Maximum time to wait for sequence completion (default: 10ms)
|
|
19
|
+
* After this time, the buffer is flushed even if incomplete
|
|
20
|
+
*/
|
|
21
|
+
timeout?: number;
|
|
22
|
+
};
|
|
23
|
+
export type StdinBufferEventMap = {
|
|
24
|
+
data: [string];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Wraps a stdin stream and emits complete sequences via the 'data' event.
|
|
28
|
+
* Handles partial escape sequences that arrive across multiple chunks.
|
|
29
|
+
*/
|
|
30
|
+
export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
31
|
+
private buffer;
|
|
32
|
+
private timeout;
|
|
33
|
+
private readonly timeoutMs;
|
|
34
|
+
private readonly stdin;
|
|
35
|
+
private readonly stdinListener;
|
|
36
|
+
constructor(stdin: NodeJS.ReadStream, options?: StdinBufferOptions);
|
|
37
|
+
private handleData;
|
|
38
|
+
flush(): string[];
|
|
39
|
+
clear(): void;
|
|
40
|
+
getBuffer(): string;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the configuration for the defaulttree-sitter parsers.
|
|
3
|
+
* It is used by ./assets/update.ts to generate the default-parsers.ts file.
|
|
4
|
+
* For changes here to be reflected in the default-parsers.ts file, you need to run `bun run ./assets/update.ts`
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: {
|
|
7
|
+
parsers: ({
|
|
8
|
+
filetype: string;
|
|
9
|
+
wasm: string;
|
|
10
|
+
queries: {
|
|
11
|
+
highlights: string[];
|
|
12
|
+
injections?: undefined;
|
|
13
|
+
};
|
|
14
|
+
injectionMapping?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
filetype: string;
|
|
17
|
+
wasm: string;
|
|
18
|
+
queries: {
|
|
19
|
+
highlights: string[];
|
|
20
|
+
injections: string[];
|
|
21
|
+
};
|
|
22
|
+
injectionMapping: {
|
|
23
|
+
nodeTypes: {
|
|
24
|
+
inline: string;
|
|
25
|
+
pipe_table_cell: string;
|
|
26
|
+
};
|
|
27
|
+
infoStringMap: {
|
|
28
|
+
javascript: string;
|
|
29
|
+
js: string;
|
|
30
|
+
typescript: string;
|
|
31
|
+
ts: string;
|
|
32
|
+
markdown: string;
|
|
33
|
+
md: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
})[];
|
|
37
|
+
};
|
|
38
|
+
export default _default;
|
|
@@ -8,13 +8,30 @@ export interface HighlightResponse {
|
|
|
8
8
|
highlights: HighlightRange[];
|
|
9
9
|
droppedHighlights: HighlightRange[];
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export interface HighlightMeta {
|
|
12
|
+
isInjection?: boolean;
|
|
13
|
+
injectionLang?: string;
|
|
14
|
+
containsInjection?: boolean;
|
|
15
|
+
conceal?: string | null;
|
|
16
|
+
concealLines?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export type SimpleHighlight = [number, number, string, HighlightMeta?];
|
|
19
|
+
export interface InjectionMapping {
|
|
20
|
+
nodeTypes?: {
|
|
21
|
+
[nodeType: string]: string;
|
|
22
|
+
};
|
|
23
|
+
infoStringMap?: {
|
|
24
|
+
[infoString: string]: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
12
27
|
export interface FiletypeParserOptions {
|
|
13
28
|
filetype: string;
|
|
14
29
|
queries: {
|
|
15
30
|
highlights: string[];
|
|
31
|
+
injections?: string[];
|
|
16
32
|
};
|
|
17
33
|
wasm: string;
|
|
34
|
+
injectionMapping?: InjectionMapping;
|
|
18
35
|
}
|
|
19
36
|
export interface BufferState {
|
|
20
37
|
id: number;
|
|
@@ -3,5 +3,12 @@ import { StyledText } from "./styled-text";
|
|
|
3
3
|
import { SyntaxStyle } from "../syntax-style";
|
|
4
4
|
import { TreeSitterClient } from "./tree-sitter/client";
|
|
5
5
|
import type { SimpleHighlight } from "./tree-sitter/types";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
interface ConcealOptions {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function treeSitterToTextChunks(content: string, highlights: SimpleHighlight[], syntaxStyle: SyntaxStyle, options?: ConcealOptions): TextChunk[];
|
|
10
|
+
export interface TreeSitterToStyledTextOptions {
|
|
11
|
+
conceal?: ConcealOptions;
|
|
12
|
+
}
|
|
13
|
+
export declare function treeSitterToStyledText(content: string, filetype: string, syntaxStyle: SyntaxStyle, client: TreeSitterClient, options?: TreeSitterToStyledTextOptions): Promise<StyledText>;
|
|
14
|
+
export {};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.32",
|
|
8
8
|
"description": "OpenTUI is a TypeScript library for building terminal user interfaces (TUIs)",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -45,21 +45,21 @@
|
|
|
45
45
|
"@types/three": "0.177.0",
|
|
46
46
|
"commander": "^13.1.0",
|
|
47
47
|
"typescript": "^5",
|
|
48
|
-
"web-tree-sitter": "
|
|
48
|
+
"web-tree-sitter": "0.25.10"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"web-tree-sitter": "
|
|
51
|
+
"web-tree-sitter": "0.25.10"
|
|
52
52
|
},
|
|
53
53
|
"optionalDependencies": {
|
|
54
54
|
"@dimforge/rapier2d-simd-compat": "^0.17.3",
|
|
55
55
|
"bun-webgpu": "0.1.3",
|
|
56
56
|
"planck": "^1.4.2",
|
|
57
57
|
"three": "0.177.0",
|
|
58
|
-
"@opentui/core-darwin-x64": "0.1.
|
|
59
|
-
"@opentui/core-darwin-arm64": "0.1.
|
|
60
|
-
"@opentui/core-linux-x64": "0.1.
|
|
61
|
-
"@opentui/core-linux-arm64": "0.1.
|
|
62
|
-
"@opentui/core-win32-x64": "0.1.
|
|
63
|
-
"@opentui/core-win32-arm64": "0.1.
|
|
58
|
+
"@opentui/core-darwin-x64": "0.1.32",
|
|
59
|
+
"@opentui/core-darwin-arm64": "0.1.32",
|
|
60
|
+
"@opentui/core-linux-x64": "0.1.32",
|
|
61
|
+
"@opentui/core-linux-arm64": "0.1.32",
|
|
62
|
+
"@opentui/core-win32-x64": "0.1.32",
|
|
63
|
+
"@opentui/core-win32-arm64": "0.1.32"
|
|
64
64
|
}
|
|
65
65
|
}
|
package/parser.worker.js
CHANGED
|
@@ -144,6 +144,7 @@ class ParserWorker {
|
|
|
144
144
|
initializePromise;
|
|
145
145
|
performance;
|
|
146
146
|
dataPath;
|
|
147
|
+
tsDataPath;
|
|
147
148
|
initialized = false;
|
|
148
149
|
constructor() {
|
|
149
150
|
this.performance = {
|
|
@@ -153,11 +154,11 @@ class ParserWorker {
|
|
|
153
154
|
queryTimes: []
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
|
-
async
|
|
157
|
-
if (!this.
|
|
157
|
+
async fetchQueries(sources, filetype) {
|
|
158
|
+
if (!this.tsDataPath) {
|
|
158
159
|
return "";
|
|
159
160
|
}
|
|
160
|
-
return DownloadUtils.fetchHighlightQueries(sources, this.
|
|
161
|
+
return DownloadUtils.fetchHighlightQueries(sources, this.tsDataPath, filetype);
|
|
161
162
|
}
|
|
162
163
|
async initialize({ dataPath }) {
|
|
163
164
|
if (this.initializePromise) {
|
|
@@ -165,10 +166,11 @@ class ParserWorker {
|
|
|
165
166
|
}
|
|
166
167
|
this.initializePromise = new Promise(async (resolve, reject) => {
|
|
167
168
|
this.dataPath = dataPath;
|
|
169
|
+
this.tsDataPath = path2.join(dataPath, "tree-sitter");
|
|
168
170
|
try {
|
|
169
|
-
await mkdir2(path2.join(
|
|
170
|
-
await mkdir2(path2.join(
|
|
171
|
-
let { default: treeWasm } = await import("web-tree-sitter/
|
|
171
|
+
await mkdir2(path2.join(this.tsDataPath, "languages"), { recursive: true });
|
|
172
|
+
await mkdir2(path2.join(this.tsDataPath, "queries"), { recursive: true });
|
|
173
|
+
let { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm", {
|
|
172
174
|
with: { type: "wasm" }
|
|
173
175
|
});
|
|
174
176
|
if (/\$bunfs/.test(treeWasm)) {
|
|
@@ -192,25 +194,33 @@ class ParserWorker {
|
|
|
192
194
|
}
|
|
193
195
|
async createQueries(filetypeParser, language) {
|
|
194
196
|
try {
|
|
195
|
-
const highlightQueryContent = await this.
|
|
197
|
+
const highlightQueryContent = await this.fetchQueries(filetypeParser.queries.highlights, filetypeParser.filetype);
|
|
196
198
|
if (!highlightQueryContent) {
|
|
197
199
|
console.error("Failed to fetch highlight queries for:", filetypeParser.filetype);
|
|
198
200
|
return;
|
|
199
201
|
}
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
highlights:
|
|
202
|
+
const highlightsQuery = new Query(language, highlightQueryContent);
|
|
203
|
+
const result = {
|
|
204
|
+
highlights: highlightsQuery
|
|
203
205
|
};
|
|
206
|
+
if (filetypeParser.queries.injections && filetypeParser.queries.injections.length > 0) {
|
|
207
|
+
const injectionQueryContent = await this.fetchQueries(filetypeParser.queries.injections, filetypeParser.filetype);
|
|
208
|
+
if (injectionQueryContent) {
|
|
209
|
+
result.injections = new Query(language, injectionQueryContent);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
204
213
|
} catch (error) {
|
|
214
|
+
console.error("Error creating queries for", filetypeParser.filetype, filetypeParser.queries);
|
|
205
215
|
console.error(error);
|
|
206
216
|
return;
|
|
207
217
|
}
|
|
208
218
|
}
|
|
209
219
|
async loadLanguage(languageSource) {
|
|
210
|
-
if (!this.initialized || !this.
|
|
220
|
+
if (!this.initialized || !this.tsDataPath) {
|
|
211
221
|
return;
|
|
212
222
|
}
|
|
213
|
-
const result = await DownloadUtils.downloadOrLoad(languageSource, this.
|
|
223
|
+
const result = await DownloadUtils.downloadOrLoad(languageSource, this.tsDataPath, "languages", ".wasm", false);
|
|
214
224
|
if (result.error) {
|
|
215
225
|
console.error(`Error loading language ${languageSource}:`, result.error);
|
|
216
226
|
return;
|
|
@@ -297,7 +307,8 @@ class ParserWorker {
|
|
|
297
307
|
parser.setLanguage(filetypeParser.language);
|
|
298
308
|
const reusableState = {
|
|
299
309
|
parser,
|
|
300
|
-
filetypeParser
|
|
310
|
+
filetypeParser,
|
|
311
|
+
queries: filetypeParser.queries
|
|
301
312
|
};
|
|
302
313
|
return reusableState;
|
|
303
314
|
}
|
|
@@ -326,7 +337,14 @@ class ParserWorker {
|
|
|
326
337
|
});
|
|
327
338
|
return;
|
|
328
339
|
}
|
|
329
|
-
const parserState = {
|
|
340
|
+
const parserState = {
|
|
341
|
+
parser,
|
|
342
|
+
tree,
|
|
343
|
+
queries: filetypeParser.queries,
|
|
344
|
+
filetype,
|
|
345
|
+
content,
|
|
346
|
+
injectionMapping: filetypeParser.injectionMapping
|
|
347
|
+
};
|
|
330
348
|
this.bufferParsers.set(bufferId, parserState);
|
|
331
349
|
self.postMessage({
|
|
332
350
|
type: "PARSER_INIT_RESPONSE",
|
|
@@ -334,7 +352,7 @@ class ParserWorker {
|
|
|
334
352
|
messageId,
|
|
335
353
|
hasParser: true
|
|
336
354
|
});
|
|
337
|
-
const highlights = this.initialQuery(parserState);
|
|
355
|
+
const highlights = await this.initialQuery(parserState);
|
|
338
356
|
self.postMessage({
|
|
339
357
|
type: "HIGHLIGHT_RESPONSE",
|
|
340
358
|
bufferId,
|
|
@@ -342,10 +360,111 @@ class ParserWorker {
|
|
|
342
360
|
...highlights
|
|
343
361
|
});
|
|
344
362
|
}
|
|
345
|
-
initialQuery(parserState) {
|
|
363
|
+
async initialQuery(parserState) {
|
|
346
364
|
const query = parserState.queries.highlights;
|
|
347
365
|
const matches = query.captures(parserState.tree.rootNode);
|
|
348
|
-
|
|
366
|
+
let injectionRanges = new Map;
|
|
367
|
+
if (parserState.queries.injections) {
|
|
368
|
+
const injectionResult = await this.processInjections(parserState);
|
|
369
|
+
matches.push(...injectionResult.captures);
|
|
370
|
+
injectionRanges = injectionResult.injectionRanges;
|
|
371
|
+
}
|
|
372
|
+
return this.getHighlights(parserState, matches, injectionRanges);
|
|
373
|
+
}
|
|
374
|
+
getNodeText(node, content) {
|
|
375
|
+
return content.substring(node.startIndex, node.endIndex);
|
|
376
|
+
}
|
|
377
|
+
async processInjections(parserState) {
|
|
378
|
+
const injectionMatches = [];
|
|
379
|
+
const injectionRanges = new Map;
|
|
380
|
+
if (!parserState.queries.injections) {
|
|
381
|
+
return { captures: injectionMatches, injectionRanges };
|
|
382
|
+
}
|
|
383
|
+
const content = parserState.content;
|
|
384
|
+
const injectionCaptures = parserState.queries.injections.captures(parserState.tree.rootNode);
|
|
385
|
+
const languageGroups = new Map;
|
|
386
|
+
const injectionMapping = parserState.injectionMapping;
|
|
387
|
+
for (const capture of injectionCaptures) {
|
|
388
|
+
const captureName = capture.name;
|
|
389
|
+
if (captureName === "injection.content" || captureName.includes("injection")) {
|
|
390
|
+
const nodeType = capture.node.type;
|
|
391
|
+
let targetLanguage;
|
|
392
|
+
if (injectionMapping?.nodeTypes && injectionMapping.nodeTypes[nodeType]) {
|
|
393
|
+
targetLanguage = injectionMapping.nodeTypes[nodeType];
|
|
394
|
+
} else if (nodeType === "code_fence_content") {
|
|
395
|
+
const parent = capture.node.parent;
|
|
396
|
+
if (parent) {
|
|
397
|
+
const infoString = parent.children.find((child) => child.type === "info_string");
|
|
398
|
+
if (infoString) {
|
|
399
|
+
const languageNode = infoString.children.find((child) => child.type === "language");
|
|
400
|
+
if (languageNode) {
|
|
401
|
+
const languageName = this.getNodeText(languageNode, content);
|
|
402
|
+
if (injectionMapping?.infoStringMap && injectionMapping.infoStringMap[languageName]) {
|
|
403
|
+
targetLanguage = injectionMapping.infoStringMap[languageName];
|
|
404
|
+
} else {
|
|
405
|
+
targetLanguage = languageName;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (targetLanguage) {
|
|
412
|
+
if (!languageGroups.has(targetLanguage)) {
|
|
413
|
+
languageGroups.set(targetLanguage, []);
|
|
414
|
+
}
|
|
415
|
+
languageGroups.get(targetLanguage).push({ node: capture.node, name: capture.name });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const [language, captures] of languageGroups.entries()) {
|
|
420
|
+
const injectedParser = await this.getReusableParser(language);
|
|
421
|
+
if (!injectedParser) {
|
|
422
|
+
console.warn(`No parser found for injection language: ${language}`);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (!injectionRanges.has(language)) {
|
|
426
|
+
injectionRanges.set(language, []);
|
|
427
|
+
}
|
|
428
|
+
const parser = injectedParser.parser;
|
|
429
|
+
for (const { node: injectionNode } of captures) {
|
|
430
|
+
try {
|
|
431
|
+
injectionRanges.get(language).push({
|
|
432
|
+
start: injectionNode.startIndex,
|
|
433
|
+
end: injectionNode.endIndex
|
|
434
|
+
});
|
|
435
|
+
const injectionContent = this.getNodeText(injectionNode, content);
|
|
436
|
+
const tree = parser.parse(injectionContent);
|
|
437
|
+
if (tree) {
|
|
438
|
+
const matches = injectedParser.queries.highlights.captures(tree.rootNode);
|
|
439
|
+
for (const match of matches) {
|
|
440
|
+
const offsetCapture = {
|
|
441
|
+
name: match.name,
|
|
442
|
+
patternIndex: match.patternIndex,
|
|
443
|
+
_injectedQuery: injectedParser.queries.highlights,
|
|
444
|
+
node: {
|
|
445
|
+
...match.node,
|
|
446
|
+
startPosition: {
|
|
447
|
+
row: match.node.startPosition.row + injectionNode.startPosition.row,
|
|
448
|
+
column: match.node.startPosition.row === 0 ? match.node.startPosition.column + injectionNode.startPosition.column : match.node.startPosition.column
|
|
449
|
+
},
|
|
450
|
+
endPosition: {
|
|
451
|
+
row: match.node.endPosition.row + injectionNode.startPosition.row,
|
|
452
|
+
column: match.node.endPosition.row === 0 ? match.node.endPosition.column + injectionNode.startPosition.column : match.node.endPosition.column
|
|
453
|
+
},
|
|
454
|
+
startIndex: match.node.startIndex + injectionNode.startIndex,
|
|
455
|
+
endIndex: match.node.endIndex + injectionNode.startIndex
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
injectionMatches.push(offsetCapture);
|
|
459
|
+
}
|
|
460
|
+
tree.delete();
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error(`Error processing injection for language ${language}:`, error);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return { captures: injectionMatches, injectionRanges };
|
|
349
468
|
}
|
|
350
469
|
editToRange(edit) {
|
|
351
470
|
return {
|
|
@@ -366,6 +485,7 @@ class ParserWorker {
|
|
|
366
485
|
if (!parserState) {
|
|
367
486
|
return { warning: "No parser state found for buffer" };
|
|
368
487
|
}
|
|
488
|
+
parserState.content = content;
|
|
369
489
|
for (const edit of edits) {
|
|
370
490
|
parserState.tree.edit(edit);
|
|
371
491
|
}
|
|
@@ -413,6 +533,12 @@ class ParserWorker {
|
|
|
413
533
|
const nodeCaptures = parserState.queries.highlights.captures(node);
|
|
414
534
|
matches.push(...nodeCaptures);
|
|
415
535
|
}
|
|
536
|
+
let injectionRanges = new Map;
|
|
537
|
+
if (parserState.queries.injections) {
|
|
538
|
+
const injectionResult = await this.processInjections(parserState);
|
|
539
|
+
matches.push(...injectionResult.captures);
|
|
540
|
+
injectionRanges = injectionResult.injectionRanges;
|
|
541
|
+
}
|
|
416
542
|
const endQuery = performance.now();
|
|
417
543
|
const queryTime = endQuery - startQuery;
|
|
418
544
|
this.performance.queryTimes.push(queryTime);
|
|
@@ -420,12 +546,12 @@ class ParserWorker {
|
|
|
420
546
|
this.performance.queryTimes.shift();
|
|
421
547
|
}
|
|
422
548
|
this.performance.averageQueryTime = this.performance.queryTimes.reduce((acc, time) => acc + time, 0) / this.performance.queryTimes.length;
|
|
423
|
-
return this.getHighlights(parserState, matches);
|
|
549
|
+
return this.getHighlights(parserState, matches, injectionRanges);
|
|
424
550
|
}
|
|
425
551
|
nodeContainsRange(node, range) {
|
|
426
552
|
return node.startPosition.row <= range.startPosition.row && node.endPosition.row >= range.endPosition.row && (node.startPosition.row < range.startPosition.row || node.startPosition.column <= range.startPosition.column) && (node.endPosition.row > range.endPosition.row || node.endPosition.column >= range.endPosition.column);
|
|
427
553
|
}
|
|
428
|
-
getHighlights(parserState, matches) {
|
|
554
|
+
getHighlights(parserState, matches, injectionRanges) {
|
|
429
555
|
const lineHighlights = new Map;
|
|
430
556
|
const droppedHighlights = new Map;
|
|
431
557
|
for (const match of matches) {
|
|
@@ -467,12 +593,54 @@ class ParserWorker {
|
|
|
467
593
|
}))
|
|
468
594
|
};
|
|
469
595
|
}
|
|
470
|
-
getSimpleHighlights(matches) {
|
|
596
|
+
getSimpleHighlights(matches, injectionRanges) {
|
|
471
597
|
const highlights = [];
|
|
598
|
+
const flatInjectionRanges = [];
|
|
599
|
+
for (const [lang, ranges] of injectionRanges.entries()) {
|
|
600
|
+
for (const range of ranges) {
|
|
601
|
+
flatInjectionRanges.push({ ...range, lang });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
472
604
|
for (const match of matches) {
|
|
473
605
|
const node = match.node;
|
|
474
|
-
|
|
606
|
+
let isInjection = false;
|
|
607
|
+
let injectionLang;
|
|
608
|
+
let containsInjection = false;
|
|
609
|
+
for (const injRange of flatInjectionRanges) {
|
|
610
|
+
if (node.startIndex >= injRange.start && node.endIndex <= injRange.end) {
|
|
611
|
+
isInjection = true;
|
|
612
|
+
injectionLang = injRange.lang;
|
|
613
|
+
break;
|
|
614
|
+
} else if (node.startIndex <= injRange.start && node.endIndex >= injRange.end) {
|
|
615
|
+
containsInjection = true;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const matchQuery = match._injectedQuery;
|
|
620
|
+
const patternProperties = matchQuery?.setProperties?.[match.patternIndex];
|
|
621
|
+
const concealValue = patternProperties?.conceal ?? match.setProperties?.conceal;
|
|
622
|
+
const concealLines = patternProperties?.conceal_lines ?? match.setProperties?.conceal_lines;
|
|
623
|
+
const meta = {};
|
|
624
|
+
if (isInjection && injectionLang) {
|
|
625
|
+
meta.isInjection = true;
|
|
626
|
+
meta.injectionLang = injectionLang;
|
|
627
|
+
}
|
|
628
|
+
if (containsInjection) {
|
|
629
|
+
meta.containsInjection = true;
|
|
630
|
+
}
|
|
631
|
+
if (concealValue !== undefined) {
|
|
632
|
+
meta.conceal = concealValue;
|
|
633
|
+
}
|
|
634
|
+
if (concealLines !== undefined) {
|
|
635
|
+
meta.concealLines = concealLines;
|
|
636
|
+
}
|
|
637
|
+
if (Object.keys(meta).length > 0) {
|
|
638
|
+
highlights.push([node.startIndex, node.endIndex, match.name, meta]);
|
|
639
|
+
} else {
|
|
640
|
+
highlights.push([node.startIndex, node.endIndex, match.name]);
|
|
641
|
+
}
|
|
475
642
|
}
|
|
643
|
+
highlights.sort((a, b) => a[0] - b[0]);
|
|
476
644
|
return highlights;
|
|
477
645
|
}
|
|
478
646
|
async handleResetBuffer(bufferId, version, content) {
|
|
@@ -480,13 +648,20 @@ class ParserWorker {
|
|
|
480
648
|
if (!parserState) {
|
|
481
649
|
return { warning: "No parser state found for buffer" };
|
|
482
650
|
}
|
|
651
|
+
parserState.content = content;
|
|
483
652
|
const newTree = parserState.parser.parse(content);
|
|
484
653
|
if (!newTree) {
|
|
485
654
|
return { error: "Failed to parse buffer during reset" };
|
|
486
655
|
}
|
|
487
656
|
parserState.tree = newTree;
|
|
488
657
|
const matches = parserState.queries.highlights.captures(parserState.tree.rootNode);
|
|
489
|
-
|
|
658
|
+
let injectionRanges = new Map;
|
|
659
|
+
if (parserState.queries.injections) {
|
|
660
|
+
const injectionResult = await this.processInjections(parserState);
|
|
661
|
+
matches.push(...injectionResult.captures);
|
|
662
|
+
injectionRanges = injectionResult.injectionRanges;
|
|
663
|
+
}
|
|
664
|
+
return this.getHighlights(parserState, matches, injectionRanges);
|
|
490
665
|
}
|
|
491
666
|
disposeBuffer(bufferId) {
|
|
492
667
|
const parserState = this.bufferParsers.get(bufferId);
|
|
@@ -508,7 +683,9 @@ class ParserWorker {
|
|
|
508
683
|
});
|
|
509
684
|
return;
|
|
510
685
|
}
|
|
511
|
-
const
|
|
686
|
+
const parseContent = filetype === "markdown" && content.endsWith("```") ? content + `
|
|
687
|
+
` : content;
|
|
688
|
+
const tree = reusableState.parser.parse(parseContent);
|
|
512
689
|
if (!tree) {
|
|
513
690
|
self.postMessage({
|
|
514
691
|
type: "ONESHOT_HIGHLIGHT_RESPONSE",
|
|
@@ -520,7 +697,21 @@ class ParserWorker {
|
|
|
520
697
|
}
|
|
521
698
|
try {
|
|
522
699
|
const matches = reusableState.filetypeParser.queries.highlights.captures(tree.rootNode);
|
|
523
|
-
|
|
700
|
+
let injectionRanges = new Map;
|
|
701
|
+
if (reusableState.filetypeParser.queries.injections) {
|
|
702
|
+
const parserState = {
|
|
703
|
+
parser: reusableState.parser,
|
|
704
|
+
tree,
|
|
705
|
+
queries: reusableState.filetypeParser.queries,
|
|
706
|
+
filetype,
|
|
707
|
+
content,
|
|
708
|
+
injectionMapping: reusableState.filetypeParser.injectionMapping
|
|
709
|
+
};
|
|
710
|
+
const injectionResult = await this.processInjections(parserState);
|
|
711
|
+
matches.push(...injectionResult.captures);
|
|
712
|
+
injectionRanges = injectionResult.injectionRanges;
|
|
713
|
+
}
|
|
714
|
+
const highlights = this.getSimpleHighlights(matches, injectionRanges);
|
|
524
715
|
self.postMessage({
|
|
525
716
|
type: "ONESHOT_HIGHLIGHT_RESPONSE",
|
|
526
717
|
messageId,
|
|
@@ -533,13 +724,32 @@ class ParserWorker {
|
|
|
533
724
|
}
|
|
534
725
|
async updateDataPath(dataPath) {
|
|
535
726
|
this.dataPath = dataPath;
|
|
727
|
+
this.tsDataPath = path2.join(dataPath, "tree-sitter");
|
|
536
728
|
try {
|
|
537
|
-
await mkdir2(path2.join(
|
|
538
|
-
await mkdir2(path2.join(
|
|
729
|
+
await mkdir2(path2.join(this.tsDataPath, "languages"), { recursive: true });
|
|
730
|
+
await mkdir2(path2.join(this.tsDataPath, "queries"), { recursive: true });
|
|
539
731
|
} catch (error) {
|
|
540
732
|
throw new Error(`Failed to update data path: ${error}`);
|
|
541
733
|
}
|
|
542
734
|
}
|
|
735
|
+
async clearCache() {
|
|
736
|
+
if (!this.dataPath || !this.tsDataPath) {
|
|
737
|
+
throw new Error("No data path configured");
|
|
738
|
+
}
|
|
739
|
+
const { rm } = await import("fs/promises");
|
|
740
|
+
try {
|
|
741
|
+
const treeSitterPath = path2.join(this.dataPath, "tree-sitter");
|
|
742
|
+
await rm(treeSitterPath, { recursive: true, force: true });
|
|
743
|
+
await mkdir2(path2.join(treeSitterPath, "languages"), { recursive: true });
|
|
744
|
+
await mkdir2(path2.join(treeSitterPath, "queries"), { recursive: true });
|
|
745
|
+
this.filetypeParsers.clear();
|
|
746
|
+
this.filetypeParserPromises.clear();
|
|
747
|
+
this.reusableParsers.clear();
|
|
748
|
+
this.reusableParserPromises.clear();
|
|
749
|
+
} catch (error) {
|
|
750
|
+
throw new Error(`Failed to clear cache: ${error}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
543
753
|
}
|
|
544
754
|
if (!isMainThread) {
|
|
545
755
|
let logMessage = function(type, ...args) {
|
|
@@ -552,6 +762,7 @@ if (!isMainThread) {
|
|
|
552
762
|
const worker = new ParserWorker;
|
|
553
763
|
console.log = (...args) => logMessage("log", ...args);
|
|
554
764
|
console.error = (...args) => logMessage("error", ...args);
|
|
765
|
+
console.warn = (...args) => logMessage("warn", ...args);
|
|
555
766
|
self.onmessage = async (e) => {
|
|
556
767
|
const { type, bufferId, version, content, filetype, edits, filetypeParser, messageId, dataPath } = e.data;
|
|
557
768
|
try {
|
|
@@ -619,6 +830,18 @@ if (!isMainThread) {
|
|
|
619
830
|
});
|
|
620
831
|
}
|
|
621
832
|
break;
|
|
833
|
+
case "CLEAR_CACHE":
|
|
834
|
+
try {
|
|
835
|
+
await worker.clearCache();
|
|
836
|
+
self.postMessage({ type: "CLEAR_CACHE_RESPONSE", messageId });
|
|
837
|
+
} catch (error) {
|
|
838
|
+
self.postMessage({
|
|
839
|
+
type: "CLEAR_CACHE_RESPONSE",
|
|
840
|
+
messageId,
|
|
841
|
+
error: error instanceof Error ? error.message : String(error)
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
break;
|
|
622
845
|
default:
|
|
623
846
|
self.postMessage({
|
|
624
847
|
type: "ERROR",
|
|
@@ -636,5 +859,5 @@ if (!isMainThread) {
|
|
|
636
859
|
};
|
|
637
860
|
}
|
|
638
861
|
|
|
639
|
-
//# debugId=
|
|
862
|
+
//# debugId=E1AC39235C034C8264756E2164756E21
|
|
640
863
|
//# sourceMappingURL=parser.worker.js.map
|