@opentui/core 0.1.30 → 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.
Files changed (40) hide show
  1. package/3d.js +1 -1
  2. package/Renderable.d.ts +4 -4
  3. package/assets/markdown/highlights.scm +150 -0
  4. package/assets/markdown/injections.scm +27 -0
  5. package/assets/markdown/tree-sitter-markdown.wasm +0 -0
  6. package/assets/markdown_inline/highlights.scm +115 -0
  7. package/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  8. package/edit-buffer.d.ts +13 -12
  9. package/editor-view.d.ts +10 -0
  10. package/{index-0qmm1k4p.js → index-3f9h747j.js} +1424 -186
  11. package/index-3f9h747j.js.map +56 -0
  12. package/index.js +314 -83
  13. package/index.js.map +14 -14
  14. package/lib/KeyHandler.d.ts +4 -1
  15. package/lib/extmarks-history.d.ts +17 -0
  16. package/lib/extmarks.d.ts +88 -0
  17. package/lib/index.d.ts +2 -0
  18. package/lib/parse.keypress.d.ts +2 -1
  19. package/lib/stdin-buffer.d.ts +42 -0
  20. package/lib/tree-sitter/client.d.ts +1 -0
  21. package/lib/tree-sitter/parsers-config.d.ts +38 -0
  22. package/lib/tree-sitter/types.d.ts +18 -1
  23. package/lib/tree-sitter-styled-text.d.ts +9 -2
  24. package/package.json +9 -9
  25. package/parser.worker.js +250 -27
  26. package/parser.worker.js.map +3 -3
  27. package/renderables/Box.d.ts +1 -0
  28. package/renderables/Code.d.ts +14 -0
  29. package/renderables/EditBufferRenderable.d.ts +20 -4
  30. package/renderables/TextBufferRenderable.d.ts +10 -0
  31. package/renderables/Textarea.d.ts +21 -13
  32. package/renderer.d.ts +1 -0
  33. package/syntax-style.d.ts +2 -0
  34. package/testing/mock-keys.d.ts +22 -61
  35. package/testing.js +36 -78
  36. package/testing.js.map +3 -3
  37. package/text-buffer-view.d.ts +2 -0
  38. package/text-buffer.d.ts +3 -0
  39. package/zig.d.ts +29 -7
  40. package/index-0qmm1k4p.js.map +0 -53
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 fetchHighlightQueries(sources, filetype) {
157
- if (!this.dataPath) {
157
+ async fetchQueries(sources, filetype) {
158
+ if (!this.tsDataPath) {
158
159
  return "";
159
160
  }
160
- return DownloadUtils.fetchHighlightQueries(sources, this.dataPath, filetype);
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(dataPath, "languages"), { recursive: true });
170
- await mkdir2(path2.join(dataPath, "queries"), { recursive: true });
171
- let { default: treeWasm } = await import("web-tree-sitter/web-tree-sitter.wasm", {
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.fetchHighlightQueries(filetypeParser.queries.highlights, filetypeParser.filetype);
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 query = new Query(language, highlightQueryContent);
201
- return {
202
- highlights: query
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.dataPath) {
220
+ if (!this.initialized || !this.tsDataPath) {
211
221
  return;
212
222
  }
213
- const result = await DownloadUtils.downloadOrLoad(languageSource, this.dataPath, "languages", ".wasm", false);
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 = { parser, tree, queries: filetypeParser.queries };
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
- return this.getHighlights(parserState, matches);
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
- highlights.push([node.startIndex, node.endIndex, match.name]);
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
- return this.getHighlights(parserState, matches);
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 tree = reusableState.parser.parse(content);
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
- const highlights = this.getSimpleHighlights(matches);
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(dataPath, "languages"), { recursive: true });
538
- await mkdir2(path2.join(dataPath, "queries"), { recursive: true });
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=E28996FCD9507DC064756E2164756E21
862
+ //# debugId=E1AC39235C034C8264756E2164756E21
640
863
  //# sourceMappingURL=parser.worker.js.map