@fairyhunter13/opentui-core 0.1.88 β†’ 0.1.90

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 (570) 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 +141 -0
  7. package/docs/env-vars.md +140 -0
  8. package/docs/getting-started.md +353 -0
  9. package/docs/renderables-vs-constructs.md +159 -0
  10. package/docs/tree-sitter.md +311 -0
  11. package/package.json +61 -52
  12. package/scripts/build.ts +400 -0
  13. package/scripts/publish.ts +60 -0
  14. package/src/3d/SpriteResourceManager.ts +286 -0
  15. package/src/3d/SpriteUtils.ts +71 -0
  16. package/src/3d/TextureUtils.ts +196 -0
  17. package/src/3d/ThreeRenderable.ts +197 -0
  18. package/src/3d/WGPURenderer.ts +294 -0
  19. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  20. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  21. package/src/3d/animation/SpriteAnimator.ts +633 -0
  22. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  23. package/src/3d/canvas.ts +464 -0
  24. package/src/3d/index.ts +12 -0
  25. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  26. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  27. package/src/3d/physics/physics-interface.ts +31 -0
  28. package/src/3d/shaders/supersampling.wgsl +201 -0
  29. package/src/3d.ts +3 -0
  30. package/src/NativeSpanFeed.ts +300 -0
  31. package/src/Renderable.ts +1698 -0
  32. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  33. package/src/animation/Timeline.test.ts +2709 -0
  34. package/src/animation/Timeline.ts +598 -0
  35. package/src/ansi.ts +18 -0
  36. package/src/benchmark/latest-all-bench-run.json +707 -0
  37. package/src/benchmark/latest-async-bench-run.json +336 -0
  38. package/src/benchmark/latest-default-bench-run.json +657 -0
  39. package/src/benchmark/latest-large-bench-run.json +707 -0
  40. package/src/benchmark/latest-quick-bench-run.json +207 -0
  41. package/src/benchmark/markdown-benchmark.ts +1804 -0
  42. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  43. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  44. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  45. package/src/benchmark/native-span-feed-compare.ts +280 -0
  46. package/src/benchmark/renderer-benchmark.ts +754 -0
  47. package/src/benchmark/text-table-benchmark.ts +947 -0
  48. package/src/buffer.test.ts +291 -0
  49. package/src/buffer.ts +519 -0
  50. package/src/console.test.ts +612 -0
  51. package/src/console.ts +1255 -0
  52. package/src/edit-buffer.test.ts +1769 -0
  53. package/src/edit-buffer.ts +411 -0
  54. package/src/editor-view.test.ts +1032 -0
  55. package/src/editor-view.ts +284 -0
  56. package/src/examples/ascii-font-selection-demo.ts +245 -0
  57. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  58. package/src/examples/assets/concrete.png +0 -0
  59. package/src/examples/assets/crate.png +0 -0
  60. package/src/examples/assets/crate_emissive.png +0 -0
  61. package/src/examples/assets/forrest_background.png +0 -0
  62. package/src/examples/assets/hast-example.json +1018 -0
  63. package/src/examples/assets/heart.png +0 -0
  64. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  65. package/src/examples/assets/main_char_idle.png +0 -0
  66. package/src/examples/assets/main_char_jump_end.png +0 -0
  67. package/src/examples/assets/main_char_jump_landing.png +0 -0
  68. package/src/examples/assets/main_char_jump_start.png +0 -0
  69. package/src/examples/assets/main_char_run_loop.png +0 -0
  70. package/src/examples/assets/roughness_map.jpg +0 -0
  71. package/src/examples/build.ts +115 -0
  72. package/src/examples/code-demo.ts +584 -0
  73. package/src/examples/console-demo.ts +358 -0
  74. package/src/examples/core-plugin-slots-demo.ts +759 -0
  75. package/src/examples/diff-demo.ts +699 -0
  76. package/src/examples/draggable-three-demo.ts +259 -0
  77. package/src/examples/editor-demo.ts +322 -0
  78. package/src/examples/extmarks-demo.ts +204 -0
  79. package/src/examples/focus-restore-demo.ts +310 -0
  80. package/src/examples/fonts.ts +245 -0
  81. package/src/examples/fractal-shader-demo.ts +268 -0
  82. package/src/examples/framebuffer-demo.ts +674 -0
  83. package/src/examples/full-unicode-demo.ts +181 -0
  84. package/src/examples/golden-star-demo.ts +933 -0
  85. package/src/examples/grayscale-buffer-demo.ts +249 -0
  86. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  87. package/src/examples/index.ts +925 -0
  88. package/src/examples/input-demo.ts +377 -0
  89. package/src/examples/input-select-layout-demo.ts +425 -0
  90. package/src/examples/install.sh +143 -0
  91. package/src/examples/keypress-debug-demo.ts +452 -0
  92. package/src/examples/lib/HexList.ts +122 -0
  93. package/src/examples/lib/PaletteGrid.ts +125 -0
  94. package/src/examples/lib/standalone-keys.ts +25 -0
  95. package/src/examples/lib/tab-controller.ts +243 -0
  96. package/src/examples/lights-phong-demo.ts +290 -0
  97. package/src/examples/link-demo.ts +220 -0
  98. package/src/examples/live-state-demo.ts +480 -0
  99. package/src/examples/markdown-demo.ts +620 -0
  100. package/src/examples/mouse-interaction-demo.ts +428 -0
  101. package/src/examples/nested-zindex-demo.ts +357 -0
  102. package/src/examples/opacity-example.ts +235 -0
  103. package/src/examples/opentui-demo.ts +1057 -0
  104. package/src/examples/physx-planck-2d-demo.ts +507 -0
  105. package/src/examples/physx-rapier-2d-demo.ts +526 -0
  106. package/src/examples/relative-positioning-demo.ts +323 -0
  107. package/src/examples/scroll-example.ts +214 -0
  108. package/src/examples/scrollbox-mouse-test.ts +112 -0
  109. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  110. package/src/examples/select-demo.ts +237 -0
  111. package/src/examples/shader-cube-demo.ts +772 -0
  112. package/src/examples/simple-layout-example.ts +591 -0
  113. package/src/examples/slider-demo.ts +617 -0
  114. package/src/examples/split-mode-demo.ts +445 -0
  115. package/src/examples/sprite-animation-demo.ts +443 -0
  116. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  117. package/src/examples/static-sprite-demo.ts +193 -0
  118. package/src/examples/sticky-scroll-example.ts +308 -0
  119. package/src/examples/styled-text-demo.ts +282 -0
  120. package/src/examples/tab-select-demo.ts +219 -0
  121. package/src/examples/terminal-title.ts +29 -0
  122. package/src/examples/terminal.ts +305 -0
  123. package/src/examples/text-node-demo.ts +416 -0
  124. package/src/examples/text-selection-demo.ts +377 -0
  125. package/src/examples/text-table-demo.ts +503 -0
  126. package/src/examples/text-truncation-demo.ts +481 -0
  127. package/src/examples/text-wrap.ts +757 -0
  128. package/src/examples/texture-loading-demo.ts +259 -0
  129. package/src/examples/timeline-example.ts +670 -0
  130. package/src/examples/transparency-demo.ts +241 -0
  131. package/src/examples/vnode-composition-demo.ts +404 -0
  132. package/src/index.ts +22 -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 +168 -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 +31 -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 +280 -0
  168. package/src/lib/keymapping.ts +87 -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 +1676 -0
  187. package/src/lib/stdin-parser.ts +1248 -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 +331 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +270 -0
  197. package/src/lib/tree-sitter/client.test.ts +1061 -0
  198. package/src/lib/tree-sitter/client.ts +615 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +80 -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 +1001 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +75 -0
  204. package/src/lib/tree-sitter/resolve-ft.ts +62 -0
  205. package/src/lib/tree-sitter/types.ts +81 -0
  206. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  207. package/src/lib/tree-sitter-styled-text.ts +306 -0
  208. package/src/lib/validate-dir-name.ts +55 -0
  209. package/src/lib/yoga.options.test.ts +628 -0
  210. package/src/lib/yoga.options.ts +346 -0
  211. package/src/plugins/core-slot.ts +579 -0
  212. package/src/plugins/registry.ts +377 -0
  213. package/src/plugins/types.ts +46 -0
  214. package/src/post/filters.ts +888 -0
  215. package/src/renderables/ASCIIFont.ts +219 -0
  216. package/src/renderables/Box.test.ts +160 -0
  217. package/src/renderables/Box.ts +295 -0
  218. package/src/renderables/Code.test.ts +2062 -0
  219. package/src/renderables/Code.ts +357 -0
  220. package/src/renderables/Diff.regression.test.ts +226 -0
  221. package/src/renderables/Diff.test.ts +3027 -0
  222. package/src/renderables/Diff.ts +1209 -0
  223. package/src/renderables/EditBufferRenderable.ts +764 -0
  224. package/src/renderables/FrameBuffer.ts +47 -0
  225. package/src/renderables/Input.test.ts +1228 -0
  226. package/src/renderables/Input.ts +245 -0
  227. package/src/renderables/LineNumberRenderable.ts +675 -0
  228. package/src/renderables/Markdown.ts +1106 -0
  229. package/src/renderables/ScrollBar.ts +422 -0
  230. package/src/renderables/ScrollBox.ts +883 -0
  231. package/src/renderables/Select.test.ts +1010 -0
  232. package/src/renderables/Select.ts +523 -0
  233. package/src/renderables/Slider.test.ts +456 -0
  234. package/src/renderables/Slider.ts +347 -0
  235. package/src/renderables/TabSelect.test.ts +197 -0
  236. package/src/renderables/TabSelect.ts +455 -0
  237. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  238. package/src/renderables/Text.test.ts +2660 -0
  239. package/src/renderables/Text.ts +147 -0
  240. package/src/renderables/TextBufferRenderable.ts +518 -0
  241. package/src/renderables/TextNode.test.ts +1058 -0
  242. package/src/renderables/TextNode.ts +325 -0
  243. package/src/renderables/TextTable.test.ts +1421 -0
  244. package/src/renderables/TextTable.ts +1344 -0
  245. package/src/renderables/Textarea.ts +732 -0
  246. package/src/renderables/TimeToFirstDraw.ts +89 -0
  247. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  248. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  249. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  250. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  251. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  252. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  253. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1787 -0
  254. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  255. package/src/renderables/__tests__/Markdown.test.ts +2287 -0
  256. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  257. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  258. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  259. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  260. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  261. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  262. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  263. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  264. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  265. package/src/renderables/__tests__/Textarea.rendering.test.ts +1864 -0
  266. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  267. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  268. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  269. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  270. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  271. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  272. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  273. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  274. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  275. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  276. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  277. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  278. package/src/renderables/composition/README.md +8 -0
  279. package/src/renderables/composition/VRenderable.ts +32 -0
  280. package/src/renderables/composition/constructs.ts +127 -0
  281. package/src/renderables/composition/vnode.ts +289 -0
  282. package/src/renderables/index.ts +22 -0
  283. package/src/renderables/markdown-parser.ts +66 -0
  284. package/src/renderer.ts +2363 -0
  285. package/src/runtime-plugin-support.ts +39 -0
  286. package/src/runtime-plugin.ts +144 -0
  287. package/src/syntax-style.test.ts +841 -0
  288. package/src/syntax-style.ts +264 -0
  289. package/src/testing/README.md +210 -0
  290. package/src/testing/capture-spans.test.ts +194 -0
  291. package/src/testing/integration.test.ts +276 -0
  292. package/src/testing/manual-clock.ts +106 -0
  293. package/src/testing/mock-keys.test.ts +1356 -0
  294. package/src/testing/mock-keys.ts +449 -0
  295. package/src/testing/mock-mouse.test.ts +218 -0
  296. package/src/testing/mock-mouse.ts +247 -0
  297. package/src/testing/mock-tree-sitter-client.ts +73 -0
  298. package/src/testing/spy.ts +13 -0
  299. package/src/testing/test-recorder.test.ts +415 -0
  300. package/src/testing/test-recorder.ts +145 -0
  301. package/src/testing/test-renderer.ts +116 -0
  302. package/src/testing.ts +7 -0
  303. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  304. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  305. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  306. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  307. package/src/tests/allocator-stats.test.ts +38 -0
  308. package/src/tests/destroy-during-render.test.ts +200 -0
  309. package/src/tests/hover-cursor.test.ts +98 -0
  310. package/src/tests/native-span-feed-async.test.ts +173 -0
  311. package/src/tests/native-span-feed-close.test.ts +120 -0
  312. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  313. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  314. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  315. package/src/tests/opacity.test.ts +123 -0
  316. package/src/tests/renderable.snapshot.test.ts +524 -0
  317. package/src/tests/renderable.test.ts +1281 -0
  318. package/src/tests/renderer.console-startup.test.ts +65 -0
  319. package/src/tests/renderer.control.test.ts +364 -0
  320. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  321. package/src/tests/renderer.cursor.test.ts +26 -0
  322. package/src/tests/renderer.destroy-during-render.test.ts +110 -0
  323. package/src/tests/renderer.focus-restore.test.ts +228 -0
  324. package/src/tests/renderer.focus.test.ts +251 -0
  325. package/src/tests/renderer.idle.test.ts +219 -0
  326. package/src/tests/renderer.input.test.ts +2145 -0
  327. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  328. package/src/tests/renderer.mouse.test.ts +1269 -0
  329. package/src/tests/renderer.palette.test.ts +629 -0
  330. package/src/tests/renderer.selection.test.ts +49 -0
  331. package/src/tests/renderer.slot-registry.test.ts +649 -0
  332. package/src/tests/renderer.useMouse.test.ts +50 -0
  333. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  334. package/src/tests/runtime-plugin-support.test.ts +28 -0
  335. package/src/tests/runtime-plugin.fixture.ts +40 -0
  336. package/src/tests/runtime-plugin.test.ts +190 -0
  337. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  338. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  339. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  340. package/src/tests/scrollbox.test.ts +1530 -0
  341. package/src/tests/wrap-resize-perf.test.ts +229 -0
  342. package/src/tests/yoga-setters.test.ts +921 -0
  343. package/src/text-buffer-view.test.ts +705 -0
  344. package/src/text-buffer-view.ts +189 -0
  345. package/src/text-buffer.test.ts +347 -0
  346. package/src/text-buffer.ts +250 -0
  347. package/src/types.ts +152 -0
  348. package/src/utils.ts +88 -0
  349. package/src/zig/ansi.zig +268 -0
  350. package/src/zig/bench/README.md +50 -0
  351. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  352. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  353. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  354. package/src/zig/bench/rope-markers_bench.zig +713 -0
  355. package/src/zig/bench/rope_bench.zig +514 -0
  356. package/src/zig/bench/styled-text_bench.zig +470 -0
  357. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  358. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  359. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  360. package/src/zig/bench/utf8_bench.zig +799 -0
  361. package/src/zig/bench-utils.zig +431 -0
  362. package/src/zig/bench.zig +217 -0
  363. package/src/zig/buffer.zig +2223 -0
  364. package/src/zig/build.zig +289 -0
  365. package/src/zig/build.zig.zon +16 -0
  366. package/src/zig/edit-buffer.zig +825 -0
  367. package/src/zig/editor-view.zig +802 -0
  368. package/src/zig/event-bus.zig +13 -0
  369. package/src/zig/event-emitter.zig +65 -0
  370. package/src/zig/file-logger.zig +92 -0
  371. package/src/zig/grapheme.zig +599 -0
  372. package/src/zig/lib.zig +1834 -0
  373. package/src/zig/link.zig +333 -0
  374. package/src/zig/logger.zig +43 -0
  375. package/src/zig/mem-registry.zig +125 -0
  376. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  377. package/src/zig/native-span-feed.zig +708 -0
  378. package/src/zig/renderer.zig +1386 -0
  379. package/src/zig/rope.zig +1220 -0
  380. package/src/zig/syntax-style.zig +161 -0
  381. package/src/zig/terminal.zig +975 -0
  382. package/src/zig/test.zig +70 -0
  383. package/src/zig/tests/README.md +18 -0
  384. package/src/zig/tests/buffer_test.zig +2526 -0
  385. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  386. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  387. package/src/zig/tests/editor-view_test.zig +3299 -0
  388. package/src/zig/tests/event-emitter_test.zig +249 -0
  389. package/src/zig/tests/grapheme_test.zig +1304 -0
  390. package/src/zig/tests/link_test.zig +190 -0
  391. package/src/zig/tests/mem-registry_test.zig +473 -0
  392. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  393. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  394. package/src/zig/tests/renderer_test.zig +1010 -0
  395. package/src/zig/tests/rope-nested_test.zig +712 -0
  396. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  397. package/src/zig/tests/rope_test.zig +2362 -0
  398. package/src/zig/tests/segment-merge.test.zig +148 -0
  399. package/src/zig/tests/syntax-style_test.zig +557 -0
  400. package/src/zig/tests/terminal_test.zig +719 -0
  401. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  402. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  403. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  404. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  405. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  406. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  407. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  408. package/src/zig/tests/text-buffer_test.zig +2191 -0
  409. package/src/zig/tests/unicode-width-map.zon +3909 -0
  410. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  411. package/src/zig/tests/utf8_test.zig +4057 -0
  412. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  413. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  414. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  415. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  416. package/src/zig/text-buffer-iterators.zig +499 -0
  417. package/src/zig/text-buffer-segment.zig +404 -0
  418. package/src/zig/text-buffer-view.zig +1371 -0
  419. package/src/zig/text-buffer.zig +1180 -0
  420. package/src/zig/utf8.zig +1948 -0
  421. package/src/zig/utils.zig +9 -0
  422. package/src/zig-structs.ts +261 -0
  423. package/src/zig.ts +3843 -0
  424. package/tsconfig.build.json +22 -0
  425. package/tsconfig.json +28 -0
  426. package/3d/SpriteResourceManager.d.ts +0 -74
  427. package/3d/SpriteUtils.d.ts +0 -13
  428. package/3d/TextureUtils.d.ts +0 -24
  429. package/3d/ThreeRenderable.d.ts +0 -40
  430. package/3d/WGPURenderer.d.ts +0 -61
  431. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  432. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  433. package/3d/animation/SpriteAnimator.d.ts +0 -124
  434. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  435. package/3d/canvas.d.ts +0 -44
  436. package/3d/index.d.ts +0 -12
  437. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  438. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  439. package/3d/physics/physics-interface.d.ts +0 -27
  440. package/3d.d.ts +0 -2
  441. package/3d.js +0 -34042
  442. package/3d.js.map +0 -155
  443. package/LICENSE +0 -21
  444. package/NativeSpanFeed.d.ts +0 -41
  445. package/Renderable.d.ts +0 -334
  446. package/animation/Timeline.d.ts +0 -126
  447. package/ansi.d.ts +0 -13
  448. package/buffer.d.ts +0 -107
  449. package/console.d.ts +0 -143
  450. package/edit-buffer.d.ts +0 -98
  451. package/editor-view.d.ts +0 -73
  452. package/index-e4hzc2j2.js +0 -113
  453. package/index-e4hzc2j2.js.map +0 -10
  454. package/index-nkrr8a4c.js +0 -18415
  455. package/index-nkrr8a4c.js.map +0 -64
  456. package/index-nyw5p3ep.js +0 -12619
  457. package/index-nyw5p3ep.js.map +0 -43
  458. package/index.d.ts +0 -21
  459. package/index.js +0 -430
  460. package/index.js.map +0 -9
  461. package/lib/KeyHandler.d.ts +0 -61
  462. package/lib/RGBA.d.ts +0 -25
  463. package/lib/ascii.font.d.ts +0 -508
  464. package/lib/border.d.ts +0 -49
  465. package/lib/bunfs.d.ts +0 -7
  466. package/lib/clipboard.d.ts +0 -17
  467. package/lib/clock.d.ts +0 -15
  468. package/lib/data-paths.d.ts +0 -26
  469. package/lib/debounce.d.ts +0 -42
  470. package/lib/detect-links.d.ts +0 -6
  471. package/lib/env.d.ts +0 -42
  472. package/lib/extmarks-history.d.ts +0 -17
  473. package/lib/extmarks.d.ts +0 -89
  474. package/lib/hast-styled-text.d.ts +0 -17
  475. package/lib/index.d.ts +0 -21
  476. package/lib/keymapping.d.ts +0 -25
  477. package/lib/objects-in-viewport.d.ts +0 -24
  478. package/lib/output.capture.d.ts +0 -24
  479. package/lib/parse.keypress-kitty.d.ts +0 -2
  480. package/lib/parse.keypress.d.ts +0 -26
  481. package/lib/parse.mouse.d.ts +0 -30
  482. package/lib/paste.d.ts +0 -7
  483. package/lib/queue.d.ts +0 -15
  484. package/lib/renderable.validations.d.ts +0 -12
  485. package/lib/scroll-acceleration.d.ts +0 -43
  486. package/lib/selection.d.ts +0 -63
  487. package/lib/singleton.d.ts +0 -7
  488. package/lib/stdin-parser.d.ts +0 -76
  489. package/lib/styled-text.d.ts +0 -63
  490. package/lib/terminal-capability-detection.d.ts +0 -30
  491. package/lib/terminal-palette.d.ts +0 -50
  492. package/lib/tree-sitter/assets/update.d.ts +0 -11
  493. package/lib/tree-sitter/client.d.ts +0 -47
  494. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  495. package/lib/tree-sitter/download-utils.d.ts +0 -21
  496. package/lib/tree-sitter/index.d.ts +0 -8
  497. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  498. package/lib/tree-sitter/parsers-config.d.ts +0 -38
  499. package/lib/tree-sitter/resolve-ft.d.ts +0 -2
  500. package/lib/tree-sitter/types.d.ts +0 -81
  501. package/lib/tree-sitter-styled-text.d.ts +0 -14
  502. package/lib/validate-dir-name.d.ts +0 -1
  503. package/lib/yoga.options.d.ts +0 -32
  504. package/parser.worker.js +0 -869
  505. package/parser.worker.js.map +0 -12
  506. package/plugins/core-slot.d.ts +0 -72
  507. package/plugins/registry.d.ts +0 -38
  508. package/plugins/types.d.ts +0 -34
  509. package/post/filters.d.ts +0 -105
  510. package/renderables/ASCIIFont.d.ts +0 -52
  511. package/renderables/Box.d.ts +0 -72
  512. package/renderables/Code.d.ts +0 -78
  513. package/renderables/Diff.d.ts +0 -142
  514. package/renderables/EditBufferRenderable.d.ts +0 -162
  515. package/renderables/FrameBuffer.d.ts +0 -16
  516. package/renderables/Input.d.ts +0 -67
  517. package/renderables/LineNumberRenderable.d.ts +0 -74
  518. package/renderables/Markdown.d.ts +0 -173
  519. package/renderables/ScrollBar.d.ts +0 -77
  520. package/renderables/ScrollBox.d.ts +0 -124
  521. package/renderables/Select.d.ts +0 -115
  522. package/renderables/Slider.d.ts +0 -44
  523. package/renderables/TabSelect.d.ts +0 -96
  524. package/renderables/Text.d.ts +0 -36
  525. package/renderables/TextBufferRenderable.d.ts +0 -105
  526. package/renderables/TextNode.d.ts +0 -91
  527. package/renderables/TextTable.d.ts +0 -140
  528. package/renderables/Textarea.d.ts +0 -114
  529. package/renderables/TimeToFirstDraw.d.ts +0 -24
  530. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  531. package/renderables/composition/VRenderable.d.ts +0 -16
  532. package/renderables/composition/constructs.d.ts +0 -35
  533. package/renderables/composition/vnode.d.ts +0 -46
  534. package/renderables/index.d.ts +0 -22
  535. package/renderables/markdown-parser.d.ts +0 -10
  536. package/renderer.d.ts +0 -388
  537. package/runtime-plugin-support.d.ts +0 -3
  538. package/runtime-plugin-support.js +0 -29
  539. package/runtime-plugin-support.js.map +0 -10
  540. package/runtime-plugin.d.ts +0 -11
  541. package/runtime-plugin.js +0 -16
  542. package/runtime-plugin.js.map +0 -9
  543. package/syntax-style.d.ts +0 -54
  544. package/testing/manual-clock.d.ts +0 -16
  545. package/testing/mock-keys.d.ts +0 -81
  546. package/testing/mock-mouse.d.ts +0 -38
  547. package/testing/mock-tree-sitter-client.d.ts +0 -23
  548. package/testing/spy.d.ts +0 -7
  549. package/testing/test-recorder.d.ts +0 -61
  550. package/testing/test-renderer.d.ts +0 -23
  551. package/testing.d.ts +0 -6
  552. package/testing.js +0 -675
  553. package/testing.js.map +0 -15
  554. package/text-buffer-view.d.ts +0 -42
  555. package/text-buffer.d.ts +0 -67
  556. package/types.d.ts +0 -131
  557. package/utils.d.ts +0 -14
  558. package/zig-structs.d.ts +0 -155
  559. package/zig.d.ts +0 -351
  560. /package/{assets β†’ src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  561. /package/{assets β†’ src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  562. /package/{assets β†’ src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  563. /package/{assets β†’ src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  564. /package/{assets β†’ src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  565. /package/{assets β†’ src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  566. /package/{assets β†’ src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  567. /package/{assets β†’ src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  568. /package/{assets β†’ src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  569. /package/{assets β†’ src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  570. /package/{assets β†’ src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,1010 @@
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
+ }
363
+
364
+ test "renderer - empty text buffer renders correctly" {
365
+ const pool = gp.initGlobalPool(std.testing.allocator);
366
+ defer gp.deinitGlobalPool();
367
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
368
+ defer local_link_pool.deinit();
369
+
370
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
371
+ defer tb.deinit();
372
+
373
+ try tb.setText("");
374
+
375
+ var view = try TextBufferView.init(std.testing.allocator, tb);
376
+ defer view.deinit();
377
+
378
+ var cli_renderer = try CliRenderer.create(
379
+ std.testing.allocator,
380
+ 80,
381
+ 24,
382
+ pool,
383
+ true,
384
+ );
385
+ defer cli_renderer.destroy();
386
+
387
+ const next_buffer = cli_renderer.getNextBuffer();
388
+ try next_buffer.drawTextBuffer(view, 0, 0);
389
+ cli_renderer.render(false);
390
+ }
391
+
392
+ test "renderer - multiple renders update currentRenderBuffer" {
393
+ const pool = gp.initGlobalPool(std.testing.allocator);
394
+ defer gp.deinitGlobalPool();
395
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
396
+ defer local_link_pool.deinit();
397
+
398
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
399
+ defer tb.deinit();
400
+
401
+ var view = try TextBufferView.init(std.testing.allocator, tb);
402
+ defer view.deinit();
403
+
404
+ var cli_renderer = try CliRenderer.create(
405
+ std.testing.allocator,
406
+ 80,
407
+ 24,
408
+ pool,
409
+ true,
410
+ );
411
+ defer cli_renderer.destroy();
412
+
413
+ try tb.setText("Hello");
414
+ const next_buffer = cli_renderer.getNextBuffer();
415
+ try next_buffer.drawTextBuffer(view, 0, 0);
416
+ cli_renderer.render(false);
417
+
418
+ var current_buffer = cli_renderer.getCurrentBuffer();
419
+ var first_cell = current_buffer.get(0, 0);
420
+ try std.testing.expect(first_cell != null);
421
+ try std.testing.expectEqual(@as(u32, 'H'), first_cell.?.char);
422
+
423
+ try tb.setText("World");
424
+ const next_buffer2 = cli_renderer.getNextBuffer();
425
+ try next_buffer2.drawTextBuffer(view, 0, 0);
426
+ cli_renderer.render(false);
427
+
428
+ current_buffer = cli_renderer.getCurrentBuffer();
429
+ first_cell = current_buffer.get(0, 0);
430
+ try std.testing.expect(first_cell != null);
431
+ try std.testing.expectEqual(@as(u32, 'W'), first_cell.?.char);
432
+ }
433
+
434
+ test "renderer - 1000 frame render loop with setStyledText" {
435
+ const pool = gp.initGlobalPool(std.testing.allocator);
436
+ defer gp.deinitGlobalPool();
437
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
438
+ defer local_link_pool.deinit();
439
+
440
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
441
+ defer tb.deinit();
442
+
443
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
444
+ defer style.deinit();
445
+ tb.setSyntaxStyle(style);
446
+
447
+ var view = try TextBufferView.init(std.testing.allocator, tb);
448
+ defer view.deinit();
449
+
450
+ var cli_renderer = try CliRenderer.create(
451
+ std.testing.allocator,
452
+ 80,
453
+ 24,
454
+ pool,
455
+ true,
456
+ );
457
+ defer cli_renderer.destroy();
458
+
459
+ var opt_buffer = try OptimizedBuffer.init(
460
+ std.testing.allocator,
461
+ 80,
462
+ 24,
463
+ .{ .pool = pool, .width_method = .unicode },
464
+ );
465
+ defer opt_buffer.deinit();
466
+
467
+ const frame_texts = [_][]const u8{
468
+ "Frame ASCII",
469
+ "Frame πŸ‘‹ emoji",
470
+ "Frame δΈ–η•Œ CJK",
471
+ "Mixed πŸ˜€ δΈ–",
472
+ };
473
+
474
+ const fg_color = [4]f32{ 1.0, 0.8, 0.6, 1.0 };
475
+ const bg_color = [4]f32{ 0.1, 0.1, 0.2, 1.0 };
476
+
477
+ var frame: u32 = 0;
478
+ while (frame < 1000) : (frame += 1) {
479
+ const text_idx = frame % frame_texts.len;
480
+ const text = frame_texts[text_idx];
481
+
482
+ const chunks = [_]text_buffer.StyledChunk{.{
483
+ .text_ptr = text.ptr,
484
+ .text_len = text.len,
485
+ .fg_ptr = @ptrCast(&fg_color),
486
+ .bg_ptr = @ptrCast(&bg_color),
487
+ .attributes = 0,
488
+ }};
489
+
490
+ try tb.setStyledText(&chunks);
491
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
492
+ try opt_buffer.drawTextBuffer(view, 0, 0);
493
+
494
+ const next_buffer = cli_renderer.getNextBuffer();
495
+ try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
496
+ next_buffer.drawFrameBuffer(0, 0, opt_buffer, null, null, null, null);
497
+
498
+ cli_renderer.render(false);
499
+
500
+ if (frame % 100 == 0) {
501
+ const current_buffer = cli_renderer.getCurrentBuffer();
502
+ const first_cell = current_buffer.get(0, 0);
503
+ try std.testing.expect(first_cell != null);
504
+ try std.testing.expect(first_cell.?.char != 32);
505
+
506
+ try std.testing.expectEqual(frame + 1, cli_renderer.renderStats.frameCount);
507
+ }
508
+ }
509
+
510
+ try std.testing.expectEqual(@as(u64, 1000), cli_renderer.renderStats.frameCount);
511
+
512
+ const current_buffer = cli_renderer.getCurrentBuffer();
513
+ const final_cell = current_buffer.get(0, 0);
514
+ try std.testing.expect(final_cell != null);
515
+ try std.testing.expectEqual(@as(u32, 'M'), final_cell.?.char);
516
+ }
517
+
518
+ test "renderer - grapheme pool refcounting with frame buffer fast path" {
519
+ const limited_pool = gp.initGlobalPoolWithOptions(std.testing.allocator, .{
520
+ .slots_per_page = [_]u32{ 2, 2, 2, 2, 2 },
521
+ });
522
+ defer gp.deinitGlobalPool();
523
+
524
+ var tb = try TextBuffer.init(std.testing.allocator, limited_pool, link.initGlobalLinkPool(std.testing.allocator), .unicode);
525
+ defer tb.deinit();
526
+
527
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
528
+ defer style.deinit();
529
+ tb.setSyntaxStyle(style);
530
+
531
+ var view = try TextBufferView.init(std.testing.allocator, tb);
532
+ defer view.deinit();
533
+
534
+ var cli_renderer = try CliRenderer.create(
535
+ std.testing.allocator,
536
+ 80,
537
+ 24,
538
+ limited_pool,
539
+ true,
540
+ );
541
+ defer cli_renderer.destroy();
542
+
543
+ var frame_buffer = try OptimizedBuffer.init(
544
+ std.testing.allocator,
545
+ 80,
546
+ 24,
547
+ .{ .pool = limited_pool, .width_method = .unicode, .respectAlpha = false },
548
+ );
549
+ defer frame_buffer.deinit();
550
+
551
+ const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
552
+ const bg_color = [4]f32{ 0.0, 0.0, 0.0, 0.0 };
553
+
554
+ const text_with_emoji = "πŸ‘‹";
555
+ const chunks = [_]text_buffer.StyledChunk{.{
556
+ .text_ptr = text_with_emoji.ptr,
557
+ .text_len = text_with_emoji.len,
558
+ .fg_ptr = @ptrCast(&fg_color),
559
+ .bg_ptr = @ptrCast(&bg_color),
560
+ .attributes = 0,
561
+ }};
562
+ try tb.setStyledText(&chunks);
563
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
564
+ try frame_buffer.drawTextBuffer(view, 0, 0);
565
+
566
+ const next_buffer = cli_renderer.getNextBuffer();
567
+ next_buffer.setRespectAlpha(false);
568
+ try next_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
569
+
570
+ next_buffer.drawFrameBuffer(0, 0, frame_buffer, null, null, null, null);
571
+
572
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
573
+
574
+ var i: usize = 0;
575
+ while (i < 10) : (i += 1) {
576
+ const new_text = "πŸŽ‰πŸš€πŸ’―";
577
+ const new_chunks = [_]text_buffer.StyledChunk{.{
578
+ .text_ptr = new_text.ptr,
579
+ .text_len = new_text.len,
580
+ .fg_ptr = @ptrCast(&fg_color),
581
+ .bg_ptr = @ptrCast(&bg_color),
582
+ .attributes = 0,
583
+ }};
584
+ try tb.setStyledText(&new_chunks);
585
+ try frame_buffer.drawTextBuffer(view, 0, 0);
586
+ try frame_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
587
+ }
588
+
589
+ cli_renderer.render(false);
590
+
591
+ const current_buffer = cli_renderer.getCurrentBuffer();
592
+ const rendered_cell = current_buffer.get(0, 0);
593
+ try std.testing.expect(rendered_cell != null);
594
+ }
595
+
596
+ test "renderer - unchanged grapheme should not churn IDs across frames" {
597
+ const pool = gp.initGlobalPool(std.testing.allocator);
598
+ defer gp.deinitGlobalPool();
599
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
600
+ defer local_link_pool.deinit();
601
+
602
+ var cli_renderer = try CliRenderer.create(
603
+ std.testing.allocator,
604
+ 4,
605
+ 1,
606
+ pool,
607
+ true,
608
+ );
609
+ defer cli_renderer.destroy();
610
+
611
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
612
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
613
+
614
+ const first_next_buffer = cli_renderer.getNextBuffer();
615
+ try first_next_buffer.drawText("πŸ‘‹", 0, 0, fg, bg, 0);
616
+ cli_renderer.render(false);
617
+
618
+ const first_output = cli_renderer.getLastOutputForTest();
619
+ try std.testing.expect(std.mem.indexOf(u8, first_output, "πŸ‘‹") != null);
620
+
621
+ const current_buffer = cli_renderer.getCurrentBuffer();
622
+ const first_cell = current_buffer.get(0, 0);
623
+ try std.testing.expect(first_cell != null);
624
+ try std.testing.expect(gp.isGraphemeChar(first_cell.?.char));
625
+ const first_gid = gp.graphemeIdFromChar(first_cell.?.char);
626
+
627
+ const second_next_buffer = cli_renderer.getNextBuffer();
628
+ try second_next_buffer.drawText("πŸ‘‹", 0, 0, fg, bg, 0);
629
+
630
+ const second_cell = second_next_buffer.get(0, 0);
631
+ try std.testing.expect(second_cell != null);
632
+ try std.testing.expect(gp.isGraphemeChar(second_cell.?.char));
633
+ const second_gid = gp.graphemeIdFromChar(second_cell.?.char);
634
+
635
+ // Same grapheme content in consecutive frames should keep a stable ID,
636
+ // otherwise diff/write treats unchanged cells as modified every frame.
637
+ try std.testing.expectEqual(first_gid, second_gid);
638
+
639
+ cli_renderer.render(false);
640
+
641
+ const second_output = cli_renderer.getLastOutputForTest();
642
+ try std.testing.expect(std.mem.indexOf(u8, second_output, "πŸ‘‹") == null);
643
+ }
644
+
645
+ test "renderer - hyperlinks enabled with OSC 8 output" {
646
+ const pool = gp.initGlobalPool(std.testing.allocator);
647
+ defer gp.deinitGlobalPool();
648
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
649
+ defer link.deinitGlobalLinkPool();
650
+
651
+ var cli_renderer = try CliRenderer.create(
652
+ std.testing.allocator,
653
+ 80,
654
+ 24,
655
+ pool,
656
+ true,
657
+ );
658
+ defer cli_renderer.destroy();
659
+
660
+ // Enable hyperlinks capability
661
+ cli_renderer.terminal.caps.hyperlinks = true;
662
+
663
+ // Allocate a link
664
+ const link_id = try local_link_pool.alloc("https://example.com");
665
+ const attributes = ansi.TextAttributes.setLinkId(ansi.TextAttributes.BOLD, link_id);
666
+
667
+ const next_buffer = cli_renderer.getNextBuffer();
668
+
669
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
670
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
671
+ try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes);
672
+
673
+ cli_renderer.render(false);
674
+
675
+ const output = cli_renderer.getLastOutputForTest();
676
+
677
+ // Verify OSC 8 contains id parameter
678
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b]8;id=") != null);
679
+ // Verify OSC 8 contains the URL
680
+ try std.testing.expect(std.mem.indexOf(u8, output, ";https://example.com\x1b\\") != null);
681
+
682
+ // Verify output contains OSC 8 end sequence
683
+ const end_seq = "\x1b]8;;\x1b\\";
684
+ var count: usize = 0;
685
+ var pos: usize = 0;
686
+ while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| {
687
+ count += 1;
688
+ pos += found + end_seq.len;
689
+ }
690
+ try std.testing.expect(count >= 1);
691
+ }
692
+
693
+ test "renderer - hyperlinks disabled no OSC 8 output" {
694
+ const pool = gp.initGlobalPool(std.testing.allocator);
695
+ defer gp.deinitGlobalPool();
696
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
697
+ defer link.deinitGlobalLinkPool();
698
+
699
+ var cli_renderer = try CliRenderer.create(
700
+ std.testing.allocator,
701
+ 80,
702
+ 24,
703
+ pool,
704
+ true,
705
+ );
706
+ defer cli_renderer.destroy();
707
+
708
+ // Hyperlinks disabled by default
709
+ cli_renderer.terminal.caps.hyperlinks = false;
710
+
711
+ // Allocate a link
712
+ const link_id = try local_link_pool.alloc("https://example.com");
713
+ const attributes = ansi.TextAttributes.setLinkId(0, link_id);
714
+
715
+ const next_buffer = cli_renderer.getNextBuffer();
716
+
717
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
718
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
719
+ try next_buffer.drawText("Click here", 0, 0, fg, bg, attributes);
720
+
721
+ cli_renderer.render(false);
722
+
723
+ const output = cli_renderer.getLastOutputForTest();
724
+
725
+ // Verify output does NOT contain OSC 8 sequences
726
+ try std.testing.expect(std.mem.indexOf(u8, output, "]8;;") == null);
727
+ try std.testing.expect(std.mem.indexOf(u8, output, "]8;id=") == null);
728
+ }
729
+
730
+ test "renderer - link transition mid-line" {
731
+ const pool = gp.initGlobalPool(std.testing.allocator);
732
+ defer gp.deinitGlobalPool();
733
+ const local_link_pool = link.initGlobalLinkPool(std.testing.allocator);
734
+ defer link.deinitGlobalLinkPool();
735
+
736
+ var cli_renderer = try CliRenderer.create(
737
+ std.testing.allocator,
738
+ 80,
739
+ 24,
740
+ pool,
741
+ true,
742
+ );
743
+ defer cli_renderer.destroy();
744
+
745
+ // Enable hyperlinks
746
+ cli_renderer.terminal.caps.hyperlinks = true;
747
+
748
+ const next_buffer = cli_renderer.getNextBuffer();
749
+
750
+ // Allocate two different links
751
+ const link_id1 = try local_link_pool.alloc("https://first.com");
752
+ const link_id2 = try local_link_pool.alloc("https://second.com");
753
+
754
+ const attr1 = ansi.TextAttributes.setLinkId(0, link_id1);
755
+ const attr2 = ansi.TextAttributes.setLinkId(0, link_id2);
756
+
757
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
758
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
759
+
760
+ // Draw first link
761
+ try next_buffer.drawText("First", 0, 0, fg, bg, attr1);
762
+ // Draw second link
763
+ try next_buffer.drawText("Second", 6, 0, fg, bg, attr2);
764
+ // Draw no link
765
+ try next_buffer.drawText("Normal", 13, 0, fg, bg, 0);
766
+
767
+ cli_renderer.render(false);
768
+
769
+ const output = cli_renderer.getLastOutputForTest();
770
+
771
+ // Should contain both URLs
772
+ try std.testing.expect(std.mem.indexOf(u8, output, "https://first.com") != null);
773
+ try std.testing.expect(std.mem.indexOf(u8, output, "https://second.com") != null);
774
+
775
+ // Should have multiple OSC 8 end sequences (at least 2 transitions)
776
+ const end_seq = "\x1b]8;;\x1b\\";
777
+ var count: usize = 0;
778
+ var pos: usize = 0;
779
+ while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| {
780
+ count += 1;
781
+ pos += found + end_seq.len;
782
+ }
783
+ try std.testing.expect(count >= 2);
784
+ }
785
+
786
+ test "renderer - hyperlink spanning multiple rows uses same id" {
787
+ const pool = gp.initGlobalPool(std.testing.allocator);
788
+ defer gp.deinitGlobalPool();
789
+
790
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
791
+ defer link.deinitGlobalLinkPool();
792
+
793
+ var cli_renderer = try CliRenderer.create(
794
+ std.testing.allocator,
795
+ 80,
796
+ 24,
797
+ pool,
798
+ true,
799
+ );
800
+ defer cli_renderer.destroy();
801
+
802
+ // Enable hyperlinks
803
+ cli_renderer.terminal.caps.hyperlinks = true;
804
+
805
+ const next_buffer = cli_renderer.getNextBuffer();
806
+
807
+ // Allocate a single link
808
+ const link_id = try link_pool.alloc("https://example.com/long-url");
809
+ const attributes = ansi.TextAttributes.setLinkId(0, link_id);
810
+
811
+ const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 };
812
+ const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 };
813
+
814
+ // Fill entire row 0 with linked text so link is never interrupted by empty cells
815
+ try next_buffer.drawText("01234567890123456789012345678901234567890123456789012345678901234567890123456789", 0, 0, fg, bg, attributes);
816
+ // Continue the same link on row 1
817
+ try next_buffer.drawText("Here", 0, 1, fg, bg, attributes);
818
+
819
+ cli_renderer.render(false);
820
+
821
+ const output = cli_renderer.getLastOutputForTest();
822
+
823
+ // Build expected OSC 8 open sequence with the link id
824
+ var buf: [256]u8 = undefined;
825
+ const expected_open = std.fmt.bufPrint(&buf, "\x1b]8;id={d};https://example.com/long-url\x1b\\", .{link_id}) catch unreachable;
826
+ var count: usize = 0;
827
+ var pos: usize = 0;
828
+ while (std.mem.indexOf(u8, output[pos..], expected_open)) |found| {
829
+ count += 1;
830
+ pos += found + expected_open.len;
831
+ }
832
+ // Should appear exactly once: the link stays open across rows without
833
+ // close/reopen at row boundaries, so terminals treat it as one contiguous link.
834
+ try std.testing.expectEqual(@as(usize, 1), count);
835
+ }
836
+
837
+ // ============================================================================
838
+ // GRAPHEME CURSOR POSITIONING TESTS
839
+ // ============================================================================
840
+
841
+ test "renderer - default cursor style emits reset cursor ANSI" {
842
+ const pool = gp.initGlobalPool(std.testing.allocator);
843
+ defer gp.deinitGlobalPool();
844
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
845
+ defer local_link_pool.deinit();
846
+
847
+ var cli_renderer = try CliRenderer.create(
848
+ std.testing.allocator,
849
+ 80,
850
+ 24,
851
+ pool,
852
+ true,
853
+ );
854
+ defer cli_renderer.destroy();
855
+
856
+ cli_renderer.terminal.setCursorPosition(4, 2, true);
857
+ cli_renderer.render(false);
858
+
859
+ const output = cli_renderer.getLastOutputForTest();
860
+
861
+ try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.defaultCursorStyle) != null);
862
+ try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.cursorBlock) == null);
863
+ }
864
+
865
+ test "renderer - explicit_cursor_positioning emits cursor move after wide graphemes" {
866
+ const pool = gp.initGlobalPool(std.testing.allocator);
867
+ defer gp.deinitGlobalPool();
868
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
869
+ defer local_link_pool.deinit();
870
+
871
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
872
+ defer tb.deinit();
873
+
874
+ try tb.setText("πŸ‘‹X");
875
+
876
+ var view = try TextBufferView.init(std.testing.allocator, tb);
877
+ defer view.deinit();
878
+
879
+ var cli_renderer = try CliRenderer.create(
880
+ std.testing.allocator,
881
+ 80,
882
+ 24,
883
+ pool,
884
+ true,
885
+ );
886
+ defer cli_renderer.destroy();
887
+
888
+ cli_renderer.terminal.caps.explicit_cursor_positioning = true;
889
+ cli_renderer.terminal.caps.explicit_width = false;
890
+
891
+ const next_buffer = cli_renderer.getNextBuffer();
892
+ try next_buffer.drawTextBuffer(view, 0, 0);
893
+
894
+ cli_renderer.render(false);
895
+
896
+ const output = cli_renderer.getLastOutputForTest();
897
+
898
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null);
899
+ }
900
+
901
+ test "renderer - explicit_cursor_positioning produces more cursor moves" {
902
+ const pool = gp.initGlobalPool(std.testing.allocator);
903
+ defer gp.deinitGlobalPool();
904
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
905
+ defer local_link_pool.deinit();
906
+
907
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
908
+ defer tb.deinit();
909
+ try tb.setText("πŸ‘‹πŸŽ‰πŸš€");
910
+
911
+ var view = try TextBufferView.init(std.testing.allocator, tb);
912
+ defer view.deinit();
913
+
914
+ var cli_renderer1 = try CliRenderer.create(
915
+ std.testing.allocator,
916
+ 80,
917
+ 24,
918
+ pool,
919
+ true,
920
+ );
921
+ defer cli_renderer1.destroy();
922
+
923
+ cli_renderer1.terminal.caps.explicit_cursor_positioning = false;
924
+ cli_renderer1.terminal.caps.explicit_width = false;
925
+
926
+ const next_buffer1 = cli_renderer1.getNextBuffer();
927
+ try next_buffer1.drawTextBuffer(view, 0, 0);
928
+ cli_renderer1.render(false);
929
+ const output_without = cli_renderer1.getLastOutputForTest();
930
+
931
+ var cli_renderer2 = try CliRenderer.create(
932
+ std.testing.allocator,
933
+ 80,
934
+ 24,
935
+ pool,
936
+ true,
937
+ );
938
+ defer cli_renderer2.destroy();
939
+
940
+ cli_renderer2.terminal.caps.explicit_cursor_positioning = true;
941
+ cli_renderer2.terminal.caps.explicit_width = false;
942
+
943
+ const next_buffer2 = cli_renderer2.getNextBuffer();
944
+ try next_buffer2.drawTextBuffer(view, 0, 0);
945
+ cli_renderer2.render(false);
946
+ const output_with = cli_renderer2.getLastOutputForTest();
947
+
948
+ var count_without: usize = 0;
949
+ var count_with: usize = 0;
950
+
951
+ var i: usize = 0;
952
+ while (i + 3 < output_without.len) : (i += 1) {
953
+ if (output_without[i] == '\x1b' and output_without[i + 1] == '[') {
954
+ var j = i + 2;
955
+ while (j < output_without.len and ((output_without[j] >= '0' and output_without[j] <= '9') or output_without[j] == ';')) : (j += 1) {}
956
+ if (j < output_without.len and output_without[j] == 'H') {
957
+ count_without += 1;
958
+ }
959
+ }
960
+ }
961
+
962
+ i = 0;
963
+ while (i + 3 < output_with.len) : (i += 1) {
964
+ if (output_with[i] == '\x1b' and output_with[i + 1] == '[') {
965
+ var j = i + 2;
966
+ while (j < output_with.len and ((output_with[j] >= '0' and output_with[j] <= '9') or output_with[j] == ';')) : (j += 1) {}
967
+ if (j < output_with.len and output_with[j] == 'H') {
968
+ count_with += 1;
969
+ }
970
+ }
971
+ }
972
+
973
+ try std.testing.expect(count_with > count_without);
974
+ }
975
+
976
+ test "renderer - explicit_cursor_positioning with CJK characters" {
977
+ const pool = gp.initGlobalPool(std.testing.allocator);
978
+ defer gp.deinitGlobalPool();
979
+ var local_link_pool = link.LinkPool.init(std.testing.allocator);
980
+ defer local_link_pool.deinit();
981
+
982
+ var tb = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode);
983
+ defer tb.deinit();
984
+
985
+ try tb.setText("δΈ–X");
986
+
987
+ var view = try TextBufferView.init(std.testing.allocator, tb);
988
+ defer view.deinit();
989
+
990
+ var cli_renderer = try CliRenderer.create(
991
+ std.testing.allocator,
992
+ 80,
993
+ 24,
994
+ pool,
995
+ true,
996
+ );
997
+ defer cli_renderer.destroy();
998
+
999
+ cli_renderer.terminal.caps.explicit_cursor_positioning = true;
1000
+ cli_renderer.terminal.caps.explicit_width = false;
1001
+
1002
+ const next_buffer = cli_renderer.getNextBuffer();
1003
+ try next_buffer.drawTextBuffer(view, 0, 0);
1004
+
1005
+ cli_renderer.render(false);
1006
+
1007
+ const output = cli_renderer.getLastOutputForTest();
1008
+
1009
+ try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null);
1010
+ }