@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,1393 @@
1
+ import { Renderable, type RenderableOptions } from "../Renderable.js"
2
+ import { type RenderContext } from "../types.js"
3
+ import { SyntaxStyle, type StyleDefinition } from "../syntax-style.js"
4
+ import type { TextChunk } from "../text-buffer.js"
5
+ import { createTextAttributes } from "../utils.js"
6
+ import type { BorderStyle } from "../lib/border.js"
7
+ import { RGBA, parseColor, type ColorInput } from "../lib/RGBA.js"
8
+ import { type MarkedToken, type Token, type Tokens } from "marked"
9
+ import { CodeRenderable, type OnChunksCallback } from "./Code.js"
10
+ import {
11
+ TextTableRenderable,
12
+ type TextTableCellContent,
13
+ type TextTableColumnFitter,
14
+ type TextTableColumnWidthMode,
15
+ type TextTableContent,
16
+ } from "./TextTable.js"
17
+ import type { TreeSitterClient } from "../lib/tree-sitter/index.js"
18
+ import { infoStringToFiletype } from "../lib/tree-sitter/resolve-ft.js"
19
+ import { parseMarkdownIncremental, type ParseState } from "./markdown-parser.js"
20
+ import type { OptimizedBuffer } from "../buffer.js"
21
+ import { detectLinks } from "../lib/detect-links.js"
22
+
23
+ export interface MarkdownTableOptions {
24
+ /**
25
+ * Strategy for sizing table columns.
26
+ * - "content": columns fit to intrinsic content width.
27
+ * - "full": columns expand to fill available width.
28
+ */
29
+ widthMode?: TextTableColumnWidthMode
30
+ /**
31
+ * Column fitting method when shrinking constrained tables.
32
+ */
33
+ columnFitter?: TextTableColumnFitter
34
+ /**
35
+ * Wrapping strategy for table cell content.
36
+ */
37
+ wrapMode?: "none" | "char" | "word"
38
+ /**
39
+ * Padding applied on all sides of each table cell.
40
+ */
41
+ cellPadding?: number
42
+ /**
43
+ * Enables/disables table border rendering.
44
+ */
45
+ borders?: boolean
46
+ /**
47
+ * Overrides outer border visibility. Defaults to `borders`.
48
+ */
49
+ outerBorder?: boolean
50
+ /**
51
+ * Border style for markdown tables.
52
+ */
53
+ borderStyle?: BorderStyle
54
+ /**
55
+ * Border color for markdown tables. Defaults to conceal style color.
56
+ */
57
+ borderColor?: ColorInput
58
+ /**
59
+ * Enables/disables selection support on markdown tables.
60
+ */
61
+ selectable?: boolean
62
+ }
63
+
64
+ export interface MarkdownOptions extends RenderableOptions<MarkdownRenderable> {
65
+ content?: string
66
+ syntaxStyle: SyntaxStyle
67
+ fg?: ColorInput
68
+ bg?: ColorInput
69
+ /** Controls concealment for markdown syntax markers in markdown text blocks. */
70
+ conceal?: boolean
71
+ /** Controls concealment inside fenced code blocks rendered by CodeRenderable. */
72
+ concealCode?: boolean
73
+ treeSitterClient?: TreeSitterClient
74
+ /**
75
+ * Enable streaming mode for incremental content updates.
76
+ *
77
+ * Semantics:
78
+ * - The trailing markdown block stays unstable while streaming is enabled.
79
+ * - Tables render all rows produced by the markdown parser (including trailing rows).
80
+ * - Incomplete table rows are normalized by the parser and rendered with empty cells
81
+ * where data is missing.
82
+ *
83
+ * Expectations:
84
+ * - Keep this true while chunks are still being appended.
85
+ * - Set this to false once streaming is complete to finalize trailing token parsing.
86
+ */
87
+ streaming?: boolean
88
+ /**
89
+ * Options for internally rendered markdown tables.
90
+ */
91
+ tableOptions?: MarkdownTableOptions
92
+ /**
93
+ * Custom node renderer. Return a Renderable to override default rendering,
94
+ * or undefined/null to use default rendering.
95
+ */
96
+ renderNode?: (token: Token, context: RenderNodeContext) => Renderable | undefined | null
97
+ }
98
+
99
+ export interface RenderNodeContext {
100
+ syntaxStyle: SyntaxStyle
101
+ conceal: boolean
102
+ concealCode: boolean
103
+ treeSitterClient?: TreeSitterClient
104
+ /** Creates default renderable for this token */
105
+ defaultRender: () => Renderable | null
106
+ }
107
+
108
+ interface TableContentCache {
109
+ content: TextTableContent
110
+ cellKeys: Uint32Array[]
111
+ }
112
+
113
+ interface ResolvedTableRenderableOptions {
114
+ columnWidthMode: TextTableColumnWidthMode
115
+ columnFitter: TextTableColumnFitter
116
+ wrapMode: "none" | "char" | "word"
117
+ cellPadding: number
118
+ border: boolean
119
+ outerBorder: boolean
120
+ showBorders: boolean
121
+ borderStyle: BorderStyle
122
+ borderColor: ColorInput
123
+ selectable: boolean
124
+ }
125
+
126
+ const TRAILING_MARKDOWN_BLOCK_BREAKS_RE = /(?:\r?\n)+$/
127
+
128
+ function colorsEqual(left?: RGBA, right?: RGBA): boolean {
129
+ if (!left || !right) return left === right
130
+ return left.equals(right)
131
+ }
132
+
133
+ export interface BlockState {
134
+ token: MarkedToken
135
+ tokenRaw: string // Cache raw for comparison
136
+ renderable: Renderable
137
+ tableContentCache?: TableContentCache
138
+ }
139
+
140
+ export type { ParseState }
141
+
142
+ export class MarkdownRenderable extends Renderable {
143
+ private _content: string = ""
144
+ private _syntaxStyle: SyntaxStyle
145
+ private _fg?: RGBA
146
+ private _bg?: RGBA
147
+ private _conceal: boolean
148
+ private _concealCode: boolean
149
+ private _treeSitterClient?: TreeSitterClient
150
+ private _tableOptions?: MarkdownTableOptions
151
+ private _renderNode?: MarkdownOptions["renderNode"]
152
+
153
+ _parseState: ParseState | null = null
154
+ private _streaming: boolean = false
155
+ _blockStates: BlockState[] = []
156
+ private _styleDirty: boolean = false
157
+ private _linkifyMarkdownChunks: OnChunksCallback = (chunks, context) =>
158
+ detectLinks(chunks, {
159
+ content: context.content,
160
+ highlights: context.highlights,
161
+ })
162
+
163
+ private _inlineConcealChunks: OnChunksCallback = (chunks, context) => {
164
+ return this.buildInlineConcealChunks(chunks, context)
165
+ }
166
+
167
+ protected _contentDefaultOptions = {
168
+ content: "",
169
+ conceal: true,
170
+ concealCode: false,
171
+ streaming: false,
172
+ } satisfies Partial<MarkdownOptions>
173
+
174
+ constructor(ctx: RenderContext, options: MarkdownOptions) {
175
+ super(ctx, {
176
+ ...options,
177
+ flexDirection: "column",
178
+ flexShrink: options.flexShrink ?? 0,
179
+ })
180
+
181
+ this._syntaxStyle = options.syntaxStyle
182
+ this._fg = options.fg ? parseColor(options.fg) : undefined
183
+ this._bg = options.bg ? parseColor(options.bg) : undefined
184
+ this._conceal = options.conceal ?? this._contentDefaultOptions.conceal
185
+ this._concealCode = options.concealCode ?? this._contentDefaultOptions.concealCode
186
+ this._content = options.content ?? this._contentDefaultOptions.content
187
+ this._treeSitterClient = options.treeSitterClient
188
+ this._tableOptions = options.tableOptions
189
+ this._renderNode = options.renderNode
190
+ this._streaming = options.streaming ?? this._contentDefaultOptions.streaming
191
+
192
+ this.updateBlocks()
193
+ }
194
+
195
+ get content(): string {
196
+ return this._content
197
+ }
198
+
199
+ set content(value: string) {
200
+ if (this.isDestroyed) return
201
+ if (this._content !== value) {
202
+ this._content = value
203
+ this.updateBlocks()
204
+ this.requestRender()
205
+ }
206
+ }
207
+
208
+ get syntaxStyle(): SyntaxStyle {
209
+ return this._syntaxStyle
210
+ }
211
+
212
+ set syntaxStyle(value: SyntaxStyle) {
213
+ if (this._syntaxStyle !== value) {
214
+ this._syntaxStyle = value
215
+ // Mark dirty - actual re-render happens in renderSelf
216
+ this._styleDirty = true
217
+ }
218
+ }
219
+
220
+ get fg(): RGBA | undefined {
221
+ return this._fg
222
+ }
223
+
224
+ set fg(value: ColorInput | undefined) {
225
+ const next = value ? parseColor(value) : undefined
226
+ if (!colorsEqual(this._fg, next)) {
227
+ this._fg = next
228
+ this._styleDirty = true
229
+ }
230
+ }
231
+
232
+ get bg(): RGBA | undefined {
233
+ return this._bg
234
+ }
235
+
236
+ set bg(value: ColorInput | undefined) {
237
+ const next = value ? parseColor(value) : undefined
238
+ if (!colorsEqual(this._bg, next)) {
239
+ this._bg = next
240
+ this._styleDirty = true
241
+ }
242
+ }
243
+
244
+ get conceal(): boolean {
245
+ return this._conceal
246
+ }
247
+
248
+ set conceal(value: boolean) {
249
+ if (this._conceal !== value) {
250
+ this._conceal = value
251
+ // Mark dirty - actual re-render happens in renderSelf
252
+ this._styleDirty = true
253
+ }
254
+ }
255
+
256
+ get concealCode(): boolean {
257
+ return this._concealCode
258
+ }
259
+
260
+ set concealCode(value: boolean) {
261
+ if (this._concealCode !== value) {
262
+ this._concealCode = value
263
+ // Mark dirty - actual re-render happens in renderSelf
264
+ this._styleDirty = true
265
+ }
266
+ }
267
+
268
+ get streaming(): boolean {
269
+ return this._streaming
270
+ }
271
+
272
+ set streaming(value: boolean) {
273
+ if (this.isDestroyed) return
274
+ if (this._streaming !== value) {
275
+ this._streaming = value
276
+ this.updateBlocks(true)
277
+ }
278
+ }
279
+
280
+ get tableOptions(): MarkdownTableOptions | undefined {
281
+ return this._tableOptions
282
+ }
283
+
284
+ set tableOptions(value: MarkdownTableOptions | undefined) {
285
+ this._tableOptions = value
286
+ this.applyTableOptionsToBlocks()
287
+ }
288
+
289
+ private getStyle(group: string): StyleDefinition | undefined {
290
+ // The solid reconciler applies props via setters in JSX declaration order.
291
+ // If `content` is set before `syntaxStyle`, updateBlocks() runs before
292
+ // _syntaxStyle is initialized.
293
+ if (!this._syntaxStyle) return undefined
294
+ let style = this._syntaxStyle.getStyle(group)
295
+ if (!style && group.includes(".")) {
296
+ const baseName = group.split(".")[0]
297
+ style = this._syntaxStyle.getStyle(baseName)
298
+ }
299
+ return style
300
+ }
301
+
302
+ private createChunk(text: string, group: string, link?: { url: string }): TextChunk {
303
+ const style = this.getStyle(group) || this.getStyle("default")
304
+ return {
305
+ __isChunk: true,
306
+ text,
307
+ fg: style?.fg,
308
+ bg: style?.bg,
309
+ attributes: style
310
+ ? createTextAttributes({
311
+ bold: style.bold,
312
+ italic: style.italic,
313
+ underline: style.underline,
314
+ dim: style.dim,
315
+ })
316
+ : 0,
317
+ link,
318
+ }
319
+ }
320
+
321
+ private createDefaultChunk(text: string): TextChunk {
322
+ return this.createChunk(text, "default")
323
+ }
324
+
325
+ private renderInlineContent(tokens: Token[], chunks: TextChunk[]): void {
326
+ for (const token of tokens) {
327
+ this.renderInlineToken(token as MarkedToken, chunks)
328
+ }
329
+ }
330
+
331
+ private renderInlineToken(token: MarkedToken, chunks: TextChunk[]): void {
332
+ switch (token.type) {
333
+ case "text":
334
+ chunks.push(this.createDefaultChunk(token.text))
335
+ break
336
+
337
+ case "escape":
338
+ chunks.push(this.createDefaultChunk(token.text))
339
+ break
340
+
341
+ case "codespan":
342
+ if (this._conceal) {
343
+ chunks.push(this.createChunk(token.text, "markup.raw"))
344
+ } else {
345
+ chunks.push(this.createChunk("`", "markup.raw"))
346
+ chunks.push(this.createChunk(token.text, "markup.raw"))
347
+ chunks.push(this.createChunk("`", "markup.raw"))
348
+ }
349
+ break
350
+
351
+ case "strong":
352
+ if (!this._conceal) {
353
+ chunks.push(this.createChunk("**", "markup.strong"))
354
+ }
355
+ for (const child of token.tokens) {
356
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.strong")
357
+ }
358
+ if (!this._conceal) {
359
+ chunks.push(this.createChunk("**", "markup.strong"))
360
+ }
361
+ break
362
+
363
+ case "em":
364
+ if (!this._conceal) {
365
+ chunks.push(this.createChunk("*", "markup.italic"))
366
+ }
367
+ for (const child of token.tokens) {
368
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.italic")
369
+ }
370
+ if (!this._conceal) {
371
+ chunks.push(this.createChunk("*", "markup.italic"))
372
+ }
373
+ break
374
+
375
+ case "del":
376
+ if (!this._conceal) {
377
+ chunks.push(this.createChunk("~~", "markup.strikethrough"))
378
+ }
379
+ for (const child of token.tokens) {
380
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.strikethrough")
381
+ }
382
+ if (!this._conceal) {
383
+ chunks.push(this.createChunk("~~", "markup.strikethrough"))
384
+ }
385
+ break
386
+
387
+ case "link": {
388
+ const linkHref = { url: token.href }
389
+ if (this._conceal) {
390
+ for (const child of token.tokens) {
391
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.link.label", linkHref)
392
+ }
393
+ chunks.push(this.createChunk(" (", "markup.link", linkHref))
394
+ chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
395
+ chunks.push(this.createChunk(")", "markup.link", linkHref))
396
+ } else {
397
+ chunks.push(this.createChunk("[", "markup.link", linkHref))
398
+ for (const child of token.tokens) {
399
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.link.label", linkHref)
400
+ }
401
+ chunks.push(this.createChunk("](", "markup.link", linkHref))
402
+ chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
403
+ chunks.push(this.createChunk(")", "markup.link", linkHref))
404
+ }
405
+ break
406
+ }
407
+
408
+ case "image": {
409
+ const imageHref = { url: token.href }
410
+ if (this._conceal) {
411
+ chunks.push(this.createChunk(token.text || "image", "markup.link.label", imageHref))
412
+ } else {
413
+ chunks.push(this.createChunk("![", "markup.link", imageHref))
414
+ chunks.push(this.createChunk(token.text || "", "markup.link.label", imageHref))
415
+ chunks.push(this.createChunk("](", "markup.link", imageHref))
416
+ chunks.push(this.createChunk(token.href, "markup.link.url", imageHref))
417
+ chunks.push(this.createChunk(")", "markup.link", imageHref))
418
+ }
419
+ break
420
+ }
421
+
422
+ case "br":
423
+ chunks.push(this.createDefaultChunk("\n"))
424
+ break
425
+
426
+ default:
427
+ if ("tokens" in token && Array.isArray(token.tokens)) {
428
+ this.renderInlineContent(token.tokens, chunks)
429
+ } else if ("text" in token && typeof token.text === "string") {
430
+ chunks.push(this.createDefaultChunk(token.text))
431
+ }
432
+ break
433
+ }
434
+ }
435
+
436
+ private renderInlineTokenWithStyle(
437
+ token: MarkedToken,
438
+ chunks: TextChunk[],
439
+ styleGroup: string,
440
+ link?: { url: string },
441
+ ): void {
442
+ switch (token.type) {
443
+ case "text":
444
+ chunks.push(this.createChunk(token.text, styleGroup, link))
445
+ break
446
+
447
+ case "escape":
448
+ chunks.push(this.createChunk(token.text, styleGroup, link))
449
+ break
450
+
451
+ case "codespan":
452
+ if (this._conceal) {
453
+ chunks.push(this.createChunk(token.text, "markup.raw", link))
454
+ } else {
455
+ chunks.push(this.createChunk("`", "markup.raw", link))
456
+ chunks.push(this.createChunk(token.text, "markup.raw", link))
457
+ chunks.push(this.createChunk("`", "markup.raw", link))
458
+ }
459
+ break
460
+
461
+ default:
462
+ this.renderInlineToken(token, chunks)
463
+ break
464
+ }
465
+ }
466
+
467
+ private buildInlineConcealChunks(chunks: TextChunk[], context: { content: string; highlights: any[] }): TextChunk[] {
468
+ // Re-parse with marked to get inline tokens, then render with concealment.
469
+ // Falls back to original chunks if inline rendering produces nothing.
470
+ const result: TextChunk[] = []
471
+ try {
472
+ const parsed = parseMarkdownIncremental(context.content, null, 0)
473
+ const tokens = parsed.tokens
474
+ for (const token of tokens) {
475
+ this.renderBlockTokenInline(token, result)
476
+ }
477
+ } catch {
478
+ return chunks
479
+ }
480
+
481
+ // Remove trailing newline added by renderBlockTokenInline
482
+ if (result.length > 0 && result[result.length - 1].text === "\n") {
483
+ result.pop()
484
+ }
485
+
486
+ if (result.length === 0) return chunks
487
+
488
+ // Apply linkification on top of inline-rendered chunks
489
+ const linked = this._linkifyMarkdownChunks(result, context as any)
490
+ if (Array.isArray(linked)) return linked
491
+ return result
492
+ }
493
+
494
+ private renderBlockTokenInline(token: MarkedToken, chunks: TextChunk[]): void {
495
+ switch (token.type) {
496
+ case "heading": {
497
+ const headingToken = token as Tokens.Heading
498
+ const style = `markup.heading.${headingToken.depth}`
499
+ for (const child of headingToken.tokens) {
500
+ this.renderInlineTokenWithStyle(child as MarkedToken, chunks, style)
501
+ }
502
+ chunks.push(this.createDefaultChunk("\n"))
503
+ break
504
+ }
505
+ case "paragraph": {
506
+ const paragraphToken = token as Tokens.Paragraph
507
+ this.renderInlineContent(paragraphToken.tokens, chunks)
508
+ chunks.push(this.createDefaultChunk("\n"))
509
+ break
510
+ }
511
+ case "list": {
512
+ this.renderListInline(token as Tokens.List, chunks, 0)
513
+ break
514
+ }
515
+ case "blockquote": {
516
+ const quoteToken = token as Tokens.Blockquote
517
+ for (const child of quoteToken.tokens) {
518
+ const markedChild = child as MarkedToken
519
+ if (markedChild.type === "paragraph" && "tokens" in markedChild) {
520
+ chunks.push(this.createChunk("▎ ", "markup.quote"))
521
+ for (const inline of (markedChild as Tokens.Paragraph).tokens) {
522
+ this.renderInlineTokenWithStyle(inline as MarkedToken, chunks, "markup.quote")
523
+ }
524
+ chunks.push(this.createDefaultChunk("\n"))
525
+ } else if (markedChild.type === "table") {
526
+ // Reconstruct pipe table from structured token data so every row
527
+ // gets the blockquote ▎ prefix (tbl.raw may only have > on first line)
528
+ const tbl = markedChild as Tokens.Table
529
+ const headerCells = tbl.header.map((h) => h.text || " ").join(" | ")
530
+ const sepCells = tbl.header.map(() => "---").join(" | ")
531
+ const tableLines = [
532
+ `| ${headerCells} |`,
533
+ `| ${sepCells} |`,
534
+ ...tbl.rows.map((row) => `| ${row.map((c) => c.text || " ").join(" | ")} |`),
535
+ ]
536
+ for (const line of tableLines) {
537
+ chunks.push(this.createChunk("▎ ", "markup.quote"))
538
+ chunks.push(this.createDefaultChunk(line + "\n"))
539
+ }
540
+ } else {
541
+ chunks.push(this.createChunk("▎ ", "markup.quote"))
542
+ this.renderBlockTokenInline(markedChild, chunks)
543
+ }
544
+ }
545
+ break
546
+ }
547
+ case "code": {
548
+ // Fenced code blocks that ended up in the inline path (rare — normally
549
+ // handled separately by createCodeRenderable). Render as raw block.
550
+ const codeToken = token as Tokens.Code
551
+ const rawStyle = this.getStyle("markup.raw.block")
552
+ const codeText = codeToken.text
553
+ for (const line of codeText.split("\n")) {
554
+ chunks.push({
555
+ __isChunk: true,
556
+ text: " " + line,
557
+ fg: rawStyle?.fg,
558
+ bg: rawStyle?.bg,
559
+ attributes: rawStyle
560
+ ? createTextAttributes({
561
+ bold: rawStyle.bold,
562
+ italic: rawStyle.italic,
563
+ underline: rawStyle.underline,
564
+ dim: rawStyle.dim,
565
+ })
566
+ : 0,
567
+ })
568
+ chunks.push(this.createDefaultChunk("\n"))
569
+ }
570
+ break
571
+ }
572
+ case "html": {
573
+ // Inline/block HTML — render as raw text
574
+ chunks.push(this.createDefaultChunk(token.text))
575
+ chunks.push(this.createDefaultChunk("\n"))
576
+ break
577
+ }
578
+ case "hr": {
579
+ const hrStyle = this.getStyle("punctuation.special")
580
+ chunks.push({
581
+ __isChunk: true,
582
+ text: "─".repeat(40),
583
+ fg: hrStyle?.fg,
584
+ bg: hrStyle?.bg,
585
+ attributes: hrStyle
586
+ ? createTextAttributes({
587
+ bold: hrStyle.bold,
588
+ italic: hrStyle.italic,
589
+ underline: hrStyle.underline,
590
+ dim: hrStyle.dim,
591
+ })
592
+ : 0,
593
+ })
594
+ chunks.push(this.createDefaultChunk("\n"))
595
+ break
596
+ }
597
+ case "table": {
598
+ // Tables that ended up in the inline path (rare — normally handled
599
+ // separately by createTextTableRenderable). Render as raw text.
600
+ chunks.push(this.createDefaultChunk((token as any).raw ?? ""))
601
+ chunks.push(this.createDefaultChunk("\n"))
602
+ break
603
+ }
604
+ case "def":
605
+ // Link reference definitions — not rendered visually
606
+ break
607
+ case "space":
608
+ chunks.push(this.createDefaultChunk("\n"))
609
+ break
610
+ default:
611
+ if ("tokens" in token && Array.isArray(token.tokens)) {
612
+ this.renderInlineContent(token.tokens, chunks)
613
+ } else {
614
+ chunks.push(this.createDefaultChunk((token as any).raw ?? ""))
615
+ }
616
+ chunks.push(this.createDefaultChunk("\n"))
617
+ break
618
+ }
619
+ }
620
+
621
+ private renderListInline(list: Tokens.List, chunks: TextChunk[], depth: number): void {
622
+ const listStyle = this.getStyle("markup.list")
623
+ const checkedStyle = this.getStyle("markup.list.checked")
624
+ const uncheckedStyle = this.getStyle("markup.list.unchecked")
625
+ for (let i = 0; i < list.items.length; i++) {
626
+ const item = list.items[i]
627
+ const indent = " ".repeat(depth)
628
+ const start = typeof list.start === "number" ? list.start : 1
629
+
630
+ // Task list items: show checkbox
631
+ if (item.task) {
632
+ const checkmark = item.checked ? "☑ " : "☐ "
633
+ const checkStyle = item.checked ? checkedStyle : uncheckedStyle
634
+ chunks.push({
635
+ __isChunk: true,
636
+ text: indent + checkmark,
637
+ fg: checkStyle?.fg ?? listStyle?.fg,
638
+ bg: checkStyle?.bg ?? listStyle?.bg,
639
+ attributes: checkStyle
640
+ ? createTextAttributes({
641
+ bold: checkStyle.bold,
642
+ italic: checkStyle.italic,
643
+ underline: checkStyle.underline,
644
+ dim: checkStyle.dim,
645
+ })
646
+ : 0,
647
+ })
648
+ } else {
649
+ const bullet = list.ordered ? `${start + i}. ` : "• "
650
+ chunks.push({
651
+ __isChunk: true,
652
+ text: indent + bullet,
653
+ fg: listStyle?.fg,
654
+ bg: listStyle?.bg,
655
+ attributes: listStyle
656
+ ? createTextAttributes({
657
+ bold: listStyle.bold,
658
+ italic: listStyle.italic,
659
+ underline: listStyle.underline,
660
+ dim: listStyle.dim,
661
+ })
662
+ : 0,
663
+ })
664
+ }
665
+
666
+ for (const subToken of item.tokens) {
667
+ const markedSub = subToken as MarkedToken
668
+ if (markedSub.type === "text" && "tokens" in markedSub && Array.isArray(markedSub.tokens)) {
669
+ this.renderInlineContent(markedSub.tokens, chunks)
670
+ } else if (markedSub.type === "list") {
671
+ chunks.push(this.createDefaultChunk("\n"))
672
+ this.renderListInline(markedSub as Tokens.List, chunks, depth + 1)
673
+ continue
674
+ } else if (markedSub.type === "paragraph" && "tokens" in markedSub) {
675
+ this.renderInlineContent((markedSub as Tokens.Paragraph).tokens, chunks)
676
+ } else if (markedSub.type === "code") {
677
+ // Render fenced code blocks inside list items on their own indented lines
678
+ if (chunks.length > 0 && !chunks[chunks.length - 1].text.endsWith("\n")) {
679
+ chunks[chunks.length - 1] = { ...chunks[chunks.length - 1], text: chunks[chunks.length - 1].text + "\n" }
680
+ } else {
681
+ chunks.push(this.createDefaultChunk("\n"))
682
+ }
683
+ const codeLines = (markedSub as Tokens.Code).text.split("\n")
684
+ const codeStyle = this.getStyle("markup.raw") || this.getStyle("default")
685
+ for (const codeLine of codeLines) {
686
+ chunks.push(this.createDefaultChunk(indent + " "))
687
+ chunks.push({
688
+ __isChunk: true,
689
+ text: codeLine,
690
+ fg: codeStyle?.fg,
691
+ bg: codeStyle?.bg,
692
+ attributes: 0,
693
+ })
694
+ chunks.push(this.createDefaultChunk("\n"))
695
+ }
696
+ continue
697
+ } else {
698
+ this.renderInlineToken(markedSub, chunks)
699
+ }
700
+ }
701
+ chunks.push(this.createDefaultChunk("\n"))
702
+ }
703
+ }
704
+
705
+ private createMarkdownCodeRenderable(content: string, id: string, marginBottom: number = 0): CodeRenderable {
706
+ return new CodeRenderable(this.ctx, {
707
+ id,
708
+ content,
709
+ filetype: "markdown",
710
+ syntaxStyle: this._syntaxStyle,
711
+ fg: this._fg,
712
+ bg: this._bg,
713
+ conceal: this._conceal,
714
+ drawUnstyledText: true,
715
+ streaming: true,
716
+ onChunks: this._conceal ? this._inlineConcealChunks : this._linkifyMarkdownChunks,
717
+ treeSitterClient: this._treeSitterClient,
718
+ width: "100%",
719
+ marginBottom,
720
+ })
721
+ }
722
+
723
+ private createCodeRenderable(token: Tokens.Code, id: string, marginBottom: number = 0): Renderable {
724
+ return new CodeRenderable(this.ctx, {
725
+ id,
726
+ content: token.text,
727
+ filetype: infoStringToFiletype(token.lang ?? ""),
728
+ syntaxStyle: this._syntaxStyle,
729
+ fg: this._fg,
730
+ bg: this._bg,
731
+ conceal: this._concealCode,
732
+ drawUnstyledText: !(this._streaming && this._concealCode),
733
+ streaming: this._streaming,
734
+ treeSitterClient: this._treeSitterClient,
735
+ width: "100%",
736
+ marginBottom,
737
+ })
738
+ }
739
+
740
+ private applyMarkdownCodeRenderable(renderable: CodeRenderable, content: string, marginBottom: number): void {
741
+ renderable.content = content
742
+ renderable.filetype = "markdown"
743
+ renderable.syntaxStyle = this._syntaxStyle
744
+ renderable.fg = this._fg
745
+ renderable.bg = this._bg
746
+ renderable.conceal = this._conceal
747
+ renderable.drawUnstyledText = true
748
+ renderable.streaming = true
749
+ renderable.onChunks = this._conceal ? this._inlineConcealChunks : this._linkifyMarkdownChunks
750
+ renderable.marginBottom = marginBottom
751
+ }
752
+
753
+ private applyCodeBlockRenderable(renderable: CodeRenderable, token: Tokens.Code, marginBottom: number): void {
754
+ renderable.content = token.text
755
+ renderable.filetype = infoStringToFiletype(token.lang ?? "")
756
+ renderable.syntaxStyle = this._syntaxStyle
757
+ renderable.fg = this._fg
758
+ renderable.bg = this._bg
759
+ renderable.conceal = this._concealCode
760
+ renderable.drawUnstyledText = !(this._streaming && this._concealCode)
761
+ renderable.streaming = this._streaming
762
+ renderable.marginBottom = marginBottom
763
+ }
764
+
765
+ private shouldRenderSeparately(token: MarkedToken): boolean {
766
+ return token.type === "code" || token.type === "table" || token.type === "blockquote"
767
+ }
768
+
769
+ private getInterBlockMargin(token: MarkedToken, hasNextToken: boolean): number {
770
+ if (!hasNextToken) return 0
771
+ return this.shouldRenderSeparately(token) ? 1 : 0
772
+ }
773
+
774
+ private createMarkdownBlockToken(raw: string): MarkedToken {
775
+ return {
776
+ type: "paragraph",
777
+ raw,
778
+ text: raw,
779
+ tokens: [],
780
+ } as MarkedToken
781
+ }
782
+
783
+ private normalizeMarkdownBlockRaw(raw: string): string {
784
+ return raw.replace(TRAILING_MARKDOWN_BLOCK_BREAKS_RE, "")
785
+ }
786
+
787
+ private buildRenderableTokens(tokens: MarkedToken[]): MarkedToken[] {
788
+ if (this._renderNode) {
789
+ return tokens.filter((token) => token.type !== "space")
790
+ }
791
+
792
+ const renderTokens: MarkedToken[] = []
793
+ let markdownRaw = ""
794
+
795
+ const flushMarkdownRaw = (): void => {
796
+ if (markdownRaw.length === 0) return
797
+ const normalizedRaw = this.normalizeMarkdownBlockRaw(markdownRaw)
798
+ if (normalizedRaw.length > 0) {
799
+ renderTokens.push(this.createMarkdownBlockToken(normalizedRaw))
800
+ }
801
+ markdownRaw = ""
802
+ }
803
+
804
+ for (let i = 0; i < tokens.length; i += 1) {
805
+ const token = tokens[i]
806
+
807
+ if (token.type === "space") {
808
+ if (markdownRaw.length === 0) {
809
+ continue
810
+ }
811
+
812
+ let nextIndex = i + 1
813
+ while (nextIndex < tokens.length && tokens[nextIndex].type === "space") {
814
+ nextIndex += 1
815
+ }
816
+
817
+ const nextToken = tokens[nextIndex]
818
+ if (nextToken && !this.shouldRenderSeparately(nextToken)) {
819
+ markdownRaw += token.raw
820
+ }
821
+ continue
822
+ }
823
+
824
+ if (this.shouldRenderSeparately(token)) {
825
+ flushMarkdownRaw()
826
+ renderTokens.push(token)
827
+ continue
828
+ }
829
+
830
+ markdownRaw += token.raw
831
+ }
832
+
833
+ flushMarkdownRaw()
834
+
835
+ return renderTokens
836
+ }
837
+
838
+ private getTableRowsToRender(table: Tokens.Table): Tokens.TableCell[][] {
839
+ return table.rows
840
+ }
841
+
842
+ private hashString(value: string, seed: number): number {
843
+ let hash = seed >>> 0
844
+ for (let i = 0; i < value.length; i += 1) {
845
+ hash ^= value.charCodeAt(i)
846
+ hash = Math.imul(hash, 16777619)
847
+ }
848
+ return hash >>> 0
849
+ }
850
+
851
+ private hashTableToken(token: MarkedToken, seed: number, depth: number = 0): number {
852
+ let hash = this.hashString(token.type, seed)
853
+
854
+ if ("raw" in token && typeof token.raw === "string") {
855
+ return this.hashString(token.raw, hash)
856
+ }
857
+
858
+ if ("text" in token && typeof token.text === "string") {
859
+ hash = this.hashString(token.text, hash)
860
+ }
861
+
862
+ if (depth < 2 && "tokens" in token && Array.isArray(token.tokens)) {
863
+ for (const child of token.tokens) {
864
+ hash = this.hashTableToken(child as MarkedToken, hash, depth + 1)
865
+ }
866
+ }
867
+
868
+ return hash >>> 0
869
+ }
870
+
871
+ private getTableCellKey(cell: Tokens.TableCell | undefined, isHeader: boolean): number {
872
+ const seed = isHeader ? 2902232141 : 1371922141
873
+ if (!cell) {
874
+ return seed
875
+ }
876
+
877
+ if (typeof cell.text === "string") {
878
+ return this.hashString(cell.text, seed)
879
+ }
880
+
881
+ if (Array.isArray(cell.tokens) && cell.tokens.length > 0) {
882
+ let hash = seed ^ cell.tokens.length
883
+ for (const token of cell.tokens) {
884
+ hash = this.hashTableToken(token as MarkedToken, hash)
885
+ }
886
+ return hash >>> 0
887
+ }
888
+
889
+ return (seed ^ 2654435769) >>> 0
890
+ }
891
+
892
+ private createTableDataCellChunks(cell: Tokens.TableCell | undefined): TextChunk[] {
893
+ const chunks: TextChunk[] = []
894
+ if (cell) {
895
+ this.renderInlineContent(cell.tokens, chunks)
896
+ }
897
+ return chunks.length > 0 ? chunks : [this.createDefaultChunk(" ")]
898
+ }
899
+
900
+ private createTableHeaderCellChunks(cell: Tokens.TableCell): TextChunk[] {
901
+ const chunks: TextChunk[] = []
902
+ this.renderInlineContent(cell.tokens, chunks)
903
+
904
+ const baseChunks = chunks.length > 0 ? chunks : [this.createDefaultChunk(" ")]
905
+ const headingStyle = this.getStyle("markup.heading") || this.getStyle("default")
906
+ if (!headingStyle) {
907
+ return baseChunks
908
+ }
909
+
910
+ const headingAttributes = createTextAttributes({
911
+ bold: headingStyle.bold,
912
+ italic: headingStyle.italic,
913
+ underline: headingStyle.underline,
914
+ dim: headingStyle.dim,
915
+ })
916
+
917
+ return baseChunks.map((chunk) => ({
918
+ ...chunk,
919
+ fg: headingStyle.fg ?? chunk.fg,
920
+ bg: headingStyle.bg ?? chunk.bg,
921
+ attributes: headingAttributes,
922
+ }))
923
+ }
924
+
925
+ private buildTableContentCache(
926
+ table: Tokens.Table,
927
+ previous?: TableContentCache,
928
+ forceRegenerate: boolean = false,
929
+ ): { cache: TableContentCache | null; changed: boolean } {
930
+ const colCount = table.header.length
931
+ const rowsToRender = this.getTableRowsToRender(table)
932
+ if (colCount === 0 || rowsToRender.length === 0) {
933
+ return { cache: null, changed: previous !== undefined }
934
+ }
935
+
936
+ const content: TextTableContent = []
937
+ const cellKeys: Uint32Array[] = []
938
+ const totalRows = rowsToRender.length + 1
939
+
940
+ let changed = forceRegenerate || !previous
941
+
942
+ for (let rowIndex = 0; rowIndex < totalRows; rowIndex += 1) {
943
+ const rowContent: TextTableCellContent[] = []
944
+ const rowKeys = new Uint32Array(colCount)
945
+
946
+ for (let colIndex = 0; colIndex < colCount; colIndex += 1) {
947
+ const isHeader = rowIndex === 0
948
+ const cell = isHeader ? table.header[colIndex] : rowsToRender[rowIndex - 1]?.[colIndex]
949
+ const cellKey = this.getTableCellKey(cell, isHeader)
950
+ rowKeys[colIndex] = cellKey
951
+
952
+ const previousCellKey = previous?.cellKeys[rowIndex]?.[colIndex]
953
+ const previousCellContent = previous?.content[rowIndex]?.[colIndex]
954
+
955
+ if (!forceRegenerate && previousCellKey === cellKey && Array.isArray(previousCellContent)) {
956
+ rowContent.push(previousCellContent)
957
+ continue
958
+ }
959
+
960
+ changed = true
961
+ rowContent.push(
962
+ isHeader ? this.createTableHeaderCellChunks(table.header[colIndex]) : this.createTableDataCellChunks(cell),
963
+ )
964
+ }
965
+
966
+ content.push(rowContent)
967
+ cellKeys.push(rowKeys)
968
+ }
969
+
970
+ if (previous && !changed) {
971
+ if (previous.content.length !== content.length) {
972
+ changed = true
973
+ } else {
974
+ for (let rowIndex = 0; rowIndex < content.length; rowIndex += 1) {
975
+ if ((previous.content[rowIndex]?.length ?? 0) !== content[rowIndex].length) {
976
+ changed = true
977
+ break
978
+ }
979
+ }
980
+ }
981
+ }
982
+
983
+ return {
984
+ cache: {
985
+ content,
986
+ cellKeys,
987
+ },
988
+ changed,
989
+ }
990
+ }
991
+
992
+ private resolveTableRenderableOptions(): ResolvedTableRenderableOptions {
993
+ const borders = this._tableOptions?.borders ?? true
994
+
995
+ return {
996
+ columnWidthMode: this._tableOptions?.widthMode ?? "full",
997
+ columnFitter: this._tableOptions?.columnFitter ?? "proportional",
998
+ wrapMode: this._tableOptions?.wrapMode ?? "word",
999
+ cellPadding: this._tableOptions?.cellPadding ?? 0,
1000
+ border: borders,
1001
+ outerBorder: this._tableOptions?.outerBorder ?? borders,
1002
+ showBorders: borders,
1003
+ borderStyle: this._tableOptions?.borderStyle ?? "single",
1004
+ borderColor: this._tableOptions?.borderColor ?? this.getStyle("conceal")?.fg ?? "#888888",
1005
+ selectable: this._tableOptions?.selectable ?? true,
1006
+ }
1007
+ }
1008
+
1009
+ private applyTableRenderableOptions(
1010
+ tableRenderable: TextTableRenderable,
1011
+ options: ResolvedTableRenderableOptions,
1012
+ ): void {
1013
+ tableRenderable.columnWidthMode = options.columnWidthMode
1014
+ tableRenderable.columnFitter = options.columnFitter
1015
+ tableRenderable.wrapMode = options.wrapMode
1016
+ tableRenderable.cellPadding = options.cellPadding
1017
+ tableRenderable.border = options.border
1018
+ tableRenderable.outerBorder = options.outerBorder
1019
+ tableRenderable.showBorders = options.showBorders
1020
+ tableRenderable.borderStyle = options.borderStyle
1021
+ tableRenderable.borderColor = options.borderColor
1022
+ tableRenderable.selectable = options.selectable
1023
+ }
1024
+
1025
+ private applyTableOptionsToBlocks(): void {
1026
+ const options = this.resolveTableRenderableOptions()
1027
+ let updated = false
1028
+
1029
+ for (const state of this._blockStates) {
1030
+ if (state.renderable instanceof TextTableRenderable) {
1031
+ this.applyTableRenderableOptions(state.renderable, options)
1032
+ updated = true
1033
+ }
1034
+ }
1035
+
1036
+ if (updated) {
1037
+ this.requestRender()
1038
+ }
1039
+ }
1040
+
1041
+ private createTextTableRenderable(
1042
+ content: TextTableContent,
1043
+ id: string,
1044
+ marginBottom: number = 0,
1045
+ ): TextTableRenderable {
1046
+ const options = this.resolveTableRenderableOptions()
1047
+ return new TextTableRenderable(this.ctx, {
1048
+ id,
1049
+ content,
1050
+ width: "100%",
1051
+ marginBottom,
1052
+ columnWidthMode: options.columnWidthMode,
1053
+ columnFitter: options.columnFitter,
1054
+ wrapMode: options.wrapMode,
1055
+ cellPadding: options.cellPadding,
1056
+ border: options.border,
1057
+ outerBorder: options.outerBorder,
1058
+ showBorders: options.showBorders,
1059
+ borderStyle: options.borderStyle,
1060
+ borderColor: options.borderColor,
1061
+ selectable: options.selectable,
1062
+ })
1063
+ }
1064
+
1065
+ private createTableBlock(
1066
+ table: Tokens.Table,
1067
+ id: string,
1068
+ marginBottom: number = 0,
1069
+ previousCache?: TableContentCache,
1070
+ forceRegenerate: boolean = false,
1071
+ ): { renderable: Renderable; tableContentCache?: TableContentCache } {
1072
+ const { cache } = this.buildTableContentCache(table, previousCache, forceRegenerate)
1073
+
1074
+ if (!cache) {
1075
+ return {
1076
+ renderable: this.createMarkdownCodeRenderable(table.raw, id, marginBottom),
1077
+ }
1078
+ }
1079
+
1080
+ return {
1081
+ renderable: this.createTextTableRenderable(cache.content, id, marginBottom),
1082
+ tableContentCache: cache,
1083
+ }
1084
+ }
1085
+
1086
+ private createDefaultRenderable(token: MarkedToken, index: number, hasNextToken: boolean = false): Renderable | null {
1087
+ const id = `${this.id}-block-${index}`
1088
+ const marginBottom = this.getInterBlockMargin(token, hasNextToken)
1089
+
1090
+ if (token.type === "code") {
1091
+ return this.createCodeRenderable(token, id, marginBottom)
1092
+ }
1093
+
1094
+ if (token.type === "table") {
1095
+ return this.createTableBlock(token, id, marginBottom).renderable
1096
+ }
1097
+
1098
+ if (token.type === "space") {
1099
+ return null
1100
+ }
1101
+
1102
+ if (!token.raw) {
1103
+ return null
1104
+ }
1105
+
1106
+ return this.createMarkdownCodeRenderable(token.raw, id, marginBottom)
1107
+ }
1108
+
1109
+ private updateBlockRenderable(state: BlockState, token: MarkedToken, index: number, hasNextToken: boolean): void {
1110
+ const marginBottom = this.getInterBlockMargin(token, hasNextToken)
1111
+
1112
+ if (token.type === "code") {
1113
+ this.applyCodeBlockRenderable(state.renderable as CodeRenderable, token as Tokens.Code, marginBottom)
1114
+ return
1115
+ }
1116
+
1117
+ if (token.type === "table") {
1118
+ const tableToken = token as Tokens.Table
1119
+ const { cache, changed } = this.buildTableContentCache(tableToken, state.tableContentCache)
1120
+
1121
+ if (!cache) {
1122
+ if (state.renderable instanceof CodeRenderable) {
1123
+ this.applyMarkdownCodeRenderable(state.renderable, tableToken.raw, marginBottom)
1124
+ state.tableContentCache = undefined
1125
+ return
1126
+ }
1127
+
1128
+ state.renderable.destroyRecursively()
1129
+ const fallbackRenderable = this.createMarkdownCodeRenderable(
1130
+ tableToken.raw,
1131
+ `${this.id}-block-${index}`,
1132
+ marginBottom,
1133
+ )
1134
+ this.add(fallbackRenderable)
1135
+ state.renderable = fallbackRenderable
1136
+ state.tableContentCache = undefined
1137
+ return
1138
+ }
1139
+
1140
+ if (state.renderable instanceof TextTableRenderable) {
1141
+ if (changed) {
1142
+ state.renderable.content = cache.content
1143
+ }
1144
+ this.applyTableRenderableOptions(state.renderable, this.resolveTableRenderableOptions())
1145
+ state.renderable.marginBottom = marginBottom
1146
+ state.tableContentCache = cache
1147
+ return
1148
+ }
1149
+
1150
+ state.renderable.destroyRecursively()
1151
+ const tableRenderable = this.createTextTableRenderable(cache.content, `${this.id}-block-${index}`, marginBottom)
1152
+ this.add(tableRenderable)
1153
+ state.renderable = tableRenderable
1154
+ state.tableContentCache = cache
1155
+ return
1156
+ }
1157
+
1158
+ if (state.renderable instanceof CodeRenderable) {
1159
+ this.applyMarkdownCodeRenderable(state.renderable, token.raw, marginBottom)
1160
+ return
1161
+ }
1162
+
1163
+ state.renderable.destroyRecursively()
1164
+ const markdownRenderable = this.createMarkdownCodeRenderable(token.raw, `${this.id}-block-${index}`, marginBottom)
1165
+ this.add(markdownRenderable)
1166
+ state.renderable = markdownRenderable
1167
+ }
1168
+
1169
+ private updateBlocks(forceTableRefresh: boolean = false): void {
1170
+ if (this.isDestroyed) return
1171
+ if (!this._content) {
1172
+ this.clearBlockStates()
1173
+ this._parseState = null
1174
+ return
1175
+ }
1176
+
1177
+ const trailingUnstable = this._streaming ? 2 : 0
1178
+ this._parseState = parseMarkdownIncremental(this._content, this._parseState, trailingUnstable)
1179
+
1180
+ const tokens = this._parseState.tokens
1181
+
1182
+ // Parse failure fallback
1183
+ if (tokens.length === 0 && this._content.length > 0) {
1184
+ this.clearBlockStates()
1185
+ const fallback = this.createMarkdownCodeRenderable(this._content, `${this.id}-fallback`)
1186
+ this.add(fallback)
1187
+ this._blockStates = [
1188
+ {
1189
+ token: { type: "text", raw: this._content, text: this._content } as MarkedToken,
1190
+ tokenRaw: this._content,
1191
+ renderable: fallback,
1192
+ },
1193
+ ]
1194
+ return
1195
+ }
1196
+
1197
+ const blockTokens = this.buildRenderableTokens(tokens)
1198
+ const lastBlockIndex = blockTokens.length - 1
1199
+
1200
+ let blockIndex = 0
1201
+ for (let i = 0; i < blockTokens.length; i++) {
1202
+ const token = blockTokens[i]
1203
+ const hasNextToken = i < lastBlockIndex
1204
+ const existing = this._blockStates[blockIndex]
1205
+
1206
+ const shouldForceRefresh = forceTableRefresh
1207
+
1208
+ // Same token object reference means unchanged
1209
+ if (existing && existing.token === token) {
1210
+ if (shouldForceRefresh) {
1211
+ this.updateBlockRenderable(existing, token, blockIndex, hasNextToken)
1212
+ existing.tokenRaw = token.raw
1213
+ }
1214
+ blockIndex++
1215
+ continue
1216
+ }
1217
+
1218
+ // Same content, update reference
1219
+ if (existing && existing.tokenRaw === token.raw && existing.token.type === token.type) {
1220
+ existing.token = token
1221
+ if (shouldForceRefresh) {
1222
+ this.updateBlockRenderable(existing, token, blockIndex, hasNextToken)
1223
+ existing.tokenRaw = token.raw
1224
+ }
1225
+ blockIndex++
1226
+ continue
1227
+ }
1228
+
1229
+ // Same type, different content - update in place
1230
+ if (existing && existing.token.type === token.type) {
1231
+ this.updateBlockRenderable(existing, token, blockIndex, hasNextToken)
1232
+ existing.token = token
1233
+ existing.tokenRaw = token.raw
1234
+ blockIndex++
1235
+ continue
1236
+ }
1237
+
1238
+ // Different type or new block
1239
+ if (existing) {
1240
+ existing.renderable.destroyRecursively()
1241
+ }
1242
+
1243
+ let renderable: Renderable | undefined
1244
+ let tableContentCache: TableContentCache | undefined
1245
+
1246
+ if (this._renderNode) {
1247
+ const context: RenderNodeContext = {
1248
+ syntaxStyle: this._syntaxStyle,
1249
+ conceal: this._conceal,
1250
+ concealCode: this._concealCode,
1251
+ treeSitterClient: this._treeSitterClient,
1252
+ defaultRender: () => this.createDefaultRenderable(token, blockIndex, hasNextToken),
1253
+ }
1254
+ const custom = this._renderNode(token, context)
1255
+ if (custom) {
1256
+ renderable = custom
1257
+ }
1258
+ }
1259
+
1260
+ if (!renderable) {
1261
+ if (token.type === "table") {
1262
+ const tableBlock = this.createTableBlock(
1263
+ token,
1264
+ `${this.id}-block-${blockIndex}`,
1265
+ this.getInterBlockMargin(token, hasNextToken),
1266
+ )
1267
+ renderable = tableBlock.renderable
1268
+ tableContentCache = tableBlock.tableContentCache
1269
+ } else {
1270
+ renderable = this.createDefaultRenderable(token, blockIndex, hasNextToken) ?? undefined
1271
+ }
1272
+ }
1273
+
1274
+ if (token.type === "table" && !tableContentCache && renderable instanceof TextTableRenderable) {
1275
+ const { cache } = this.buildTableContentCache(token as Tokens.Table)
1276
+ tableContentCache = cache ?? undefined
1277
+ }
1278
+
1279
+ if (renderable) {
1280
+ this.add(renderable)
1281
+ this._blockStates[blockIndex] = {
1282
+ token,
1283
+ tokenRaw: token.raw,
1284
+ renderable,
1285
+ tableContentCache,
1286
+ }
1287
+ }
1288
+ blockIndex++
1289
+ }
1290
+
1291
+ while (this._blockStates.length > blockIndex) {
1292
+ const removed = this._blockStates.pop()!
1293
+ removed.renderable.destroyRecursively()
1294
+ }
1295
+ }
1296
+
1297
+ private clearBlockStates(): void {
1298
+ for (const state of this._blockStates) {
1299
+ state.renderable.destroyRecursively()
1300
+ }
1301
+ this._blockStates = []
1302
+ }
1303
+
1304
+ /**
1305
+ * Re-render existing blocks without rebuilding the parse state or block structure.
1306
+ * Used when only style/conceal changes - much faster than full rebuild.
1307
+ */
1308
+ private rerenderBlocks(): void {
1309
+ for (let i = 0; i < this._blockStates.length; i++) {
1310
+ const state = this._blockStates[i]
1311
+ const hasNextToken = i < this._blockStates.length - 1
1312
+ const marginBottom = this.getInterBlockMargin(state.token, hasNextToken)
1313
+
1314
+ if (state.token.type === "code") {
1315
+ this.applyCodeBlockRenderable(state.renderable as CodeRenderable, state.token as Tokens.Code, marginBottom)
1316
+ continue
1317
+ }
1318
+
1319
+ if (state.token.type === "table") {
1320
+ const tableToken = state.token as Tokens.Table
1321
+ const { cache } = this.buildTableContentCache(tableToken, state.tableContentCache, true)
1322
+
1323
+ if (!cache) {
1324
+ if (state.renderable instanceof CodeRenderable) {
1325
+ this.applyMarkdownCodeRenderable(state.renderable, tableToken.raw, marginBottom)
1326
+ } else {
1327
+ state.renderable.destroyRecursively()
1328
+ const fallbackRenderable = this.createMarkdownCodeRenderable(
1329
+ tableToken.raw,
1330
+ `${this.id}-block-${i}`,
1331
+ marginBottom,
1332
+ )
1333
+ this.add(fallbackRenderable)
1334
+ state.renderable = fallbackRenderable
1335
+ }
1336
+ state.tableContentCache = undefined
1337
+ continue
1338
+ }
1339
+
1340
+ if (state.renderable instanceof TextTableRenderable) {
1341
+ state.renderable.content = cache.content
1342
+ this.applyTableRenderableOptions(state.renderable, this.resolveTableRenderableOptions())
1343
+ state.renderable.marginBottom = marginBottom
1344
+ state.tableContentCache = cache
1345
+ continue
1346
+ }
1347
+
1348
+ state.renderable.destroyRecursively()
1349
+ const tableRenderable = this.createTextTableRenderable(cache.content, `${this.id}-block-${i}`, marginBottom)
1350
+ this.add(tableRenderable)
1351
+ state.renderable = tableRenderable
1352
+ state.tableContentCache = cache
1353
+ continue
1354
+ }
1355
+
1356
+ if (state.renderable instanceof CodeRenderable) {
1357
+ this.applyMarkdownCodeRenderable(state.renderable, state.token.raw, marginBottom)
1358
+ continue
1359
+ }
1360
+
1361
+ state.renderable.destroyRecursively()
1362
+ const markdownRenderable = this.createMarkdownCodeRenderable(
1363
+ state.token.raw,
1364
+ `${this.id}-block-${i}`,
1365
+ marginBottom,
1366
+ )
1367
+ this.add(markdownRenderable)
1368
+ state.renderable = markdownRenderable
1369
+ }
1370
+ }
1371
+
1372
+ public clearCache(): void {
1373
+ this._parseState = null
1374
+ this.clearBlockStates()
1375
+ this.updateBlocks()
1376
+ this.requestRender()
1377
+ }
1378
+
1379
+ public refreshStyles(): void {
1380
+ this._styleDirty = false
1381
+ this.rerenderBlocks()
1382
+ this.requestRender()
1383
+ }
1384
+
1385
+ protected override renderSelf(buffer: OptimizedBuffer, deltaTime: number): void {
1386
+ // Check if style/conceal changed - re-render blocks before rendering
1387
+ if (this._styleDirty) {
1388
+ this._styleDirty = false
1389
+ this.rerenderBlocks()
1390
+ }
1391
+ super.renderSelf(buffer, deltaTime)
1392
+ }
1393
+ }