@fairyhunter13/opentui-core 0.1.112 → 0.1.114

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 (591) hide show
  1. package/dev/keypress-debug-renderer.ts +148 -0
  2. package/dev/keypress-debug.ts +43 -0
  3. package/dev/print-env-vars.ts +32 -0
  4. package/dev/test-tmux-graphics-334.sh +68 -0
  5. package/dev/thai-debug-test.ts +68 -0
  6. package/docs/development.md +144 -0
  7. package/package.json +63 -51
  8. package/scripts/build.ts +400 -0
  9. package/scripts/publish.ts +60 -0
  10. package/src/3d/SpriteResourceManager.ts +286 -0
  11. package/src/3d/SpriteUtils.ts +70 -0
  12. package/src/3d/TextureUtils.ts +196 -0
  13. package/src/3d/ThreeRenderable.ts +197 -0
  14. package/src/3d/WGPURenderer.ts +294 -0
  15. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  16. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  17. package/src/3d/animation/SpriteAnimator.ts +633 -0
  18. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  19. package/src/3d/canvas.ts +464 -0
  20. package/src/3d/index.ts +12 -0
  21. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  22. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  23. package/src/3d/physics/physics-interface.ts +31 -0
  24. package/src/3d/shaders/supersampling.wgsl +201 -0
  25. package/src/3d.ts +3 -0
  26. package/src/NativeSpanFeed.ts +300 -0
  27. package/src/Renderable.ts +1704 -0
  28. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  29. package/src/animation/Timeline.test.ts +2709 -0
  30. package/src/animation/Timeline.ts +598 -0
  31. package/src/ansi.ts +18 -0
  32. package/src/benchmark/attenuation-benchmark.ts +81 -0
  33. package/src/benchmark/colormatrix-benchmark.ts +128 -0
  34. package/src/benchmark/gain-benchmark.ts +80 -0
  35. package/src/benchmark/latest-all-bench-run.json +707 -0
  36. package/src/benchmark/latest-async-bench-run.json +336 -0
  37. package/src/benchmark/latest-default-bench-run.json +657 -0
  38. package/src/benchmark/latest-large-bench-run.json +707 -0
  39. package/src/benchmark/latest-quick-bench-run.json +207 -0
  40. package/src/benchmark/markdown-benchmark.ts +1796 -0
  41. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  42. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  43. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  44. package/src/benchmark/native-span-feed-compare.ts +280 -0
  45. package/src/benchmark/renderer-benchmark.ts +754 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +291 -0
  48. package/src/buffer.ts +554 -0
  49. package/src/console.test.ts +612 -0
  50. package/src/console.ts +1254 -0
  51. package/src/edit-buffer.test.ts +1769 -0
  52. package/src/edit-buffer.ts +411 -0
  53. package/src/editor-view.test.ts +1032 -0
  54. package/src/editor-view.ts +284 -0
  55. package/src/examples/ascii-font-selection-demo.ts +245 -0
  56. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  57. package/src/examples/assets/concrete.png +0 -0
  58. package/src/examples/assets/crate.png +0 -0
  59. package/src/examples/assets/crate_emissive.png +0 -0
  60. package/src/examples/assets/forrest_background.png +0 -0
  61. package/src/examples/assets/hast-example.json +1018 -0
  62. package/src/examples/assets/heart.png +0 -0
  63. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  64. package/src/examples/assets/main_char_idle.png +0 -0
  65. package/src/examples/assets/main_char_jump_end.png +0 -0
  66. package/src/examples/assets/main_char_jump_landing.png +0 -0
  67. package/src/examples/assets/main_char_jump_start.png +0 -0
  68. package/src/examples/assets/main_char_run_loop.png +0 -0
  69. package/src/examples/assets/roughness_map.jpg +0 -0
  70. package/src/examples/build.ts +115 -0
  71. package/src/examples/code-demo.ts +924 -0
  72. package/src/examples/console-demo.ts +358 -0
  73. package/src/examples/core-plugin-slots-demo.ts +759 -0
  74. package/src/examples/diff-demo.ts +701 -0
  75. package/src/examples/draggable-three-demo.ts +259 -0
  76. package/src/examples/editor-demo.ts +322 -0
  77. package/src/examples/extmarks-demo.ts +196 -0
  78. package/src/examples/focus-restore-demo.ts +310 -0
  79. package/src/examples/fonts.ts +245 -0
  80. package/src/examples/fractal-shader-demo.ts +268 -0
  81. package/src/examples/framebuffer-demo.ts +674 -0
  82. package/src/examples/full-unicode-demo.ts +241 -0
  83. package/src/examples/golden-star-demo.ts +933 -0
  84. package/src/examples/grayscale-buffer-demo.ts +249 -0
  85. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  86. package/src/examples/index.ts +926 -0
  87. package/src/examples/input-demo.ts +377 -0
  88. package/src/examples/input-select-layout-demo.ts +425 -0
  89. package/src/examples/install.sh +143 -0
  90. package/src/examples/keypress-debug-demo.ts +452 -0
  91. package/src/examples/lib/HexList.ts +122 -0
  92. package/src/examples/lib/PaletteGrid.ts +125 -0
  93. package/src/examples/lib/standalone-keys.ts +25 -0
  94. package/src/examples/lib/tab-controller.ts +243 -0
  95. package/src/examples/lights-phong-demo.ts +290 -0
  96. package/src/examples/link-demo.ts +220 -0
  97. package/src/examples/live-state-demo.ts +480 -0
  98. package/src/examples/markdown-demo.ts +725 -0
  99. package/src/examples/mouse-interaction-demo.ts +428 -0
  100. package/src/examples/nested-zindex-demo.ts +357 -0
  101. package/src/examples/opacity-example.ts +235 -0
  102. package/src/examples/opentui-demo.ts +1057 -0
  103. package/src/examples/physx-planck-2d-demo.ts +623 -0
  104. package/src/examples/physx-rapier-2d-demo.ts +655 -0
  105. package/src/examples/relative-positioning-demo.ts +323 -0
  106. package/src/examples/scroll-example.ts +214 -0
  107. package/src/examples/scrollbox-mouse-test.ts +112 -0
  108. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  109. package/src/examples/select-demo.ts +237 -0
  110. package/src/examples/shader-cube-demo.ts +1015 -0
  111. package/src/examples/simple-layout-example.ts +591 -0
  112. package/src/examples/slider-demo.ts +617 -0
  113. package/src/examples/split-mode-demo.ts +453 -0
  114. package/src/examples/sprite-animation-demo.ts +443 -0
  115. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  116. package/src/examples/static-sprite-demo.ts +193 -0
  117. package/src/examples/sticky-scroll-example.ts +308 -0
  118. package/src/examples/styled-text-demo.ts +282 -0
  119. package/src/examples/tab-select-demo.ts +219 -0
  120. package/src/examples/terminal-title.ts +29 -0
  121. package/src/examples/terminal.ts +305 -0
  122. package/src/examples/text-node-demo.ts +416 -0
  123. package/src/examples/text-selection-demo.ts +377 -0
  124. package/src/examples/text-table-demo.ts +503 -0
  125. package/src/examples/text-truncation-demo.ts +481 -0
  126. package/src/examples/text-wrap.ts +757 -0
  127. package/src/examples/texture-loading-demo.ts +259 -0
  128. package/src/examples/timeline-example.ts +670 -0
  129. package/src/examples/transparency-demo.ts +400 -0
  130. package/src/examples/vnode-composition-demo.ts +404 -0
  131. package/src/examples/wide-grapheme-overlay-demo.ts +280 -0
  132. package/src/index.ts +24 -0
  133. package/src/lib/KeyHandler.integration.test.ts +292 -0
  134. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  135. package/src/lib/KeyHandler.test.ts +662 -0
  136. package/src/lib/KeyHandler.ts +222 -0
  137. package/src/lib/RGBA.test.ts +984 -0
  138. package/src/lib/RGBA.ts +204 -0
  139. package/src/lib/ascii.font.ts +330 -0
  140. package/src/lib/border.test.ts +83 -0
  141. package/src/lib/border.ts +170 -0
  142. package/src/lib/bunfs.test.ts +27 -0
  143. package/src/lib/bunfs.ts +18 -0
  144. package/src/lib/clipboard.test.ts +41 -0
  145. package/src/lib/clipboard.ts +47 -0
  146. package/src/lib/clock.ts +35 -0
  147. package/src/lib/data-paths.test.ts +133 -0
  148. package/src/lib/data-paths.ts +109 -0
  149. package/src/lib/debounce.ts +106 -0
  150. package/src/lib/detect-links.test.ts +98 -0
  151. package/src/lib/detect-links.ts +56 -0
  152. package/src/lib/env.test.ts +228 -0
  153. package/src/lib/env.ts +209 -0
  154. package/src/lib/extmarks-history.ts +51 -0
  155. package/src/lib/extmarks-multiwidth.test.ts +322 -0
  156. package/src/lib/extmarks.test.ts +3457 -0
  157. package/src/lib/extmarks.ts +843 -0
  158. package/src/lib/fonts/block.json +405 -0
  159. package/src/lib/fonts/grid.json +265 -0
  160. package/src/lib/fonts/huge.json +741 -0
  161. package/src/lib/fonts/pallet.json +314 -0
  162. package/src/lib/fonts/shade.json +591 -0
  163. package/src/lib/fonts/slick.json +321 -0
  164. package/src/lib/fonts/tiny.json +69 -0
  165. package/src/lib/hast-styled-text.ts +59 -0
  166. package/src/lib/index.ts +21 -0
  167. package/src/lib/keymapping.test.ts +317 -0
  168. package/src/lib/keymapping.ts +115 -0
  169. package/src/lib/objects-in-viewport.test.ts +787 -0
  170. package/src/lib/objects-in-viewport.ts +153 -0
  171. package/src/lib/output.capture.ts +58 -0
  172. package/src/lib/parse.keypress-kitty.protocol.test.ts +340 -0
  173. package/src/lib/parse.keypress-kitty.test.ts +663 -0
  174. package/src/lib/parse.keypress-kitty.ts +439 -0
  175. package/src/lib/parse.keypress.test.ts +1849 -0
  176. package/src/lib/parse.keypress.ts +397 -0
  177. package/src/lib/parse.mouse.test.ts +552 -0
  178. package/src/lib/parse.mouse.ts +232 -0
  179. package/src/lib/paste.ts +16 -0
  180. package/src/lib/queue.ts +65 -0
  181. package/src/lib/renderable.validations.test.ts +87 -0
  182. package/src/lib/renderable.validations.ts +83 -0
  183. package/src/lib/scroll-acceleration.ts +98 -0
  184. package/src/lib/selection.ts +240 -0
  185. package/src/lib/singleton.ts +28 -0
  186. package/src/lib/stdin-parser.test.ts +2290 -0
  187. package/src/lib/stdin-parser.ts +1810 -0
  188. package/src/lib/styled-text.ts +178 -0
  189. package/src/lib/terminal-capability-detection.test.ts +202 -0
  190. package/src/lib/terminal-capability-detection.ts +79 -0
  191. package/src/lib/terminal-palette.test.ts +878 -0
  192. package/src/lib/terminal-palette.ts +383 -0
  193. package/src/lib/tree-sitter/assets/README.md +118 -0
  194. package/src/lib/tree-sitter/assets/update.ts +334 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +273 -0
  197. package/src/lib/tree-sitter/client.test.ts +1165 -0
  198. package/src/lib/tree-sitter/client.ts +607 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +86 -0
  200. package/src/lib/tree-sitter/download-utils.ts +148 -0
  201. package/src/lib/tree-sitter/index.ts +28 -0
  202. package/src/lib/tree-sitter/parser.worker.ts +1042 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  204. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  205. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  206. package/src/lib/tree-sitter/types.ts +82 -0
  207. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  208. package/src/lib/tree-sitter-styled-text.ts +306 -0
  209. package/src/lib/validate-dir-name.ts +55 -0
  210. package/src/lib/yoga.options.test.ts +628 -0
  211. package/src/lib/yoga.options.ts +346 -0
  212. package/src/plugins/core-slot.ts +579 -0
  213. package/src/plugins/registry.ts +402 -0
  214. package/src/plugins/types.ts +46 -0
  215. package/src/post/effects.ts +930 -0
  216. package/src/post/filters.ts +489 -0
  217. package/src/post/matrices.ts +288 -0
  218. package/src/renderables/ASCIIFont.ts +219 -0
  219. package/src/renderables/Box.test.ts +205 -0
  220. package/src/renderables/Box.ts +326 -0
  221. package/src/renderables/Code.test.ts +2062 -0
  222. package/src/renderables/Code.ts +357 -0
  223. package/src/renderables/Diff.regression.test.ts +226 -0
  224. package/src/renderables/Diff.test.ts +3101 -0
  225. package/src/renderables/Diff.ts +1211 -0
  226. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  227. package/src/renderables/EditBufferRenderable.ts +1166 -0
  228. package/src/renderables/FrameBuffer.ts +47 -0
  229. package/src/renderables/Input.test.ts +1228 -0
  230. package/src/renderables/Input.ts +247 -0
  231. package/src/renderables/LineNumberRenderable.ts +724 -0
  232. package/src/renderables/Markdown.ts +1393 -0
  233. package/src/renderables/ScrollBar.ts +422 -0
  234. package/src/renderables/ScrollBox.ts +883 -0
  235. package/src/renderables/Select.test.ts +1033 -0
  236. package/src/renderables/Select.ts +524 -0
  237. package/src/renderables/Slider.test.ts +456 -0
  238. package/src/renderables/Slider.ts +342 -0
  239. package/src/renderables/TabSelect.test.ts +197 -0
  240. package/src/renderables/TabSelect.ts +455 -0
  241. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  242. package/src/renderables/Text.test.ts +2660 -0
  243. package/src/renderables/Text.ts +147 -0
  244. package/src/renderables/TextBufferRenderable.ts +518 -0
  245. package/src/renderables/TextNode.test.ts +1058 -0
  246. package/src/renderables/TextNode.ts +325 -0
  247. package/src/renderables/TextTable.test.ts +1421 -0
  248. package/src/renderables/TextTable.ts +1344 -0
  249. package/src/renderables/Textarea.ts +430 -0
  250. package/src/renderables/TimeToFirstDraw.ts +89 -0
  251. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  252. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  253. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  254. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  255. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  256. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  257. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1865 -0
  258. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  259. package/src/renderables/__tests__/Markdown.code-colors.test.ts +242 -0
  260. package/src/renderables/__tests__/Markdown.test.ts +2518 -0
  261. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  262. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  263. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  264. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  265. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  266. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  267. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  268. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  269. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  270. package/src/renderables/__tests__/Textarea.rendering.test.ts +1866 -0
  271. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  272. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  273. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  274. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  275. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  276. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  277. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  278. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  279. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  280. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  281. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  282. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  283. package/src/renderables/composition/README.md +8 -0
  284. package/src/renderables/composition/VRenderable.ts +32 -0
  285. package/src/renderables/composition/constructs.ts +127 -0
  286. package/src/renderables/composition/vnode.ts +289 -0
  287. package/src/renderables/index.ts +23 -0
  288. package/src/renderables/markdown-parser.ts +66 -0
  289. package/src/renderer.ts +2681 -0
  290. package/src/runtime-plugin-support.ts +39 -0
  291. package/src/runtime-plugin.ts +615 -0
  292. package/src/syntax-style.test.ts +841 -0
  293. package/src/syntax-style.ts +257 -0
  294. package/src/testing/README.md +210 -0
  295. package/src/testing/capture-spans.test.ts +194 -0
  296. package/src/testing/integration.test.ts +276 -0
  297. package/src/testing/manual-clock.ts +117 -0
  298. package/src/testing/mock-keys.test.ts +1378 -0
  299. package/src/testing/mock-keys.ts +457 -0
  300. package/src/testing/mock-mouse.test.ts +218 -0
  301. package/src/testing/mock-mouse.ts +247 -0
  302. package/src/testing/mock-tree-sitter-client.ts +73 -0
  303. package/src/testing/spy.ts +13 -0
  304. package/src/testing/test-recorder.test.ts +415 -0
  305. package/src/testing/test-recorder.ts +145 -0
  306. package/src/testing/test-renderer.ts +132 -0
  307. package/src/testing.ts +7 -0
  308. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  309. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  310. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  311. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  312. package/src/tests/allocator-stats.test.ts +38 -0
  313. package/src/tests/destroy-during-render.test.ts +200 -0
  314. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  315. package/src/tests/destroy-on-exit.test.ts +41 -0
  316. package/src/tests/hover-cursor.test.ts +98 -0
  317. package/src/tests/native-span-feed-async.test.ts +173 -0
  318. package/src/tests/native-span-feed-close.test.ts +120 -0
  319. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  320. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  321. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  322. package/src/tests/opacity.test.ts +123 -0
  323. package/src/tests/renderable.snapshot.test.ts +524 -0
  324. package/src/tests/renderable.test.ts +1281 -0
  325. package/src/tests/renderer.clock.test.ts +158 -0
  326. package/src/tests/renderer.console-startup.test.ts +185 -0
  327. package/src/tests/renderer.control.test.ts +425 -0
  328. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  329. package/src/tests/renderer.cursor.test.ts +26 -0
  330. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  331. package/src/tests/renderer.focus-restore.test.ts +257 -0
  332. package/src/tests/renderer.focus.test.ts +294 -0
  333. package/src/tests/renderer.idle.test.ts +219 -0
  334. package/src/tests/renderer.input.test.ts +2237 -0
  335. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  336. package/src/tests/renderer.mouse.test.ts +1274 -0
  337. package/src/tests/renderer.palette.test.ts +629 -0
  338. package/src/tests/renderer.selection.test.ts +49 -0
  339. package/src/tests/renderer.slot-registry.test.ts +684 -0
  340. package/src/tests/renderer.useMouse.test.ts +47 -0
  341. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  342. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  343. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  344. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  345. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  346. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  347. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  348. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  349. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  350. package/src/tests/runtime-plugin-support.test.ts +19 -0
  351. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  352. package/src/tests/runtime-plugin.fixture.ts +40 -0
  353. package/src/tests/runtime-plugin.test.ts +354 -0
  354. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  355. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  356. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  357. package/src/tests/scrollbox.test.ts +1530 -0
  358. package/src/tests/wrap-resize-perf.test.ts +276 -0
  359. package/src/tests/yoga-setters.test.ts +921 -0
  360. package/src/text-buffer-view.test.ts +705 -0
  361. package/src/text-buffer-view.ts +189 -0
  362. package/src/text-buffer.test.ts +347 -0
  363. package/src/text-buffer.ts +250 -0
  364. package/src/types.ts +161 -0
  365. package/src/utils.ts +88 -0
  366. package/src/zig/ansi.zig +268 -0
  367. package/src/zig/bench/README.md +50 -0
  368. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  369. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  370. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  371. package/src/zig/bench/rope-markers_bench.zig +713 -0
  372. package/src/zig/bench/rope_bench.zig +514 -0
  373. package/src/zig/bench/styled-text_bench.zig +470 -0
  374. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  375. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  376. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  377. package/src/zig/bench/utf8_bench.zig +799 -0
  378. package/src/zig/bench-utils.zig +431 -0
  379. package/src/zig/bench.zig +217 -0
  380. package/src/zig/buffer-methods.zig +211 -0
  381. package/src/zig/buffer.zig +2281 -0
  382. package/src/zig/build.zig +289 -0
  383. package/src/zig/build.zig.zon +16 -0
  384. package/src/zig/edit-buffer.zig +825 -0
  385. package/src/zig/editor-view.zig +802 -0
  386. package/src/zig/event-bus.zig +13 -0
  387. package/src/zig/event-emitter.zig +65 -0
  388. package/src/zig/file-logger.zig +92 -0
  389. package/src/zig/grapheme.zig +599 -0
  390. package/src/zig/lib.zig +1854 -0
  391. package/src/zig/link.zig +333 -0
  392. package/src/zig/logger.zig +43 -0
  393. package/src/zig/mem-registry.zig +125 -0
  394. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  395. package/src/zig/native-span-feed.zig +708 -0
  396. package/src/zig/renderer.zig +1393 -0
  397. package/src/zig/rope.zig +1220 -0
  398. package/src/zig/syntax-style.zig +161 -0
  399. package/src/zig/terminal.zig +987 -0
  400. package/src/zig/test.zig +72 -0
  401. package/src/zig/tests/README.md +18 -0
  402. package/src/zig/tests/buffer-methods_test.zig +1109 -0
  403. package/src/zig/tests/buffer_test.zig +2557 -0
  404. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  405. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  406. package/src/zig/tests/editor-view_test.zig +3299 -0
  407. package/src/zig/tests/event-emitter_test.zig +249 -0
  408. package/src/zig/tests/grapheme_test.zig +1304 -0
  409. package/src/zig/tests/link_test.zig +190 -0
  410. package/src/zig/tests/mem-registry_test.zig +473 -0
  411. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  412. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  413. package/src/zig/tests/renderer_test.zig +1017 -0
  414. package/src/zig/tests/rope-nested_test.zig +712 -0
  415. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  416. package/src/zig/tests/rope_test.zig +2362 -0
  417. package/src/zig/tests/segment-merge.test.zig +148 -0
  418. package/src/zig/tests/syntax-style_test.zig +557 -0
  419. package/src/zig/tests/terminal_test.zig +754 -0
  420. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  421. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  422. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  423. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  424. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  425. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  426. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  427. package/src/zig/tests/text-buffer_test.zig +2191 -0
  428. package/src/zig/tests/unicode-width-map.zon +3909 -0
  429. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  430. package/src/zig/tests/utf8_test.zig +4057 -0
  431. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  432. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  433. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  434. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  435. package/src/zig/text-buffer-iterators.zig +499 -0
  436. package/src/zig/text-buffer-segment.zig +404 -0
  437. package/src/zig/text-buffer-view.zig +1371 -0
  438. package/src/zig/text-buffer.zig +1180 -0
  439. package/src/zig/utf8.zig +1948 -0
  440. package/src/zig/utils.zig +9 -0
  441. package/src/zig-structs.ts +261 -0
  442. package/src/zig.ts +3884 -0
  443. package/tsconfig.build.json +24 -0
  444. package/tsconfig.json +27 -0
  445. package/3d/SpriteResourceManager.d.ts +0 -74
  446. package/3d/SpriteUtils.d.ts +0 -13
  447. package/3d/TextureUtils.d.ts +0 -24
  448. package/3d/ThreeRenderable.d.ts +0 -40
  449. package/3d/WGPURenderer.d.ts +0 -61
  450. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  451. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  452. package/3d/animation/SpriteAnimator.d.ts +0 -124
  453. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  454. package/3d/canvas.d.ts +0 -44
  455. package/3d/index.d.ts +0 -12
  456. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  457. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  458. package/3d/physics/physics-interface.d.ts +0 -27
  459. package/3d.d.ts +0 -2
  460. package/3d.js +0 -34041
  461. package/3d.js.map +0 -155
  462. package/LICENSE +0 -21
  463. package/NativeSpanFeed.d.ts +0 -41
  464. package/Renderable.d.ts +0 -334
  465. package/animation/Timeline.d.ts +0 -126
  466. package/ansi.d.ts +0 -13
  467. package/buffer.d.ts +0 -111
  468. package/console.d.ts +0 -144
  469. package/edit-buffer.d.ts +0 -98
  470. package/editor-view.d.ts +0 -73
  471. package/index-8fks7yv1.js +0 -411
  472. package/index-8fks7yv1.js.map +0 -10
  473. package/index-egy5e2rs.js +0 -12267
  474. package/index-egy5e2rs.js.map +0 -42
  475. package/index-tse8gzh0.js +0 -20614
  476. package/index-tse8gzh0.js.map +0 -67
  477. package/index.d.ts +0 -23
  478. package/index.js +0 -478
  479. package/index.js.map +0 -9
  480. package/lib/KeyHandler.d.ts +0 -61
  481. package/lib/RGBA.d.ts +0 -25
  482. package/lib/ascii.font.d.ts +0 -508
  483. package/lib/border.d.ts +0 -51
  484. package/lib/bunfs.d.ts +0 -7
  485. package/lib/clipboard.d.ts +0 -17
  486. package/lib/clock.d.ts +0 -15
  487. package/lib/data-paths.d.ts +0 -26
  488. package/lib/debounce.d.ts +0 -42
  489. package/lib/detect-links.d.ts +0 -6
  490. package/lib/env.d.ts +0 -42
  491. package/lib/extmarks-history.d.ts +0 -17
  492. package/lib/extmarks.d.ts +0 -89
  493. package/lib/hast-styled-text.d.ts +0 -17
  494. package/lib/index.d.ts +0 -21
  495. package/lib/keymapping.d.ts +0 -25
  496. package/lib/objects-in-viewport.d.ts +0 -24
  497. package/lib/output.capture.d.ts +0 -24
  498. package/lib/parse.keypress-kitty.d.ts +0 -2
  499. package/lib/parse.keypress.d.ts +0 -26
  500. package/lib/parse.mouse.d.ts +0 -30
  501. package/lib/paste.d.ts +0 -7
  502. package/lib/queue.d.ts +0 -15
  503. package/lib/renderable.validations.d.ts +0 -12
  504. package/lib/scroll-acceleration.d.ts +0 -43
  505. package/lib/selection.d.ts +0 -63
  506. package/lib/singleton.d.ts +0 -7
  507. package/lib/stdin-parser.d.ts +0 -87
  508. package/lib/styled-text.d.ts +0 -63
  509. package/lib/terminal-capability-detection.d.ts +0 -30
  510. package/lib/terminal-palette.d.ts +0 -50
  511. package/lib/tree-sitter/assets/update.d.ts +0 -11
  512. package/lib/tree-sitter/client.d.ts +0 -47
  513. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  514. package/lib/tree-sitter/download-utils.d.ts +0 -21
  515. package/lib/tree-sitter/index.d.ts +0 -8
  516. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  517. package/lib/tree-sitter/parsers-config.d.ts +0 -53
  518. package/lib/tree-sitter/resolve-ft.d.ts +0 -5
  519. package/lib/tree-sitter/types.d.ts +0 -82
  520. package/lib/tree-sitter-styled-text.d.ts +0 -14
  521. package/lib/validate-dir-name.d.ts +0 -1
  522. package/lib/yoga.options.d.ts +0 -32
  523. package/parser.worker.js +0 -899
  524. package/parser.worker.js.map +0 -12
  525. package/plugins/core-slot.d.ts +0 -72
  526. package/plugins/registry.d.ts +0 -42
  527. package/plugins/types.d.ts +0 -34
  528. package/post/effects.d.ts +0 -147
  529. package/post/filters.d.ts +0 -65
  530. package/post/matrices.d.ts +0 -20
  531. package/renderables/ASCIIFont.d.ts +0 -52
  532. package/renderables/Box.d.ts +0 -81
  533. package/renderables/Code.d.ts +0 -78
  534. package/renderables/Diff.d.ts +0 -142
  535. package/renderables/EditBufferRenderable.d.ts +0 -237
  536. package/renderables/FrameBuffer.d.ts +0 -16
  537. package/renderables/Input.d.ts +0 -67
  538. package/renderables/LineNumberRenderable.d.ts +0 -78
  539. package/renderables/Markdown.d.ts +0 -185
  540. package/renderables/ScrollBar.d.ts +0 -77
  541. package/renderables/ScrollBox.d.ts +0 -124
  542. package/renderables/Select.d.ts +0 -115
  543. package/renderables/Slider.d.ts +0 -47
  544. package/renderables/TabSelect.d.ts +0 -96
  545. package/renderables/Text.d.ts +0 -36
  546. package/renderables/TextBufferRenderable.d.ts +0 -105
  547. package/renderables/TextNode.d.ts +0 -91
  548. package/renderables/TextTable.d.ts +0 -140
  549. package/renderables/Textarea.d.ts +0 -63
  550. package/renderables/TimeToFirstDraw.d.ts +0 -24
  551. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  552. package/renderables/composition/VRenderable.d.ts +0 -16
  553. package/renderables/composition/constructs.d.ts +0 -35
  554. package/renderables/composition/vnode.d.ts +0 -46
  555. package/renderables/index.d.ts +0 -23
  556. package/renderables/markdown-parser.d.ts +0 -10
  557. package/renderer.d.ts +0 -419
  558. package/runtime-plugin-support.d.ts +0 -3
  559. package/runtime-plugin-support.js +0 -29
  560. package/runtime-plugin-support.js.map +0 -10
  561. package/runtime-plugin.d.ts +0 -16
  562. package/runtime-plugin.js +0 -16
  563. package/runtime-plugin.js.map +0 -9
  564. package/syntax-style.d.ts +0 -54
  565. package/testing/manual-clock.d.ts +0 -17
  566. package/testing/mock-keys.d.ts +0 -81
  567. package/testing/mock-mouse.d.ts +0 -38
  568. package/testing/mock-tree-sitter-client.d.ts +0 -23
  569. package/testing/spy.d.ts +0 -7
  570. package/testing/test-recorder.d.ts +0 -61
  571. package/testing/test-renderer.d.ts +0 -23
  572. package/testing.d.ts +0 -6
  573. package/testing.js +0 -697
  574. package/testing.js.map +0 -15
  575. package/text-buffer-view.d.ts +0 -42
  576. package/text-buffer.d.ts +0 -67
  577. package/types.d.ts +0 -139
  578. package/utils.d.ts +0 -14
  579. package/zig-structs.d.ts +0 -155
  580. package/zig.d.ts +0 -353
  581. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  582. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  583. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  584. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  585. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  586. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  587. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  588. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  589. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  590. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  591. /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,1165 @@
1
+ import { test, expect, beforeEach, afterEach, beforeAll, describe } from "bun:test"
2
+ import { TreeSitterClient } from "./client.js"
3
+ import { tmpdir } from "os"
4
+ import { join } from "path"
5
+ import { mkdir, writeFile, unlink } from "fs/promises"
6
+ import { getDataPaths } from "../data-paths.js"
7
+ import { getTreeSitterClient } from "./index.js"
8
+
9
+ describe("TreeSitterClient", () => {
10
+ let client: TreeSitterClient
11
+ let dataPath: string
12
+
13
+ const sharedDataPath = join(tmpdir(), "tree-sitter-shared-test-data")
14
+
15
+ beforeAll(async () => {
16
+ await mkdir(sharedDataPath, { recursive: true })
17
+ })
18
+
19
+ beforeEach(async () => {
20
+ dataPath = sharedDataPath
21
+ client = new TreeSitterClient({
22
+ dataPath,
23
+ })
24
+ })
25
+
26
+ afterEach(async () => {
27
+ if (client) {
28
+ await client.destroy()
29
+ }
30
+ })
31
+
32
+ test("should initialize successfully", async () => {
33
+ await client.initialize()
34
+ expect(client.isInitialized()).toBe(true)
35
+ })
36
+
37
+ test("should preload parsers for supported filetypes", async () => {
38
+ await client.initialize()
39
+
40
+ const hasJavaScript = await client.preloadParser("javascript")
41
+ expect(hasJavaScript).toBe(true)
42
+
43
+ const hasJavaScriptReact = await client.preloadParser("javascriptreact")
44
+ expect(hasJavaScriptReact).toBe(true)
45
+
46
+ const hasTypeScript = await client.preloadParser("typescript")
47
+ expect(hasTypeScript).toBe(true)
48
+
49
+ const hasTypeScriptReact = await client.preloadParser("typescriptreact")
50
+ expect(hasTypeScriptReact).toBe(true)
51
+ })
52
+
53
+ test("should return false for unsupported filetypes", async () => {
54
+ await client.initialize()
55
+
56
+ const hasUnsupported = await client.preloadParser("unsupported-language")
57
+ expect(hasUnsupported).toBe(false)
58
+ })
59
+
60
+ test("should create buffer with supported filetype", async () => {
61
+ await client.initialize()
62
+
63
+ const jsCode = 'const hello = "world";'
64
+ const hasParser = await client.createBuffer(1, jsCode, "javascript")
65
+
66
+ expect(hasParser).toBe(true)
67
+
68
+ const buffer = client.getBuffer(1)
69
+ expect(buffer).toBeDefined()
70
+ expect(buffer?.hasParser).toBe(true)
71
+ expect(buffer?.content).toBe(jsCode)
72
+ expect(buffer?.filetype).toBe("javascript")
73
+ })
74
+
75
+ test("should create buffer without parser for unsupported filetype", async () => {
76
+ await client.initialize()
77
+
78
+ const content = "some random content"
79
+ const hasParser = await client.createBuffer(1, content, "unsupported")
80
+
81
+ expect(hasParser).toBe(false)
82
+
83
+ const buffer = client.getBuffer(1)
84
+ expect(buffer).toBeDefined()
85
+ expect(buffer?.hasParser).toBe(false)
86
+ })
87
+
88
+ test("should emit highlights:response event when buffer is updated", async () => {
89
+ await client.initialize()
90
+
91
+ const jsCode = 'const hello = "world";'
92
+ await client.createBuffer(1, jsCode, "javascript")
93
+
94
+ let highlightReceived = false
95
+ let receivedBufferId: number | undefined
96
+ let receivedVersion: number | undefined
97
+
98
+ client.on("highlights:response", (bufferId, version, highlights) => {
99
+ highlightReceived = true
100
+ receivedBufferId = bufferId
101
+ receivedVersion = version
102
+ })
103
+
104
+ await new Promise((resolve) => setTimeout(resolve, 100))
105
+
106
+ const newCode = 'const hello = "world";\nconst foo = 42;'
107
+ const edits = [
108
+ {
109
+ startIndex: jsCode.length,
110
+ oldEndIndex: jsCode.length,
111
+ newEndIndex: newCode.length,
112
+ startPosition: { row: 0, column: jsCode.length },
113
+ oldEndPosition: { row: 0, column: jsCode.length },
114
+ newEndPosition: { row: 1, column: 14 },
115
+ },
116
+ ]
117
+
118
+ await client.updateBuffer(1, edits, newCode, 2)
119
+
120
+ await new Promise((resolve) => setTimeout(resolve, 200))
121
+
122
+ expect(highlightReceived).toBe(true)
123
+ expect(receivedBufferId).toBe(1)
124
+ expect(receivedVersion).toBe(2)
125
+ })
126
+
127
+ test("should handle buffer removal", async () => {
128
+ await client.initialize()
129
+
130
+ const jsCode = 'const hello = "world";'
131
+ await client.createBuffer(1, jsCode, "javascript")
132
+
133
+ let bufferDisposed = false
134
+ client.on("buffer:disposed", (bufferId) => {
135
+ if (bufferId === 1) {
136
+ bufferDisposed = true
137
+ }
138
+ })
139
+
140
+ await client.removeBuffer(1)
141
+
142
+ expect(bufferDisposed).toBe(true)
143
+ expect(client.getBuffer(1)).toBeUndefined()
144
+ })
145
+
146
+ test("should handle multiple buffers", async () => {
147
+ await client.initialize()
148
+
149
+ const jsCode = 'const hello = "world";'
150
+ const tsCode = "interface Test { value: string }"
151
+
152
+ await client.createBuffer(1, jsCode, "javascript")
153
+ await client.createBuffer(2, tsCode, "typescript")
154
+
155
+ const buffers = client.getAllBuffers()
156
+ expect(buffers).toHaveLength(2)
157
+
158
+ const jsBuffer = client.getBuffer(1)
159
+ const tsBuffer = client.getBuffer(2)
160
+
161
+ expect(jsBuffer?.filetype).toBe("javascript")
162
+ expect(tsBuffer?.filetype).toBe("typescript")
163
+ expect(jsBuffer?.hasParser).toBe(true)
164
+ expect(tsBuffer?.hasParser).toBe(true)
165
+ })
166
+
167
+ test("should handle buffer reset", async () => {
168
+ await client.initialize()
169
+
170
+ const jsCode = 'const hello = "world";'
171
+ await client.createBuffer(1, jsCode, "javascript")
172
+
173
+ const newContent = "function test() { return 42; }"
174
+ await client.resetBuffer(1, 2, newContent)
175
+
176
+ const buffer = client.getBuffer(1)
177
+ expect(buffer?.content).toBe(newContent)
178
+ expect(buffer?.version).toBe(2)
179
+ })
180
+
181
+ test("should emit error events for invalid operations", async () => {
182
+ await client.initialize()
183
+
184
+ let errorReceived = false
185
+ let errorMessage = ""
186
+
187
+ client.on("error", (error, bufferId) => {
188
+ errorReceived = true
189
+ errorMessage = error
190
+ })
191
+
192
+ await client.resetBuffer(999, 1, "test")
193
+
194
+ expect(errorReceived).toBe(true)
195
+ expect(errorMessage).toContain("Cannot reset buffer with no parser")
196
+ })
197
+
198
+ test("should prevent duplicate buffer creation", async () => {
199
+ await client.initialize()
200
+
201
+ const jsCode = 'const hello = "world";'
202
+ await client.createBuffer(1, jsCode, "javascript")
203
+
204
+ await expect(client.createBuffer(1, "other code", "javascript")).rejects.toThrow("Buffer with id 1 already exists")
205
+ })
206
+
207
+ test("should handle performance metrics", async () => {
208
+ await client.initialize()
209
+
210
+ const performance = await client.getPerformance()
211
+ expect(performance).toBeDefined()
212
+ expect(typeof performance.averageParseTime).toBe("number")
213
+ expect(typeof performance.averageQueryTime).toBe("number")
214
+ expect(Array.isArray(performance.parseTimes)).toBe(true)
215
+ expect(Array.isArray(performance.queryTimes)).toBe(true)
216
+ })
217
+
218
+ test("should handle concurrent buffer operations", async () => {
219
+ await client.initialize()
220
+
221
+ const promises = []
222
+
223
+ for (let i = 0; i < 5; i++) {
224
+ const code = `const var${i} = ${i};`
225
+ promises.push(client.createBuffer(i, code, "javascript"))
226
+ }
227
+
228
+ const results = await Promise.all(promises)
229
+ expect(results.every((result) => result === true)).toBe(true)
230
+
231
+ const buffers = client.getAllBuffers()
232
+ expect(buffers).toHaveLength(5)
233
+ })
234
+
235
+ test("should clean up resources on destroy", async () => {
236
+ await client.initialize()
237
+
238
+ const jsCode = 'const hello = "world";'
239
+ await client.createBuffer(1, jsCode, "javascript")
240
+
241
+ expect(client.getAllBuffers()).toHaveLength(1)
242
+
243
+ await client.destroy()
244
+
245
+ expect(client.isInitialized()).toBe(false)
246
+ expect(client.getAllBuffers()).toHaveLength(0)
247
+ })
248
+
249
+ test("should perform one-shot highlighting", async () => {
250
+ await client.initialize()
251
+
252
+ const jsCode = 'const hello = "world";\nfunction test() { return 42; }'
253
+ const result = await client.highlightOnce(jsCode, "javascript")
254
+
255
+ expect(result.highlights).toBeDefined()
256
+ expect(result.highlights!.length).toBeGreaterThan(0)
257
+
258
+ const firstHighlight = result.highlights![0]
259
+ expect(Array.isArray(firstHighlight)).toBe(true)
260
+ expect(firstHighlight).toHaveLength(3)
261
+ expect(typeof firstHighlight[0]).toBe("number")
262
+ expect(typeof firstHighlight[1]).toBe("number")
263
+ expect(typeof firstHighlight[2]).toBe("string")
264
+
265
+ const groups = result.highlights!.map((hl) => hl[2])
266
+ expect(groups.length).toBeGreaterThan(0)
267
+ expect(groups).toContain("keyword")
268
+ })
269
+
270
+ test("should handle one-shot highlighting for unsupported filetype", async () => {
271
+ await client.initialize()
272
+
273
+ const result = await client.highlightOnce("some content", "unsupported-lang")
274
+
275
+ expect(result.highlights).toBeUndefined()
276
+ expect(result.warning).toContain("No parser available for filetype unsupported-lang")
277
+ }, 5000)
278
+
279
+ test("should perform multiple one-shot highlights independently", async () => {
280
+ await client.initialize()
281
+
282
+ const jsCode = 'const hello = "world";'
283
+ const tsCode = "interface Test { value: string }"
284
+
285
+ const [jsResult, tsResult] = await Promise.all([
286
+ client.highlightOnce(jsCode, "javascript"),
287
+ client.highlightOnce(tsCode, "typescript"),
288
+ ])
289
+
290
+ expect(jsResult.highlights).toBeDefined()
291
+ expect(tsResult.highlights).toBeDefined()
292
+ expect(jsResult.highlights!.length).toBeGreaterThan(0)
293
+ expect(tsResult.highlights!.length).toBeGreaterThan(0)
294
+
295
+ jsResult.highlights!.forEach((hl) => {
296
+ expect(Array.isArray(hl)).toBe(true)
297
+ expect(hl).toHaveLength(3)
298
+ })
299
+
300
+ tsResult.highlights!.forEach((hl) => {
301
+ expect(Array.isArray(hl)).toBe(true)
302
+ expect(hl).toHaveLength(3)
303
+ })
304
+
305
+ expect(client.getAllBuffers()).toHaveLength(0)
306
+ })
307
+
308
+ test("should perform one-shot highlighting for react parser aliases", async () => {
309
+ await client.initialize()
310
+
311
+ const jsxCode = 'const view = <div className="card">hello</div>'
312
+ const tsxCode = 'const view: JSX.Element = <div className="card">hello</div>'
313
+
314
+ const [jsxResult, tsxResult] = await Promise.all([
315
+ client.highlightOnce(jsxCode, "javascriptreact"),
316
+ client.highlightOnce(tsxCode, "typescriptreact"),
317
+ ])
318
+
319
+ expect(jsxResult.highlights).toBeDefined()
320
+ expect(tsxResult.highlights).toBeDefined()
321
+ expect(jsxResult.highlights!.length).toBeGreaterThan(0)
322
+ expect(tsxResult.highlights!.length).toBeGreaterThan(0)
323
+
324
+ const jsxGroups = jsxResult.highlights!.map((hl) => hl[2])
325
+ const tsxGroups = tsxResult.highlights!.map((hl) => hl[2])
326
+
327
+ expect(jsxGroups).toContain("keyword")
328
+ expect(tsxGroups).toContain("keyword")
329
+ })
330
+
331
+ test("should handle Devanagari characters and highlight ranges after them correctly", async () => {
332
+ await client.initialize()
333
+
334
+ const jsCode = 'const greeting = "नमस्ते";\nconst x = 42;'
335
+ const result = await client.highlightOnce(jsCode, "javascript")
336
+
337
+ expect(result.highlights).toBeDefined()
338
+ expect(result.highlights!.length).toBeGreaterThan(0)
339
+
340
+ const keywordHighlights = result.highlights!.filter((hl) => hl[2] === "keyword")
341
+ expect(keywordHighlights.length).toBeGreaterThanOrEqual(2)
342
+
343
+ const constHighlights = keywordHighlights.filter((hl) => {
344
+ const text = jsCode.substring(hl[0], hl[1])
345
+ return text === "const"
346
+ })
347
+
348
+ expect(constHighlights).toHaveLength(2)
349
+
350
+ const firstConst = constHighlights[0]
351
+ const secondConst = constHighlights[1]
352
+
353
+ expect(jsCode.substring(firstConst[0], firstConst[1])).toBe("const")
354
+ expect(jsCode.substring(secondConst[0], secondConst[1])).toBe("const")
355
+
356
+ expect(firstConst[0]).toBe(0)
357
+ expect(firstConst[1]).toBe(5)
358
+
359
+ expect(secondConst[0]).toBeGreaterThan(firstConst[1])
360
+ const textBetween = jsCode.substring(firstConst[1], secondConst[0])
361
+ expect(textBetween).toContain("नमस्ते")
362
+
363
+ const numberHighlight = result.highlights!.find((hl) => {
364
+ const text = jsCode.substring(hl[0], hl[1])
365
+ return text === "42" && hl[2] === "number"
366
+ })
367
+
368
+ expect(numberHighlight).toBeDefined()
369
+ if (numberHighlight) {
370
+ const [start, end] = numberHighlight
371
+ const actualText = jsCode.substring(start, end)
372
+ expect(actualText).toBe("42")
373
+
374
+ const secondLine = jsCode.split("\n")[1]
375
+ const secondLineStart = jsCode.indexOf(secondLine)
376
+ const expectedStart = secondLineStart + secondLine.indexOf("42")
377
+ expect(start).toBe(expectedStart)
378
+ }
379
+ })
380
+
381
+ test("should support local file paths for parser configuration", async () => {
382
+ const testQueryPath = join(dataPath, `test-highlights-${Date.now()}.scm`)
383
+ const simpleQuery = "(identifier) @variable"
384
+ await writeFile(testQueryPath, simpleQuery, "utf8")
385
+
386
+ try {
387
+ client.addFiletypeParser({
388
+ filetype: "test-lang",
389
+ aliases: ["test-lang-react"],
390
+ queries: {
391
+ highlights: [testQueryPath],
392
+ },
393
+ wasm: "https://github.com/tree-sitter/tree-sitter-javascript/releases/download/v0.23.1/tree-sitter-javascript.wasm",
394
+ })
395
+
396
+ await client.initialize()
397
+
398
+ const hasParser = await client.preloadParser("test-lang")
399
+ expect(hasParser).toBe(true)
400
+
401
+ const hasAliasParser = await client.preloadParser("test-lang-react")
402
+ expect(hasAliasParser).toBe(true)
403
+
404
+ const testCode = "const myVariable = 42;"
405
+ const result = await client.highlightOnce(testCode, "test-lang")
406
+ const aliasResult = await client.highlightOnce(testCode, "test-lang-react")
407
+
408
+ expect(result.highlights).toBeDefined()
409
+ expect(aliasResult.highlights).toBeDefined()
410
+ expect(result.error).toBeUndefined()
411
+ expect(aliasResult.error).toBeUndefined()
412
+ expect(result.warning).toBeUndefined()
413
+ expect(aliasResult.warning).toBeUndefined()
414
+ } finally {
415
+ try {
416
+ await unlink(testQueryPath)
417
+ } catch (e) {
418
+ // Ignore cleanup errors
419
+ }
420
+ }
421
+ })
422
+
423
+ test("should handle concurrent highlightOnce calls efficiently (no duplicate parser loading)", async () => {
424
+ const freshClient = new TreeSitterClient({ dataPath })
425
+ const workerLogs: string[] = []
426
+
427
+ freshClient.on("worker:log", (logType, message) => {
428
+ if (message.includes("Loading from local path:")) {
429
+ workerLogs.push(message)
430
+ }
431
+ })
432
+
433
+ try {
434
+ await freshClient.initialize()
435
+
436
+ const jsCode = 'const hello = "world"; function test() { return 42; }'
437
+ const promises = Array.from({ length: 5 }, () => freshClient.highlightOnce(jsCode, "javascript"))
438
+
439
+ const results = await Promise.all(promises)
440
+
441
+ for (const result of results) {
442
+ expect(result.highlights).toBeDefined()
443
+ expect(result.highlights!.length).toBeGreaterThan(0)
444
+ expect(result.error).toBeUndefined()
445
+ }
446
+
447
+ const firstResult = results[0]
448
+ for (let i = 1; i < results.length; i++) {
449
+ expect(results[i].highlights).toEqual(firstResult.highlights)
450
+ }
451
+
452
+ await new Promise((resolve) => setTimeout(resolve, 100))
453
+
454
+ const languageLoadLogs = workerLogs.filter((log) => log.includes("tree-sitter-javascript.wasm"))
455
+ const queryLoadLogs = workerLogs.filter((log) => log.includes("highlights.scm"))
456
+
457
+ expect(languageLoadLogs.length).toBeLessThanOrEqual(1)
458
+ expect(queryLoadLogs.length).toBeLessThanOrEqual(1)
459
+ } finally {
460
+ await freshClient.destroy()
461
+ }
462
+ })
463
+
464
+ test("should reuse canonical parser assets for aliased filetypes", async () => {
465
+ const freshClient = new TreeSitterClient({ dataPath })
466
+ const workerLogs: string[] = []
467
+
468
+ freshClient.on("worker:log", (_logType, message) => {
469
+ if (message.includes("Loading from local path:")) {
470
+ workerLogs.push(message)
471
+ }
472
+ })
473
+
474
+ try {
475
+ await freshClient.initialize()
476
+
477
+ const jsxCode = 'const view = <div className="card">hello</div>'
478
+ const [canonicalResult, aliasResult] = await Promise.all([
479
+ freshClient.highlightOnce(jsxCode, "javascript"),
480
+ freshClient.highlightOnce(jsxCode, "javascriptreact"),
481
+ ])
482
+
483
+ expect(canonicalResult.highlights).toBeDefined()
484
+ expect(aliasResult.highlights).toBeDefined()
485
+ expect(canonicalResult.error).toBeUndefined()
486
+ expect(aliasResult.error).toBeUndefined()
487
+
488
+ await new Promise((resolve) => setTimeout(resolve, 100))
489
+
490
+ const languageLoadLogs = workerLogs.filter((log) => log.includes("tree-sitter-javascript.wasm"))
491
+ const queryLoadLogs = workerLogs.filter((log) => log.includes("/assets/javascript/highlights.scm"))
492
+
493
+ expect(languageLoadLogs.length).toBeLessThanOrEqual(1)
494
+ expect(queryLoadLogs.length).toBeLessThanOrEqual(1)
495
+ expect(workerLogs.some((log) => log.includes("javascriptreact"))).toBe(false)
496
+ } finally {
497
+ await freshClient.destroy()
498
+ }
499
+ })
500
+ })
501
+
502
+ describe("TreeSitterClient Injections", () => {
503
+ let dataPath: string
504
+
505
+ const injectionsDataPath = join(tmpdir(), "tree-sitter-injections-test-data")
506
+
507
+ beforeAll(async () => {
508
+ await mkdir(injectionsDataPath, { recursive: true })
509
+ })
510
+
511
+ beforeEach(async () => {
512
+ dataPath = injectionsDataPath
513
+ })
514
+
515
+ test("should highlight inline code in markdown using markdown_inline injection", async () => {
516
+ const client = new TreeSitterClient({ dataPath })
517
+
518
+ try {
519
+ await client.initialize()
520
+
521
+ const markdownCode = `# Hello World
522
+
523
+ The \`CodeRenderable\` component provides syntax highlighting.
524
+
525
+ You can use \`const x = 42\` in your code.`
526
+
527
+ const result = await client.highlightOnce(markdownCode, "markdown")
528
+
529
+ expect(result.highlights).toBeDefined()
530
+ expect(result.highlights!.length).toBeGreaterThan(0)
531
+
532
+ const groups = result.highlights!.map((hl) => hl[2])
533
+ const hasInlineCodeHighlights = groups.some((g) => g.includes("markup.raw"))
534
+
535
+ expect(hasInlineCodeHighlights).toBe(true)
536
+ } finally {
537
+ await client.destroy()
538
+ }
539
+ }, 10000)
540
+
541
+ test("should highlight code blocks in markdown using language-specific injection", async () => {
542
+ const client = new TreeSitterClient({ dataPath })
543
+
544
+ try {
545
+ await client.initialize()
546
+
547
+ const markdownCode = `# Code Example
548
+
549
+ \`\`\`typescript
550
+ const hello: string = "world";
551
+ function test() { return 42; }
552
+ \`\`\`
553
+
554
+ Some text here.`
555
+
556
+ const result = await client.highlightOnce(markdownCode, "markdown")
557
+
558
+ expect(result.highlights).toBeDefined()
559
+ expect(result.highlights!.length).toBeGreaterThan(0)
560
+
561
+ const groups = result.highlights!.map((hl) => hl[2])
562
+ const hasTypeScriptHighlights = groups.some((g) => g === "keyword" || g === "type" || g === "function")
563
+
564
+ expect(hasTypeScriptHighlights).toBe(true)
565
+ } finally {
566
+ await client.destroy()
567
+ }
568
+ }, 10000)
569
+
570
+ test("should highlight tsx code blocks in markdown using language-specific injection", async () => {
571
+ const client = new TreeSitterClient({ dataPath })
572
+
573
+ try {
574
+ await client.initialize()
575
+
576
+ const markdownCode = `# Code Example
577
+
578
+ \`\`\`tsx
579
+ const view: JSX.Element = <div>Hello</div>;
580
+ \`\`\`
581
+
582
+ Some text here.`
583
+
584
+ const result = await client.highlightOnce(markdownCode, "markdown")
585
+
586
+ expect(result.highlights).toBeDefined()
587
+ expect(result.highlights!.length).toBeGreaterThan(0)
588
+
589
+ const constHighlight = result.highlights!.find((hl) => {
590
+ const text = markdownCode.substring(hl[0], hl[1])
591
+ return text === "const" && hl[2] === "keyword"
592
+ })
593
+
594
+ expect(constHighlight).toBeDefined()
595
+ } finally {
596
+ await client.destroy()
597
+ }
598
+ }, 10000)
599
+
600
+ test("should return correct offsets for injected code in markdown code blocks", async () => {
601
+ const client = new TreeSitterClient({ dataPath })
602
+
603
+ try {
604
+ await client.initialize()
605
+
606
+ const markdownCode = `# Title\n\n\`\`\`typescript\nconst x = 42;\n\`\`\``
607
+
608
+ const result = await client.highlightOnce(markdownCode, "markdown")
609
+
610
+ expect(result.highlights).toBeDefined()
611
+ expect(result.highlights!.length).toBeGreaterThan(0)
612
+
613
+ const constHighlight = result.highlights!.find((hl) => {
614
+ const text = markdownCode.substring(hl[0], hl[1])
615
+ return text === "const" && hl[2] === "keyword"
616
+ })
617
+
618
+ expect(constHighlight).toBeDefined()
619
+ if (constHighlight) {
620
+ const [start, end, group] = constHighlight
621
+ const text = markdownCode.substring(start, end)
622
+
623
+ expect(text).toBe("const")
624
+ expect(group).toBe("keyword")
625
+ expect(start).toBe(23)
626
+ expect(end).toBe(28)
627
+ }
628
+
629
+ const numberHighlight = result.highlights!.find((hl) => {
630
+ const text = markdownCode.substring(hl[0], hl[1])
631
+ return text === "42" && hl[2] === "number"
632
+ })
633
+
634
+ expect(numberHighlight).toBeDefined()
635
+ if (numberHighlight) {
636
+ const [start, end, group] = numberHighlight
637
+ const text = markdownCode.substring(start, end)
638
+
639
+ expect(text).toBe("42")
640
+ expect(group).toBe("number")
641
+ expect(start).toBe(33)
642
+ expect(end).toBe(35)
643
+ }
644
+ } finally {
645
+ await client.destroy()
646
+ }
647
+ }, 10000)
648
+
649
+ test("should return highlights sorted by start offset for injected code", async () => {
650
+ const client = new TreeSitterClient({ dataPath })
651
+
652
+ try {
653
+ await client.initialize()
654
+
655
+ const markdownCode = `# Documentation
656
+
657
+ Some text with \`inline code\` here.
658
+
659
+ \`\`\`typescript
660
+ const first = 1;
661
+ const second = 2;
662
+ \`\`\`
663
+
664
+ More text with \`another inline\` code.
665
+
666
+ \`\`\`javascript
667
+ function test() {
668
+ return 42;
669
+ }
670
+ \`\`\``
671
+
672
+ const result = await client.highlightOnce(markdownCode, "markdown")
673
+
674
+ expect(result.highlights).toBeDefined()
675
+ expect(result.highlights!.length).toBeGreaterThan(0)
676
+
677
+ for (let i = 1; i < result.highlights!.length; i++) {
678
+ const prevStart = result.highlights![i - 1][0]
679
+ const currStart = result.highlights![i][0]
680
+ expect(currStart).toBeGreaterThanOrEqual(prevStart)
681
+ }
682
+ } finally {
683
+ await client.destroy()
684
+ }
685
+ }, 10000)
686
+
687
+ test("should handle markdown with injections and return valid highlights", async () => {
688
+ const client = new TreeSitterClient({ dataPath })
689
+
690
+ try {
691
+ await client.initialize()
692
+
693
+ const markdownCode = `# Heading
694
+
695
+ Some **bold** text with \`inline code\`.
696
+
697
+ \`\`\`typescript
698
+ const x: string = "hello";
699
+ \`\`\`
700
+
701
+ [Link text](https://example.com)`
702
+
703
+ const result = await client.highlightOnce(markdownCode, "markdown")
704
+
705
+ expect(result.highlights).toBeDefined()
706
+ expect(result.highlights!.length).toBeGreaterThan(0)
707
+
708
+ const overlaps: Array<[number, number]> = []
709
+ for (let i = 0; i < result.highlights!.length; i++) {
710
+ for (let j = i + 1; j < result.highlights!.length; j++) {
711
+ const [start1, end1] = result.highlights![i]
712
+ const [start2, end2] = result.highlights![j]
713
+
714
+ if (start2 < end1) {
715
+ overlaps.push([i, j])
716
+ }
717
+ }
718
+ }
719
+
720
+ expect(overlaps.length).toBeGreaterThanOrEqual(0)
721
+
722
+ const injectionHighlights = result.highlights!.filter((hl) => hl[2].includes("injection"))
723
+ expect(injectionHighlights).toBeDefined()
724
+
725
+ const concealHighlights = result.highlights!.filter((hl) => hl[2] === "conceal")
726
+ expect(concealHighlights).toBeDefined()
727
+
728
+ const blockHighlights = result.highlights!.filter((hl) => hl[2] === "markup.raw.block")
729
+ expect(blockHighlights).toBeDefined()
730
+ } finally {
731
+ await client.destroy()
732
+ }
733
+ }, 10000)
734
+
735
+ test("should handle fast concurrent markdown highlighting requests with injections", async () => {
736
+ const client = new TreeSitterClient({ dataPath })
737
+
738
+ const errors: string[] = []
739
+ client.on("error", (error) => {
740
+ errors.push(error)
741
+ })
742
+
743
+ client.on("worker:log", (logType, message) => {
744
+ if (logType === "error") {
745
+ errors.push(message)
746
+ }
747
+ })
748
+
749
+ try {
750
+ await client.initialize()
751
+
752
+ const markdownCode = `# OpenTUI Documentation
753
+
754
+ ## Getting Started
755
+
756
+ OpenTUI is a modern terminal UI framework built on **tree-sitter** and WebGPU.
757
+
758
+ ### Installation
759
+
760
+ \`\`\`bash
761
+ bun install opentui
762
+ \`\`\`
763
+
764
+ ### Quick Example
765
+
766
+ \`\`\`typescript
767
+ import { createCliRenderer, BoxRenderable } from 'opentui';
768
+
769
+ const renderer = await createCliRenderer();
770
+ const box = new BoxRenderable(renderer, {
771
+ border: true,
772
+ title: "Hello World"
773
+ });
774
+ renderer.root.add(box);
775
+ \`\`\`
776
+
777
+ The \`CodeRenderable\` component provides syntax highlighting.
778
+
779
+ | Property | Type | Description |
780
+ |----------|------|-------------|
781
+ | content | string | Code to display |
782
+ | filetype | string | Language type |`
783
+
784
+ const jsCode = `function test() {
785
+ const hello = "world";
786
+ return hello;
787
+ }`
788
+
789
+ const tsCode = `interface User {
790
+ name: string;
791
+ age: number;
792
+ }
793
+
794
+ const user: User = { name: "Alice", age: 25 };`
795
+
796
+ const promises = []
797
+ for (let i = 0; i < 5; i++) {
798
+ promises.push(client.highlightOnce(markdownCode, "markdown"))
799
+ }
800
+
801
+ const results = await Promise.allSettled(promises)
802
+
803
+ for (let i = 0; i < results.length; i++) {
804
+ const result = results[i]
805
+ if (result.status === "fulfilled") {
806
+ expect(result.value.error).toBeUndefined()
807
+ expect(result.value.highlights).toBeDefined()
808
+ } else {
809
+ throw new Error(`Request ${i} was rejected: ${result.reason}`)
810
+ }
811
+ }
812
+
813
+ await new Promise((resolve) => setTimeout(resolve, 500))
814
+
815
+ const hasMemoryErrors = errors.some((err) => err.includes("Out of bounds memory access"))
816
+ expect(hasMemoryErrors).toBe(false)
817
+ } finally {
818
+ await client.destroy()
819
+ }
820
+ }, 15000)
821
+ })
822
+
823
+ describe("TreeSitterClient Conceal Values", () => {
824
+ let dataPath: string
825
+
826
+ const concealDataPath = join(tmpdir(), "tree-sitter-conceal-test-data")
827
+
828
+ beforeAll(async () => {
829
+ await mkdir(concealDataPath, { recursive: true })
830
+ })
831
+
832
+ beforeEach(async () => {
833
+ dataPath = concealDataPath
834
+ })
835
+
836
+ test("should return conceal values from normal (non-injected) queries", async () => {
837
+ const client = new TreeSitterClient({ dataPath })
838
+
839
+ try {
840
+ await client.initialize()
841
+
842
+ const markdownCode = `![Image Alt Text](https://example.com/image.png)`
843
+
844
+ const result = await client.highlightOnce(markdownCode, "markdown")
845
+
846
+ expect(result.highlights).toBeDefined()
847
+ expect(result.error).toBeUndefined()
848
+
849
+ const concealedHighlights = result.highlights!.filter((hl) => {
850
+ const meta = (hl as any)[3]
851
+ return meta && meta.conceal !== undefined
852
+ })
853
+
854
+ expect(concealedHighlights.length).toBeGreaterThan(0)
855
+
856
+ concealedHighlights.forEach((hl) => {
857
+ const meta = (hl as any)[3]
858
+ expect(meta.conceal).toBeDefined()
859
+ })
860
+ } finally {
861
+ await client.destroy()
862
+ }
863
+ }, 10000)
864
+
865
+ test("should return conceal values from injected queries (markdown_inline)", async () => {
866
+ const client = new TreeSitterClient({ dataPath })
867
+
868
+ try {
869
+ await client.initialize()
870
+
871
+ const markdownCode = `Here is a [link](https://example.com) in text.`
872
+
873
+ const result = await client.highlightOnce(markdownCode, "markdown")
874
+
875
+ expect(result.highlights).toBeDefined()
876
+ expect(result.error).toBeUndefined()
877
+
878
+ const concealedHighlights = result.highlights!.filter((hl) => {
879
+ const meta = (hl as any)[3]
880
+ return meta && meta.conceal !== undefined
881
+ })
882
+
883
+ expect(concealedHighlights.length).toBeGreaterThan(0)
884
+
885
+ concealedHighlights.forEach((hl) => {
886
+ const meta = (hl as any)[3]
887
+ expect(meta.conceal).toBeDefined()
888
+ expect(meta.isInjection).toBeDefined()
889
+ })
890
+
891
+ const closingBracketHighlight = concealedHighlights.find((hl) => {
892
+ const text = markdownCode.substring(hl[0], hl[1])
893
+ const meta = (hl as any)[3]
894
+ return text === "]" && meta.conceal !== ""
895
+ })
896
+
897
+ if (closingBracketHighlight) {
898
+ const meta = (closingBracketHighlight as any)[3]
899
+ expect(meta.conceal).toBeDefined()
900
+ }
901
+ } finally {
902
+ await client.destroy()
903
+ }
904
+ }, 10000)
905
+
906
+ test("should distinguish conceal values between normal and injected queries", async () => {
907
+ const client = new TreeSitterClient({ dataPath })
908
+
909
+ try {
910
+ await client.initialize()
911
+
912
+ const markdownCode = `Here is a [link](https://example.com) and ![image](https://example.com/img.png).`
913
+
914
+ const result = await client.highlightOnce(markdownCode, "markdown")
915
+
916
+ expect(result.highlights).toBeDefined()
917
+ expect(result.error).toBeUndefined()
918
+
919
+ const concealedHighlights = result.highlights!.filter((hl) => {
920
+ const meta = (hl as any)[3]
921
+ return meta && meta.conceal !== undefined
922
+ })
923
+
924
+ expect(concealedHighlights.length).toBeGreaterThan(0)
925
+
926
+ const normalConceal = concealedHighlights.filter((hl) => {
927
+ const meta = (hl as any)[3]
928
+ return !meta.isInjection
929
+ })
930
+
931
+ const injectedConceal = concealedHighlights.filter((hl) => {
932
+ const meta = (hl as any)[3]
933
+ return meta.isInjection
934
+ })
935
+
936
+ expect(injectedConceal.length).toBeGreaterThan(0)
937
+
938
+ injectedConceal.forEach((hl) => {
939
+ const meta = (hl as any)[3]
940
+ expect(meta.conceal).toBeDefined()
941
+ expect(meta.isInjection).toBe(true)
942
+ })
943
+
944
+ concealedHighlights.forEach((hl) => {
945
+ const meta = (hl as any)[3]
946
+ expect(meta.conceal).toBeDefined()
947
+ expect(typeof meta.isInjection).toBe("boolean")
948
+ })
949
+ } finally {
950
+ await client.destroy()
951
+ }
952
+ }, 10000)
953
+
954
+ test("should handle pattern index lookups correctly for injections", async () => {
955
+ const client = new TreeSitterClient({ dataPath })
956
+
957
+ try {
958
+ await client.initialize()
959
+
960
+ const markdownCode = `A [link](url) here.`
961
+
962
+ const result = await client.highlightOnce(markdownCode, "markdown")
963
+
964
+ expect(result.highlights).toBeDefined()
965
+ expect(result.error).toBeUndefined()
966
+
967
+ const concealedHighlights = result.highlights!.filter((hl) => {
968
+ const meta = (hl as any)[3]
969
+ return meta && meta.conceal !== undefined
970
+ })
971
+
972
+ expect(concealedHighlights.length).toBeGreaterThan(0)
973
+
974
+ concealedHighlights.forEach((hl) => {
975
+ const meta = (hl as any)[3]
976
+ expect(meta.conceal).toBeDefined()
977
+ })
978
+ } finally {
979
+ await client.destroy()
980
+ }
981
+ }, 10000)
982
+
983
+ test("should handle multiple injected languages with different conceal patterns", async () => {
984
+ const client = new TreeSitterClient({ dataPath })
985
+
986
+ try {
987
+ await client.initialize()
988
+
989
+ const markdownCode = `# Title
990
+
991
+ Inline \`code\` and a [link](url) here.
992
+
993
+ \`\`\`typescript
994
+ const x = 42;
995
+ \`\`\`
996
+
997
+ More text with ![image](img.png) and **bold**.`
998
+
999
+ const result = await client.highlightOnce(markdownCode, "markdown")
1000
+
1001
+ expect(result.highlights).toBeDefined()
1002
+ expect(result.error).toBeUndefined()
1003
+
1004
+ const concealedHighlights = result.highlights!.filter((hl) => {
1005
+ const meta = (hl as any)[3]
1006
+ return meta && meta.conceal !== undefined
1007
+ })
1008
+
1009
+ expect(concealedHighlights.length).toBeGreaterThan(0)
1010
+
1011
+ const byLang = new Map<string, any[]>()
1012
+ concealedHighlights.forEach((hl) => {
1013
+ const meta = (hl as any)[3]
1014
+ const lang = meta.isInjection ? meta.injectionLang || "injected" : "normal"
1015
+ if (!byLang.has(lang)) {
1016
+ byLang.set(lang, [])
1017
+ }
1018
+ byLang.get(lang)!.push(hl)
1019
+ })
1020
+
1021
+ expect(byLang.size).toBeGreaterThan(0)
1022
+
1023
+ byLang.forEach((highlights) => {
1024
+ expect(highlights.length).toBeGreaterThan(0)
1025
+ highlights.forEach((hl: any) => {
1026
+ const meta = hl[3]
1027
+ expect(meta.conceal).toBeDefined()
1028
+ })
1029
+ })
1030
+ } finally {
1031
+ await client.destroy()
1032
+ }
1033
+ }, 10000)
1034
+
1035
+ test("should preserve non-empty conceal replacements like space character", async () => {
1036
+ const client = new TreeSitterClient({ dataPath })
1037
+
1038
+ try {
1039
+ await client.initialize()
1040
+
1041
+ const markdownCode = `Check [this link](https://example.com) out!`
1042
+
1043
+ const result = await client.highlightOnce(markdownCode, "markdown")
1044
+
1045
+ expect(result.highlights).toBeDefined()
1046
+ expect(result.error).toBeUndefined()
1047
+
1048
+ const closingBracket = result.highlights!.find((hl) => {
1049
+ const text = markdownCode.substring(hl[0], hl[1])
1050
+ const meta = (hl as any)[3]
1051
+ return text === "]" && hl[2] === "conceal" && meta?.conceal !== undefined
1052
+ })
1053
+
1054
+ if (closingBracket) {
1055
+ const meta = (closingBracket as any)[3]
1056
+ expect(meta).toBeDefined()
1057
+ expect(meta.conceal).toBeDefined()
1058
+ expect(meta.conceal).toBe(" ")
1059
+ expect(meta.conceal.length).toBeGreaterThan(0)
1060
+ }
1061
+ } finally {
1062
+ await client.destroy()
1063
+ }
1064
+ }, 10000)
1065
+ })
1066
+
1067
+ describe("TreeSitterClient Edge Cases", () => {
1068
+ let dataPath: string
1069
+
1070
+ const edgeCaseDataPath = join(tmpdir(), "tree-sitter-edge-case-test-data")
1071
+
1072
+ beforeAll(async () => {
1073
+ await mkdir(edgeCaseDataPath, { recursive: true })
1074
+ })
1075
+
1076
+ beforeEach(async () => {
1077
+ dataPath = edgeCaseDataPath
1078
+ })
1079
+
1080
+ test("should handle initialization timeout", async () => {
1081
+ const client = new TreeSitterClient({
1082
+ dataPath,
1083
+ workerPath: "invalid-path",
1084
+ initTimeout: 500,
1085
+ })
1086
+
1087
+ await expect(client.initialize()).rejects.toThrow(/Worker error|Worker initialization timed out/)
1088
+
1089
+ await client.destroy()
1090
+ })
1091
+
1092
+ test("should handle operations before initialization", async () => {
1093
+ const client = new TreeSitterClient({ dataPath })
1094
+
1095
+ expect(client.isInitialized()).toBe(false)
1096
+ expect(client.getAllBuffers()).toHaveLength(0)
1097
+ expect(client.getBuffer(1)).toBeUndefined()
1098
+
1099
+ await client.destroy()
1100
+ })
1101
+
1102
+ test("should handle destroy() during pending initialization", async () => {
1103
+ const client = new TreeSitterClient({ dataPath })
1104
+
1105
+ // Start init but don't await
1106
+ const initPromise = client.initialize()
1107
+
1108
+ // Immediately destroy
1109
+ await client.destroy()
1110
+
1111
+ // Init promise should reject with specific error
1112
+ await expect(initPromise).rejects.toThrow("Client destroyed during initialization")
1113
+
1114
+ expect(client.isInitialized()).toBe(false)
1115
+ })
1116
+
1117
+ test("should handle worker errors gracefully", async () => {
1118
+ const client = new TreeSitterClient({ dataPath })
1119
+
1120
+ let errorReceived = false
1121
+ client.on("error", () => {
1122
+ errorReceived = true
1123
+ })
1124
+
1125
+ const hasParser = await client.createBuffer(1, "test", "javascript", 1, false)
1126
+ expect(hasParser).toBe(false)
1127
+ expect(errorReceived).toBe(true)
1128
+
1129
+ await client.destroy()
1130
+ })
1131
+
1132
+ test("should handle data path changes with reactive getTreeSitterClient", async () => {
1133
+ const dataPathsManager = getDataPaths()
1134
+ const originalAppName = dataPathsManager.appName
1135
+ let client: any
1136
+
1137
+ try {
1138
+ client = getTreeSitterClient()
1139
+ await client.initialize()
1140
+
1141
+ const initialDataPath = dataPathsManager.globalDataPath
1142
+
1143
+ dataPathsManager.appName = "test-app-changed"
1144
+
1145
+ await new Promise((resolve) => setTimeout(resolve, 100))
1146
+
1147
+ const newDataPath = dataPathsManager.globalDataPath
1148
+ expect(newDataPath).not.toBe(initialDataPath)
1149
+ expect(newDataPath).toContain("test-app-changed")
1150
+
1151
+ if (!client.isInitialized()) {
1152
+ await client.initialize()
1153
+ }
1154
+
1155
+ const hasParser = await client.preloadParser("javascript")
1156
+ expect(hasParser).toBe(true)
1157
+ } finally {
1158
+ if (client) {
1159
+ await client.destroy()
1160
+ }
1161
+
1162
+ dataPathsManager.appName = originalAppName
1163
+ }
1164
+ })
1165
+ })