@fairyhunter13/opentui-core 0.1.113 β†’ 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 +62 -53
  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-9vwc3fg6.js +0 -12260
  472. package/index-9vwc3fg6.js.map +0 -42
  473. package/index-dcj62y8t.js +0 -20614
  474. package/index-dcj62y8t.js.map +0 -67
  475. package/index-f7n39gpy.js +0 -411
  476. package/index-f7n39gpy.js.map +0 -10
  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,1017 @@
1
+ const std = @import("std");
2
+ const renderer = @import("../renderer.zig");
3
+ const text_buffer = @import("../text-buffer.zig");
4
+ const text_buffer_view = @import("../text-buffer-view.zig");
5
+ const buffer = @import("../buffer.zig");
6
+ const gp = @import("../grapheme.zig");
7
+ const ss = @import("../syntax-style.zig");
8
+ const link = @import("../link.zig");
9
+ const ansi = @import("../ansi.zig");
10
+
11
+ const CliRenderer = renderer.CliRenderer;
12
+ const TextBuffer = text_buffer.TextBuffer;
13
+ const TextBufferView = text_buffer_view.TextBufferView;
14
+ const OptimizedBuffer = buffer.OptimizedBuffer;
15
+ const RGBA = text_buffer.RGBA;
16
+
17
+ fn createWithOptionsOnce(allocator: std.mem.Allocator, width: u32, height: u32) !void {
18
+ const pool = gp.initGlobalPool(allocator);
19
+ defer gp.deinitGlobalPool();
20
+ defer link.deinitGlobalLinkPool();
21
+
22
+ var cli_renderer = try CliRenderer.createWithOptions(allocator, width, height, pool, true, false);
23
+ cli_renderer.destroy();
24
+ }
25
+
26
+ test "renderer - createWithOptions late allocation failure cleans up" {
27
+ const allocation_count = blk: {
28
+ var counting_allocator = std.testing.FailingAllocator.init(std.testing.allocator, .{});
29
+ try createWithOptionsOnce(counting_allocator.allocator(), 80, 24);
30
+ break :blk counting_allocator.alloc_index;
31
+ };
32
+
33
+ try std.testing.expect(allocation_count > 0);
34
+
35
+ var failing_allocator = std.testing.FailingAllocator.init(std.testing.allocator, .{
36
+ .fail_index = allocation_count - 1,
37
+ });
38
+
39
+ try std.testing.expectError(error.OutOfMemory, createWithOptionsOnce(failing_allocator.allocator(), 80, 24));
40
+ try std.testing.expect(failing_allocator.has_induced_failure);
41
+ try std.testing.expectEqual(failing_allocator.allocated_bytes, failing_allocator.freed_bytes);
42
+ }
43
+
44
+ test "renderer - create and destroy" {
45
+ const pool = gp.initGlobalPool(std.testing.allocator);
46
+ defer gp.deinitGlobalPool();
47
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
48
+ defer local_link_pool.deinit();
49
+
50
+ var cli_renderer = try CliRenderer.create(
51
+ std.testing.allocator,
52
+ 80,
53
+ 24,
54
+ pool,
55
+ true,
56
+ );
57
+ defer cli_renderer.destroy();
58
+
59
+ try std.testing.expectEqual(@as(u32, 80), cli_renderer.width);
60
+ try std.testing.expectEqual(@as(u32, 24), cli_renderer.height);
61
+ try std.testing.expect(cli_renderer.testing == true);
62
+ }
63
+
64
+ test "renderer - simple text rendering to currentRenderBuffer" {
65
+ const pool = gp.initGlobalPool(std.testing.allocator);
66
+ defer gp.deinitGlobalPool();
67
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
68
+ defer local_link_pool.deinit();
69
+
70
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
71
+ defer tb.deinit();
72
+
73
+ try tb.setText("Hello World");
74
+
75
+ var view = try TextBufferView.init(std.testing.allocator, tb);
76
+ defer view.deinit();
77
+
78
+ var cli_renderer = try CliRenderer.create(
79
+ std.testing.allocator,
80
+ 80,
81
+ 24,
82
+ pool,
83
+ true,
84
+ );
85
+ defer cli_renderer.destroy();
86
+
87
+ const next_buffer = cli_renderer.getNextBuffer();
88
+ try next_buffer.drawTextBuffer(view, 0, 0);
89
+
90
+ cli_renderer.render(false);
91
+
92
+ const current_buffer = cli_renderer.getCurrentBuffer();
93
+
94
+ const cell_h = current_buffer.get(0, 0);
95
+ try std.testing.expect(cell_h != null);
96
+ try std.testing.expectEqual(@as(u32, 'H'), cell_h.?.char);
97
+
98
+ const cell_e = current_buffer.get(1, 0);
99
+ try std.testing.expect(cell_e != null);
100
+ try std.testing.expectEqual(@as(u32, 'e'), cell_e.?.char);
101
+
102
+ const cell_w = current_buffer.get(6, 0);
103
+ try std.testing.expect(cell_w != null);
104
+ try std.testing.expectEqual(@as(u32, 'W'), cell_w.?.char);
105
+ }
106
+
107
+ test "renderer - multi-line text rendering" {
108
+ const pool = gp.initGlobalPool(std.testing.allocator);
109
+ defer gp.deinitGlobalPool();
110
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
111
+ defer local_link_pool.deinit();
112
+
113
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
114
+ defer tb.deinit();
115
+
116
+ try tb.setText("Line 1\nLine 2\nLine 3");
117
+
118
+ var view = try TextBufferView.init(std.testing.allocator, tb);
119
+ defer view.deinit();
120
+
121
+ var cli_renderer = try CliRenderer.create(
122
+ std.testing.allocator,
123
+ 80,
124
+ 24,
125
+ pool,
126
+ true,
127
+ );
128
+ defer cli_renderer.destroy();
129
+
130
+ const next_buffer = cli_renderer.getNextBuffer();
131
+ try next_buffer.drawTextBuffer(view, 0, 0);
132
+ cli_renderer.render(false);
133
+
134
+ const current_buffer = cli_renderer.getCurrentBuffer();
135
+
136
+ const cell_line1 = current_buffer.get(0, 0);
137
+ try std.testing.expect(cell_line1 != null);
138
+ try std.testing.expectEqual(@as(u32, 'L'), cell_line1.?.char);
139
+
140
+ const cell_line2 = current_buffer.get(0, 1);
141
+ try std.testing.expect(cell_line2 != null);
142
+ try std.testing.expectEqual(@as(u32, 'L'), cell_line2.?.char);
143
+
144
+ const cell_line3 = current_buffer.get(0, 2);
145
+ try std.testing.expect(cell_line3 != null);
146
+ try std.testing.expectEqual(@as(u32, 'L'), cell_line3.?.char);
147
+ }
148
+
149
+ test "renderer - emoji (wide grapheme) rendering" {
150
+ const pool = gp.initGlobalPool(std.testing.allocator);
151
+ defer gp.deinitGlobalPool();
152
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
153
+ defer local_link_pool.deinit();
154
+
155
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
156
+ defer tb.deinit();
157
+
158
+ try tb.setText("Hi πŸ‘‹ there");
159
+
160
+ var view = try TextBufferView.init(std.testing.allocator, tb);
161
+ defer view.deinit();
162
+
163
+ var cli_renderer = try CliRenderer.create(
164
+ std.testing.allocator,
165
+ 80,
166
+ 24,
167
+ pool,
168
+ true,
169
+ );
170
+ defer cli_renderer.destroy();
171
+
172
+ const next_buffer = cli_renderer.getNextBuffer();
173
+ try next_buffer.drawTextBuffer(view, 0, 0);
174
+ cli_renderer.render(false);
175
+
176
+ const current_buffer = cli_renderer.getCurrentBuffer();
177
+
178
+ const cell_h = current_buffer.get(0, 0);
179
+ try std.testing.expect(cell_h != null);
180
+ try std.testing.expectEqual(@as(u32, 'H'), cell_h.?.char);
181
+
182
+ const cell_i = current_buffer.get(1, 0);
183
+ try std.testing.expect(cell_i != null);
184
+ try std.testing.expectEqual(@as(u32, 'i'), cell_i.?.char);
185
+
186
+ const cell_space1 = current_buffer.get(2, 0);
187
+ try std.testing.expect(cell_space1 != null);
188
+ try std.testing.expectEqual(@as(u32, ' '), cell_space1.?.char);
189
+
190
+ const cell_emoji = current_buffer.get(3, 0);
191
+ try std.testing.expect(cell_emoji != null);
192
+ try std.testing.expect(gp.isGraphemeChar(cell_emoji.?.char));
193
+
194
+ const cell_emoji_continuation = current_buffer.get(4, 0);
195
+ try std.testing.expect(cell_emoji_continuation != null);
196
+ try std.testing.expect(gp.isContinuationChar(cell_emoji_continuation.?.char));
197
+
198
+ const cell_space2 = current_buffer.get(5, 0);
199
+ try std.testing.expect(cell_space2 != null);
200
+ try std.testing.expectEqual(@as(u32, ' '), cell_space2.?.char);
201
+
202
+ const cell_t = current_buffer.get(6, 0);
203
+ try std.testing.expect(cell_t != null);
204
+ try std.testing.expectEqual(@as(u32, 't'), cell_t.?.char);
205
+ }
206
+
207
+ test "renderer - CJK characters rendering" {
208
+ const pool = gp.initGlobalPool(std.testing.allocator);
209
+ defer gp.deinitGlobalPool();
210
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
211
+ defer local_link_pool.deinit();
212
+
213
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
214
+ defer tb.deinit();
215
+
216
+ try tb.setText("Hello δΈ–η•Œ");
217
+
218
+ var view = try TextBufferView.init(std.testing.allocator, tb);
219
+ defer view.deinit();
220
+
221
+ var cli_renderer = try CliRenderer.create(
222
+ std.testing.allocator,
223
+ 80,
224
+ 24,
225
+ pool,
226
+ true,
227
+ );
228
+ defer cli_renderer.destroy();
229
+
230
+ const next_buffer = cli_renderer.getNextBuffer();
231
+ try next_buffer.drawTextBuffer(view, 0, 0);
232
+ cli_renderer.render(false);
233
+
234
+ const current_buffer = cli_renderer.getCurrentBuffer();
235
+
236
+ const cell_h = current_buffer.get(0, 0);
237
+ try std.testing.expect(cell_h != null);
238
+ try std.testing.expectEqual(@as(u32, 'H'), cell_h.?.char);
239
+
240
+ const cell_space = current_buffer.get(5, 0);
241
+ try std.testing.expect(cell_space != null);
242
+ try std.testing.expectEqual(@as(u32, ' '), cell_space.?.char);
243
+
244
+ const cell_cjk1 = current_buffer.get(6, 0);
245
+ try std.testing.expect(cell_cjk1 != null);
246
+ try std.testing.expect(gp.isGraphemeChar(cell_cjk1.?.char));
247
+
248
+ const cell_cjk1_continuation = current_buffer.get(7, 0);
249
+ try std.testing.expect(cell_cjk1_continuation != null);
250
+ try std.testing.expect(gp.isContinuationChar(cell_cjk1_continuation.?.char));
251
+
252
+ const cell_cjk2 = current_buffer.get(8, 0);
253
+ try std.testing.expect(cell_cjk2 != null);
254
+ try std.testing.expect(gp.isGraphemeChar(cell_cjk2.?.char));
255
+
256
+ const cell_cjk2_continuation = current_buffer.get(9, 0);
257
+ try std.testing.expect(cell_cjk2_continuation != null);
258
+ try std.testing.expect(gp.isContinuationChar(cell_cjk2_continuation.?.char));
259
+ }
260
+
261
+ test "renderer - mixed ASCII, emoji, and CJK" {
262
+ const pool = gp.initGlobalPool(std.testing.allocator);
263
+ defer gp.deinitGlobalPool();
264
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
265
+ defer local_link_pool.deinit();
266
+
267
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
268
+ defer tb.deinit();
269
+
270
+ try tb.setText("A πŸ˜€ δΈ–");
271
+
272
+ var view = try TextBufferView.init(std.testing.allocator, tb);
273
+ defer view.deinit();
274
+
275
+ var cli_renderer = try CliRenderer.create(
276
+ std.testing.allocator,
277
+ 80,
278
+ 24,
279
+ pool,
280
+ true,
281
+ );
282
+ defer cli_renderer.destroy();
283
+
284
+ const next_buffer = cli_renderer.getNextBuffer();
285
+ try next_buffer.drawTextBuffer(view, 0, 0);
286
+ cli_renderer.render(false);
287
+
288
+ const current_buffer = cli_renderer.getCurrentBuffer();
289
+
290
+ const cell_a = current_buffer.get(0, 0);
291
+ try std.testing.expect(cell_a != null);
292
+ try std.testing.expectEqual(@as(u32, 'A'), cell_a.?.char);
293
+
294
+ const cell_space1 = current_buffer.get(1, 0);
295
+ try std.testing.expect(cell_space1 != null);
296
+ try std.testing.expectEqual(@as(u32, ' '), cell_space1.?.char);
297
+
298
+ const cell_emoji = current_buffer.get(2, 0);
299
+ try std.testing.expect(cell_emoji != null);
300
+ try std.testing.expect(gp.isGraphemeChar(cell_emoji.?.char));
301
+
302
+ const cell_emoji_continuation = current_buffer.get(3, 0);
303
+ try std.testing.expect(cell_emoji_continuation != null);
304
+ try std.testing.expect(gp.isContinuationChar(cell_emoji_continuation.?.char));
305
+
306
+ const cell_space2 = current_buffer.get(4, 0);
307
+ try std.testing.expect(cell_space2 != null);
308
+ try std.testing.expectEqual(@as(u32, ' '), cell_space2.?.char);
309
+
310
+ const cell_cjk = current_buffer.get(5, 0);
311
+ try std.testing.expect(cell_cjk != null);
312
+ try std.testing.expect(gp.isGraphemeChar(cell_cjk.?.char));
313
+
314
+ const cell_cjk_continuation = current_buffer.get(6, 0);
315
+ try std.testing.expect(cell_cjk_continuation != null);
316
+ try std.testing.expect(gp.isContinuationChar(cell_cjk_continuation.?.char));
317
+ }
318
+
319
+ test "renderer - resize updates dimensions" {
320
+ const pool = gp.initGlobalPool(std.testing.allocator);
321
+ defer gp.deinitGlobalPool();
322
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
323
+ defer local_link_pool.deinit();
324
+
325
+ var cli_renderer = try CliRenderer.create(
326
+ std.testing.allocator,
327
+ 80,
328
+ 24,
329
+ pool,
330
+ true,
331
+ );
332
+ defer cli_renderer.destroy();
333
+
334
+ try std.testing.expectEqual(@as(u32, 80), cli_renderer.width);
335
+ try std.testing.expectEqual(@as(u32, 24), cli_renderer.height);
336
+
337
+ try cli_renderer.resize(120, 40);
338
+
339
+ try std.testing.expectEqual(@as(u32, 120), cli_renderer.width);
340
+ try std.testing.expectEqual(@as(u32, 40), cli_renderer.height);
341
+ }
342
+
343
+ test "renderer - background color setting" {
344
+ const pool = gp.initGlobalPool(std.testing.allocator);
345
+ defer gp.deinitGlobalPool();
346
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
347
+ defer local_link_pool.deinit();
348
+
349
+ var cli_renderer = try CliRenderer.create(
350
+ std.testing.allocator,
351
+ 80,
352
+ 24,
353
+ pool,
354
+ true,
355
+ );
356
+ defer cli_renderer.destroy();
357
+
358
+ const bg_color = RGBA{ 0.1, 0.2, 0.3, 1.0 };
359
+ cli_renderer.setBackgroundColor(bg_color);
360
+
361
+ try std.testing.expectEqual(bg_color, cli_renderer.backgroundColor);
362
+ try std.testing.expectEqual(bg_color, cli_renderer.getNextBuffer().getBlendBackdropColor().?);
363
+
364
+ const transparent_bg = RGBA{ 0.25, 0.5, 0.75, 0.0 };
365
+ cli_renderer.setBackgroundColor(transparent_bg);
366
+
367
+ try std.testing.expectEqual(transparent_bg, cli_renderer.backgroundColor);
368
+ try std.testing.expectEqual(RGBA{ 0.25, 0.5, 0.75, 1.0 }, cli_renderer.getNextBuffer().getBlendBackdropColor().?);
369
+ }
370
+
371
+ test "renderer - empty text buffer renders correctly" {
372
+ const pool = gp.initGlobalPool(std.testing.allocator);
373
+ defer gp.deinitGlobalPool();
374
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
375
+ defer local_link_pool.deinit();
376
+
377
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
378
+ defer tb.deinit();
379
+
380
+ try tb.setText("");
381
+
382
+ var view = try TextBufferView.init(std.testing.allocator, tb);
383
+ defer view.deinit();
384
+
385
+ var cli_renderer = try CliRenderer.create(
386
+ std.testing.allocator,
387
+ 80,
388
+ 24,
389
+ pool,
390
+ true,
391
+ );
392
+ defer cli_renderer.destroy();
393
+
394
+ const next_buffer = cli_renderer.getNextBuffer();
395
+ try next_buffer.drawTextBuffer(view, 0, 0);
396
+ cli_renderer.render(false);
397
+ }
398
+
399
+ test "renderer - multiple renders update currentRenderBuffer" {
400
+ const pool = gp.initGlobalPool(std.testing.allocator);
401
+ defer gp.deinitGlobalPool();
402
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
403
+ defer local_link_pool.deinit();
404
+
405
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
406
+ defer tb.deinit();
407
+
408
+ var view = try TextBufferView.init(std.testing.allocator, tb);
409
+ defer view.deinit();
410
+
411
+ var cli_renderer = try CliRenderer.create(
412
+ std.testing.allocator,
413
+ 80,
414
+ 24,
415
+ pool,
416
+ true,
417
+ );
418
+ defer cli_renderer.destroy();
419
+
420
+ try tb.setText("Hello");
421
+ const next_buffer = cli_renderer.getNextBuffer();
422
+ try next_buffer.drawTextBuffer(view, 0, 0);
423
+ cli_renderer.render(false);
424
+
425
+ var current_buffer = cli_renderer.getCurrentBuffer();
426
+ var first_cell = current_buffer.get(0, 0);
427
+ try std.testing.expect(first_cell != null);
428
+ try std.testing.expectEqual(@as(u32, 'H'), first_cell.?.char);
429
+
430
+ try tb.setText("World");
431
+ const next_buffer2 = cli_renderer.getNextBuffer();
432
+ try next_buffer2.drawTextBuffer(view, 0, 0);
433
+ cli_renderer.render(false);
434
+
435
+ current_buffer = cli_renderer.getCurrentBuffer();
436
+ first_cell = current_buffer.get(0, 0);
437
+ try std.testing.expect(first_cell != null);
438
+ try std.testing.expectEqual(@as(u32, 'W'), first_cell.?.char);
439
+ }
440
+
441
+ test "renderer - 1000 frame render loop with setStyledText" {
442
+ const pool = gp.initGlobalPool(std.testing.allocator);
443
+ defer gp.deinitGlobalPool();
444
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
445
+ defer local_link_pool.deinit();
446
+
447
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
448
+ defer tb.deinit();
449
+
450
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
451
+ defer style.deinit();
452
+ tb.setSyntaxStyle(style);
453
+
454
+ var view = try TextBufferView.init(std.testing.allocator, tb);
455
+ defer view.deinit();
456
+
457
+ var cli_renderer = try CliRenderer.create(
458
+ std.testing.allocator,
459
+ 80,
460
+ 24,
461
+ pool,
462
+ true,
463
+ );
464
+ defer cli_renderer.destroy();
465
+
466
+ var opt_buffer = try OptimizedBuffer.init(
467
+ std.testing.allocator,
468
+ 80,
469
+ 24,
470
+ .{ .pool = pool, .width_method = .unicode },
471
+ );
472
+ defer opt_buffer.deinit();
473
+
474
+ const frame_texts = [_][]const u8{
475
+ "Frame ASCII",
476
+ "Frame πŸ‘‹ emoji",
477
+ "Frame δΈ–η•Œ CJK",
478
+ "Mixed πŸ˜€ δΈ–",
479
+ };
480
+
481
+ const fg_color = [4]f32{ 1.0, 0.8, 0.6, 1.0 };
482
+ const bg_color = [4]f32{ 0.1, 0.1, 0.2, 1.0 };
483
+
484
+ var frame: u32 = 0;
485
+ while (frame < 1000) : (frame += 1) {
486
+ const text_idx = frame % frame_texts.len;
487
+ const text = frame_texts[text_idx];
488
+
489
+ const chunks = [_]text_buffer.StyledChunk{.{
490
+ .text_ptr = text.ptr,
491
+ .text_len = text.len,
492
+ .fg_ptr = @ptrCast(&fg_color),
493
+ .bg_ptr = @ptrCast(&bg_color),
494
+ .attributes = 0,
495
+ }};
496
+
497
+ try tb.setStyledText(&chunks);
498
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
499
+ try opt_buffer.drawTextBuffer(view, 0, 0);
500
+
501
+ const next_buffer = cli_renderer.getNextBuffer();
502
+ try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
503
+ next_buffer.drawFrameBuffer(0, 0, opt_buffer, null, null, null, null);
504
+
505
+ cli_renderer.render(false);
506
+
507
+ if (frame % 100 == 0) {
508
+ const current_buffer = cli_renderer.getCurrentBuffer();
509
+ const first_cell = current_buffer.get(0, 0);
510
+ try std.testing.expect(first_cell != null);
511
+ try std.testing.expect(first_cell.?.char != 32);
512
+
513
+ try std.testing.expectEqual(frame + 1, cli_renderer.renderStats.frameCount);
514
+ }
515
+ }
516
+
517
+ try std.testing.expectEqual(@as(u64, 1000), cli_renderer.renderStats.frameCount);
518
+
519
+ const current_buffer = cli_renderer.getCurrentBuffer();
520
+ const final_cell = current_buffer.get(0, 0);
521
+ try std.testing.expect(final_cell != null);
522
+ try std.testing.expectEqual(@as(u32, 'M'), final_cell.?.char);
523
+ }
524
+
525
+ test "renderer - grapheme pool refcounting with frame buffer fast path" {
526
+ const limited_pool = gp.initGlobalPoolWithOptions(std.testing.allocator, .{
527
+ .slots_per_page = [_]u32{ 2, 2, 2, 2, 2 },
528
+ });
529
+ defer gp.deinitGlobalPool();
530
+
531
+ var tb = try TextBuffer.init(std.testing.allocator, limited_pool, link.initGlobalLinkPool(std.testing.allocator), .unicode);
532
+ defer tb.deinit();
533
+
534
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
535
+ defer style.deinit();
536
+ tb.setSyntaxStyle(style);
537
+
538
+ var view = try TextBufferView.init(std.testing.allocator, tb);
539
+ defer view.deinit();
540
+
541
+ var cli_renderer = try CliRenderer.create(
542
+ std.testing.allocator,
543
+ 80,
544
+ 24,
545
+ limited_pool,
546
+ true,
547
+ );
548
+ defer cli_renderer.destroy();
549
+
550
+ var frame_buffer = try OptimizedBuffer.init(
551
+ std.testing.allocator,
552
+ 80,
553
+ 24,
554
+ .{ .pool = limited_pool, .width_method = .unicode, .respectAlpha = false },
555
+ );
556
+ defer frame_buffer.deinit();
557
+
558
+ const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
559
+ const bg_color = [4]f32{ 0.0, 0.0, 0.0, 0.0 };
560
+
561
+ const text_with_emoji = "πŸ‘‹";
562
+ const chunks = [_]text_buffer.StyledChunk{.{
563
+ .text_ptr = text_with_emoji.ptr,
564
+ .text_len = text_with_emoji.len,
565
+ .fg_ptr = @ptrCast(&fg_color),
566
+ .bg_ptr = @ptrCast(&bg_color),
567
+ .attributes = 0,
568
+ }};
569
+ try tb.setStyledText(&chunks);
570
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
571
+ try frame_buffer.drawTextBuffer(view, 0, 0);
572
+
573
+ const next_buffer = cli_renderer.getNextBuffer();
574
+ next_buffer.setRespectAlpha(false);
575
+ try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
576
+
577
+ next_buffer.drawFrameBuffer(0, 0, frame_buffer, null, null, null, null);
578
+
579
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
580
+
581
+ var i: usize = 0;
582
+ while (i < 10) : (i += 1) {
583
+ const new_text = "πŸŽ‰πŸš€πŸ’―";
584
+ const new_chunks = [_]text_buffer.StyledChunk{.{
585
+ .text_ptr = new_text.ptr,
586
+ .text_len = new_text.len,
587
+ .fg_ptr = @ptrCast(&fg_color),
588
+ .bg_ptr = @ptrCast(&bg_color),
589
+ .attributes = 0,
590
+ }};
591
+ try tb.setStyledText(&new_chunks);
592
+ try frame_buffer.drawTextBuffer(view, 0, 0);
593
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
594
+ }
595
+
596
+ cli_renderer.render(false);
597
+
598
+ const current_buffer = cli_renderer.getCurrentBuffer();
599
+ const rendered_cell = current_buffer.get(0, 0);
600
+ try std.testing.expect(rendered_cell != null);
601
+ }
602
+
603
+ test "renderer - unchanged grapheme should not churn IDs across frames" {
604
+ const pool = gp.initGlobalPool(std.testing.allocator);
605
+ defer gp.deinitGlobalPool();
606
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
607
+ defer local_link_pool.deinit();
608
+
609
+ var cli_renderer = try CliRenderer.create(
610
+ std.testing.allocator,
611
+ 4,
612
+ 1,
613
+ pool,
614
+ true,
615
+ );
616
+ defer cli_renderer.destroy();
617
+
618
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
619
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
620
+
621
+ const first_next_buffer = cli_renderer.getNextBuffer();
622
+ try first_next_buffer.drawText("πŸ‘‹", 0, 0, fg, bg, 0);
623
+ cli_renderer.render(false);
624
+
625
+ const first_output = cli_renderer.getLastOutputForTest();
626
+ try std.testing.expect(std.mem.indexOf(u8, first_output, "πŸ‘‹") != null);
627
+
628
+ const current_buffer = cli_renderer.getCurrentBuffer();
629
+ const first_cell = current_buffer.get(0, 0);
630
+ try std.testing.expect(first_cell != null);
631
+ try std.testing.expect(gp.isGraphemeChar(first_cell.?.char));
632
+ const first_gid = gp.graphemeIdFromChar(first_cell.?.char);
633
+
634
+ const second_next_buffer = cli_renderer.getNextBuffer();
635
+ try second_next_buffer.drawText("πŸ‘‹", 0, 0, fg, bg, 0);
636
+
637
+ const second_cell = second_next_buffer.get(0, 0);
638
+ try std.testing.expect(second_cell != null);
639
+ try std.testing.expect(gp.isGraphemeChar(second_cell.?.char));
640
+ const second_gid = gp.graphemeIdFromChar(second_cell.?.char);
641
+
642
+ // Same grapheme content in consecutive frames should keep a stable ID,
643
+ // otherwise diff/write treats unchanged cells as modified every frame.
644
+ try std.testing.expectEqual(first_gid, second_gid);
645
+
646
+ cli_renderer.render(false);
647
+
648
+ const second_output = cli_renderer.getLastOutputForTest();
649
+ try std.testing.expect(std.mem.indexOf(u8, second_output, "πŸ‘‹") == null);
650
+ }
651
+
652
+ test "renderer - hyperlinks enabled with OSC 8 output" {
653
+ const pool = gp.initGlobalPool(std.testing.allocator);
654
+ defer gp.deinitGlobalPool();
655
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
656
+ defer link.deinitGlobalLinkPool();
657
+
658
+ var cli_renderer = try CliRenderer.create(
659
+ std.testing.allocator,
660
+ 80,
661
+ 24,
662
+ pool,
663
+ true,
664
+ );
665
+ defer cli_renderer.destroy();
666
+
667
+ // Enable hyperlinks capability
668
+ cli_renderer.terminal.caps.hyperlinks = true;
669
+
670
+ // Allocate a link
671
+ const link_id = try local_link_pool.alloc("https://example.com");
672
+ const attributes = ansi.TextAttributes.setLinkId(ansi.TextAttributes.BOLD, link_id);
673
+
674
+ const next_buffer = cli_renderer.getNextBuffer();
675
+
676
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
677
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
678
+ try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes);
679
+
680
+ cli_renderer.render(false);
681
+
682
+ const output = cli_renderer.getLastOutputForTest();
683
+
684
+ // Verify OSC 8 contains id parameter
685
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b]8;id=") != null);
686
+ // Verify OSC 8 contains the URL
687
+ try std.testing.expect(std.mem.indexOf(u8, output, ";https://example.com\x1b\\") != null);
688
+
689
+ // Verify output contains OSC 8 end sequence
690
+ const end_seq = "\x1b]8;;\x1b\\";
691
+ var count: usize = 0;
692
+ var pos: usize = 0;
693
+ while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| {
694
+ count += 1;
695
+ pos += found + end_seq.len;
696
+ }
697
+ try std.testing.expect(count >= 1);
698
+ }
699
+
700
+ test "renderer - hyperlinks disabled no OSC 8 output" {
701
+ const pool = gp.initGlobalPool(std.testing.allocator);
702
+ defer gp.deinitGlobalPool();
703
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
704
+ defer link.deinitGlobalLinkPool();
705
+
706
+ var cli_renderer = try CliRenderer.create(
707
+ std.testing.allocator,
708
+ 80,
709
+ 24,
710
+ pool,
711
+ true,
712
+ );
713
+ defer cli_renderer.destroy();
714
+
715
+ // Hyperlinks disabled by default
716
+ cli_renderer.terminal.caps.hyperlinks = false;
717
+
718
+ // Allocate a link
719
+ const link_id = try local_link_pool.alloc("https://example.com");
720
+ const attributes = ansi.TextAttributes.setLinkId(0, link_id);
721
+
722
+ const next_buffer = cli_renderer.getNextBuffer();
723
+
724
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
725
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
726
+ try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes);
727
+
728
+ cli_renderer.render(false);
729
+
730
+ const output = cli_renderer.getLastOutputForTest();
731
+
732
+ // Verify output does NOT contain OSC 8 sequences
733
+ try std.testing.expect(std.mem.indexOf(u8, output, "]8;;") == null);
734
+ try std.testing.expect(std.mem.indexOf(u8, output, "]8;id=") == null);
735
+ }
736
+
737
+ test "renderer - link transition mid-line" {
738
+ const pool = gp.initGlobalPool(std.testing.allocator);
739
+ defer gp.deinitGlobalPool();
740
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
741
+ defer link.deinitGlobalLinkPool();
742
+
743
+ var cli_renderer = try CliRenderer.create(
744
+ std.testing.allocator,
745
+ 80,
746
+ 24,
747
+ pool,
748
+ true,
749
+ );
750
+ defer cli_renderer.destroy();
751
+
752
+ // Enable hyperlinks
753
+ cli_renderer.terminal.caps.hyperlinks = true;
754
+
755
+ const next_buffer = cli_renderer.getNextBuffer();
756
+
757
+ // Allocate two different links
758
+ const link_id1 = try local_link_pool.alloc("https://first.com");
759
+ const link_id2 = try local_link_pool.alloc("https://second.com");
760
+
761
+ const attr1 = ansi.TextAttributes.setLinkId(0, link_id1);
762
+ const attr2 = ansi.TextAttributes.setLinkId(0, link_id2);
763
+
764
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
765
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
766
+
767
+ // Draw first link
768
+ try next_buffer.drawText("First", 0, 0, fg, bg, attr1);
769
+ // Draw second link
770
+ try next_buffer.drawText("Second", 6, 0, fg, bg, attr2);
771
+ // Draw no link
772
+ try next_buffer.drawText("Normal", 13, 0, fg, bg, 0);
773
+
774
+ cli_renderer.render(false);
775
+
776
+ const output = cli_renderer.getLastOutputForTest();
777
+
778
+ // Should contain both URLs
779
+ try std.testing.expect(std.mem.indexOf(u8, output, "https://first.com") != null);
780
+ try std.testing.expect(std.mem.indexOf(u8, output, "https://second.com") != null);
781
+
782
+ // Should have multiple OSC 8 end sequences (at least 2 transitions)
783
+ const end_seq = "\x1b]8;;\x1b\\";
784
+ var count: usize = 0;
785
+ var pos: usize = 0;
786
+ while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| {
787
+ count += 1;
788
+ pos += found + end_seq.len;
789
+ }
790
+ try std.testing.expect(count >= 2);
791
+ }
792
+
793
+ test "renderer - hyperlink spanning multiple rows uses same id" {
794
+ const pool = gp.initGlobalPool(std.testing.allocator);
795
+ defer gp.deinitGlobalPool();
796
+
797
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
798
+ defer link.deinitGlobalLinkPool();
799
+
800
+ var cli_renderer = try CliRenderer.create(
801
+ std.testing.allocator,
802
+ 80,
803
+ 24,
804
+ pool,
805
+ true,
806
+ );
807
+ defer cli_renderer.destroy();
808
+
809
+ // Enable hyperlinks
810
+ cli_renderer.terminal.caps.hyperlinks = true;
811
+
812
+ const next_buffer = cli_renderer.getNextBuffer();
813
+
814
+ // Allocate a single link
815
+ const link_id = try link_pool.alloc("https://example.com/long-url");
816
+ const attributes = ansi.TextAttributes.setLinkId(0, link_id);
817
+
818
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
819
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
820
+
821
+ // Fill entire row 0 with linked text so link is never interrupted by empty cells
822
+ try next_buffer.drawText("01234567890123456789012345678901234567890123456789012345678901234567890123456789", 0, 0, fg, bg, attributes);
823
+ // Continue the same link on row 1
824
+ try next_buffer.drawText("Here", 0, 1, fg, bg, attributes);
825
+
826
+ cli_renderer.render(false);
827
+
828
+ const output = cli_renderer.getLastOutputForTest();
829
+
830
+ // Build expected OSC 8 open sequence with the link id
831
+ var buf: [256]u8 = undefined;
832
+ const expected_open = std.fmt.bufPrint(&buf, "\x1b]8;id={d};https://example.com/long-url\x1b\\", .{link_id}) catch unreachable;
833
+ var count: usize = 0;
834
+ var pos: usize = 0;
835
+ while (std.mem.indexOf(u8, output[pos..], expected_open)) |found| {
836
+ count += 1;
837
+ pos += found + expected_open.len;
838
+ }
839
+ // Should appear exactly once: the link stays open across rows without
840
+ // close/reopen at row boundaries, so terminals treat it as one contiguous link.
841
+ try std.testing.expectEqual(@as(usize, 1), count);
842
+ }
843
+
844
+ // ============================================================================
845
+ // GRAPHEME CURSOR POSITIONING TESTS
846
+ // ============================================================================
847
+
848
+ test "renderer - default cursor style emits reset cursor ANSI" {
849
+ const pool = gp.initGlobalPool(std.testing.allocator);
850
+ defer gp.deinitGlobalPool();
851
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
852
+ defer local_link_pool.deinit();
853
+
854
+ var cli_renderer = try CliRenderer.create(
855
+ std.testing.allocator,
856
+ 80,
857
+ 24,
858
+ pool,
859
+ true,
860
+ );
861
+ defer cli_renderer.destroy();
862
+
863
+ cli_renderer.terminal.setCursorPosition(4, 2, true);
864
+ cli_renderer.render(false);
865
+
866
+ const output = cli_renderer.getLastOutputForTest();
867
+
868
+ try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.defaultCursorStyle) != null);
869
+ try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.cursorBlock) == null);
870
+ }
871
+
872
+ test "renderer - explicit_cursor_positioning emits cursor move after wide graphemes" {
873
+ const pool = gp.initGlobalPool(std.testing.allocator);
874
+ defer gp.deinitGlobalPool();
875
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
876
+ defer local_link_pool.deinit();
877
+
878
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
879
+ defer tb.deinit();
880
+
881
+ try tb.setText("πŸ‘‹X");
882
+
883
+ var view = try TextBufferView.init(std.testing.allocator, tb);
884
+ defer view.deinit();
885
+
886
+ var cli_renderer = try CliRenderer.create(
887
+ std.testing.allocator,
888
+ 80,
889
+ 24,
890
+ pool,
891
+ true,
892
+ );
893
+ defer cli_renderer.destroy();
894
+
895
+ cli_renderer.terminal.caps.explicit_cursor_positioning = true;
896
+ cli_renderer.terminal.caps.explicit_width = false;
897
+
898
+ const next_buffer = cli_renderer.getNextBuffer();
899
+ try next_buffer.drawTextBuffer(view, 0, 0);
900
+
901
+ cli_renderer.render(false);
902
+
903
+ const output = cli_renderer.getLastOutputForTest();
904
+
905
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null);
906
+ }
907
+
908
+ test "renderer - explicit_cursor_positioning produces more cursor moves" {
909
+ const pool = gp.initGlobalPool(std.testing.allocator);
910
+ defer gp.deinitGlobalPool();
911
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
912
+ defer local_link_pool.deinit();
913
+
914
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
915
+ defer tb.deinit();
916
+ try tb.setText("πŸ‘‹πŸŽ‰πŸš€");
917
+
918
+ var view = try TextBufferView.init(std.testing.allocator, tb);
919
+ defer view.deinit();
920
+
921
+ var cli_renderer1 = try CliRenderer.create(
922
+ std.testing.allocator,
923
+ 80,
924
+ 24,
925
+ pool,
926
+ true,
927
+ );
928
+ defer cli_renderer1.destroy();
929
+
930
+ cli_renderer1.terminal.caps.explicit_cursor_positioning = false;
931
+ cli_renderer1.terminal.caps.explicit_width = false;
932
+
933
+ const next_buffer1 = cli_renderer1.getNextBuffer();
934
+ try next_buffer1.drawTextBuffer(view, 0, 0);
935
+ cli_renderer1.render(false);
936
+ const output_without = cli_renderer1.getLastOutputForTest();
937
+
938
+ var cli_renderer2 = try CliRenderer.create(
939
+ std.testing.allocator,
940
+ 80,
941
+ 24,
942
+ pool,
943
+ true,
944
+ );
945
+ defer cli_renderer2.destroy();
946
+
947
+ cli_renderer2.terminal.caps.explicit_cursor_positioning = true;
948
+ cli_renderer2.terminal.caps.explicit_width = false;
949
+
950
+ const next_buffer2 = cli_renderer2.getNextBuffer();
951
+ try next_buffer2.drawTextBuffer(view, 0, 0);
952
+ cli_renderer2.render(false);
953
+ const output_with = cli_renderer2.getLastOutputForTest();
954
+
955
+ var count_without: usize = 0;
956
+ var count_with: usize = 0;
957
+
958
+ var i: usize = 0;
959
+ while (i + 3 < output_without.len) : (i += 1) {
960
+ if (output_without[i] == '\x1b' and output_without[i + 1] == '[') {
961
+ var j = i + 2;
962
+ while (j < output_without.len and ((output_without[j] >= '0' and output_without[j] <= '9') or output_without[j] == ';')) : (j += 1) {}
963
+ if (j < output_without.len and output_without[j] == 'H') {
964
+ count_without += 1;
965
+ }
966
+ }
967
+ }
968
+
969
+ i = 0;
970
+ while (i + 3 < output_with.len) : (i += 1) {
971
+ if (output_with[i] == '\x1b' and output_with[i + 1] == '[') {
972
+ var j = i + 2;
973
+ while (j < output_with.len and ((output_with[j] >= '0' and output_with[j] <= '9') or output_with[j] == ';')) : (j += 1) {}
974
+ if (j < output_with.len and output_with[j] == 'H') {
975
+ count_with += 1;
976
+ }
977
+ }
978
+ }
979
+
980
+ try std.testing.expect(count_with > count_without);
981
+ }
982
+
983
+ test "renderer - explicit_cursor_positioning with CJK characters" {
984
+ const pool = gp.initGlobalPool(std.testing.allocator);
985
+ defer gp.deinitGlobalPool();
986
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
987
+ defer local_link_pool.deinit();
988
+
989
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
990
+ defer tb.deinit();
991
+
992
+ try tb.setText("δΈ–X");
993
+
994
+ var view = try TextBufferView.init(std.testing.allocator, tb);
995
+ defer view.deinit();
996
+
997
+ var cli_renderer = try CliRenderer.create(
998
+ std.testing.allocator,
999
+ 80,
1000
+ 24,
1001
+ pool,
1002
+ true,
1003
+ );
1004
+ defer cli_renderer.destroy();
1005
+
1006
+ cli_renderer.terminal.caps.explicit_cursor_positioning = true;
1007
+ cli_renderer.terminal.caps.explicit_width = false;
1008
+
1009
+ const next_buffer = cli_renderer.getNextBuffer();
1010
+ try next_buffer.drawTextBuffer(view, 0, 0);
1011
+
1012
+ cli_renderer.render(false);
1013
+
1014
+ const output = cli_renderer.getLastOutputForTest();
1015
+
1016
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null);
1017
+ }