@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,3237 @@
1
+ const std = @import("std");
2
+ const text_buffer = @import("../text-buffer.zig");
3
+ const text_buffer_view = @import("../text-buffer-view.zig");
4
+ const buffer = @import("../buffer.zig");
5
+ const gp = @import("../grapheme.zig");
6
+ const link = @import("../link.zig");
7
+ const ss = @import("../syntax-style.zig");
8
+
9
+ const TextBuffer = text_buffer.TextBuffer;
10
+ const TextBufferView = text_buffer_view.TextBufferView;
11
+ const OptimizedBuffer = buffer.OptimizedBuffer;
12
+ const RGBA = text_buffer.RGBA;
13
+ const WrapMode = text_buffer.WrapMode;
14
+ const StyledChunk = text_buffer.StyledChunk;
15
+
16
+ test "drawTextBuffer - simple single line text" {
17
+ const pool = gp.initGlobalPool(std.testing.allocator);
18
+ defer gp.deinitGlobalPool();
19
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
20
+ defer link.deinitGlobalLinkPool();
21
+
22
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
23
+ defer tb.deinit();
24
+
25
+ var view = try TextBufferView.init(std.testing.allocator, tb);
26
+ defer view.deinit();
27
+
28
+ try tb.setText("Hello World");
29
+
30
+ var opt_buffer = try OptimizedBuffer.init(
31
+ std.testing.allocator,
32
+ 20,
33
+ 5,
34
+ .{ .pool = pool, .width_method = .unicode },
35
+ );
36
+ defer opt_buffer.deinit();
37
+
38
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
39
+ try opt_buffer.drawTextBuffer(view, 0, 0);
40
+
41
+ var out_buffer: [100]u8 = undefined;
42
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
43
+ const result = out_buffer[0..written];
44
+
45
+ try std.testing.expect(std.mem.startsWith(u8, result, "Hello World"));
46
+ }
47
+
48
+ test "drawTextBuffer - empty text buffer" {
49
+ const pool = gp.initGlobalPool(std.testing.allocator);
50
+ defer gp.deinitGlobalPool();
51
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
52
+ defer link.deinitGlobalLinkPool();
53
+
54
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
55
+ defer tb.deinit();
56
+
57
+ var view = try TextBufferView.init(std.testing.allocator, tb);
58
+ defer view.deinit();
59
+
60
+ try tb.setText("");
61
+
62
+ var opt_buffer = try OptimizedBuffer.init(
63
+ std.testing.allocator,
64
+ 20,
65
+ 5,
66
+ .{ .pool = pool, .width_method = .unicode },
67
+ );
68
+ defer opt_buffer.deinit();
69
+
70
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
71
+ try opt_buffer.drawTextBuffer(view, 0, 0);
72
+ }
73
+
74
+ test "drawTextBuffer - multiple lines without wrapping" {
75
+ const pool = gp.initGlobalPool(std.testing.allocator);
76
+ defer gp.deinitGlobalPool();
77
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
78
+ defer link.deinitGlobalLinkPool();
79
+
80
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
81
+ defer tb.deinit();
82
+
83
+ var view = try TextBufferView.init(std.testing.allocator, tb);
84
+ defer view.deinit();
85
+
86
+ try tb.setText("Line 1\nLine 2\nLine 3");
87
+
88
+ var opt_buffer = try OptimizedBuffer.init(
89
+ std.testing.allocator,
90
+ 20,
91
+ 10,
92
+ .{ .pool = pool, .width_method = .unicode },
93
+ );
94
+ defer opt_buffer.deinit();
95
+
96
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
97
+ try opt_buffer.drawTextBuffer(view, 0, 0);
98
+
99
+ const virtual_lines = view.getVirtualLines();
100
+ try std.testing.expect(virtual_lines.len == 3);
101
+ }
102
+
103
+ test "drawTextBuffer - text wrapping at word boundaries" {
104
+ const pool = gp.initGlobalPool(std.testing.allocator);
105
+ defer gp.deinitGlobalPool();
106
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
107
+ defer link.deinitGlobalLinkPool();
108
+
109
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
110
+ defer tb.deinit();
111
+
112
+ var view = try TextBufferView.init(std.testing.allocator, tb);
113
+ defer view.deinit();
114
+
115
+ try tb.setText("This is a long line that should wrap at word boundaries");
116
+ view.setWrapMode(.word);
117
+ view.setWrapWidth(15);
118
+ view.updateVirtualLines();
119
+
120
+ var opt_buffer = try OptimizedBuffer.init(
121
+ std.testing.allocator,
122
+ 15,
123
+ 10,
124
+ .{ .pool = pool, .width_method = .unicode },
125
+ );
126
+ defer opt_buffer.deinit();
127
+
128
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
129
+ try opt_buffer.drawTextBuffer(view, 0, 0);
130
+
131
+ const virtual_lines = view.getVirtualLines();
132
+ try std.testing.expect(virtual_lines.len > 1);
133
+ }
134
+
135
+ test "drawTextBuffer - text wrapping at character boundaries" {
136
+ const pool = gp.initGlobalPool(std.testing.allocator);
137
+ defer gp.deinitGlobalPool();
138
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
139
+ defer link.deinitGlobalLinkPool();
140
+
141
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
142
+ defer tb.deinit();
143
+
144
+ var view = try TextBufferView.init(std.testing.allocator, tb);
145
+ defer view.deinit();
146
+
147
+ try tb.setText("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
148
+ view.setWrapMode(.char);
149
+ view.setWrapWidth(10);
150
+ view.updateVirtualLines();
151
+
152
+ var opt_buffer = try OptimizedBuffer.init(
153
+ std.testing.allocator,
154
+ 10,
155
+ 10,
156
+ .{ .pool = pool, .width_method = .unicode },
157
+ );
158
+ defer opt_buffer.deinit();
159
+
160
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
161
+ try opt_buffer.drawTextBuffer(view, 0, 0);
162
+
163
+ const virtual_lines = view.getVirtualLines();
164
+ try std.testing.expect(virtual_lines.len == 4);
165
+ }
166
+
167
+ test "drawTextBuffer - no wrapping with none mode" {
168
+ const pool = gp.initGlobalPool(std.testing.allocator);
169
+ defer gp.deinitGlobalPool();
170
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
171
+ defer link.deinitGlobalLinkPool();
172
+
173
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
174
+ defer tb.deinit();
175
+
176
+ var view = try TextBufferView.init(std.testing.allocator, tb);
177
+ defer view.deinit();
178
+
179
+ try tb.setText("This is a very long line that extends beyond the buffer width");
180
+ view.setWrapMode(.word);
181
+ view.setWrapWidth(null);
182
+ view.updateVirtualLines();
183
+
184
+ var opt_buffer = try OptimizedBuffer.init(
185
+ std.testing.allocator,
186
+ 20,
187
+ 5,
188
+ .{ .pool = pool, .width_method = .unicode },
189
+ );
190
+ defer opt_buffer.deinit();
191
+
192
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
193
+ try opt_buffer.drawTextBuffer(view, 0, 0);
194
+
195
+ const virtual_lines = view.getVirtualLines();
196
+ try std.testing.expect(virtual_lines.len == 1);
197
+ }
198
+
199
+ test "drawTextBuffer - wrapped text with multiple lines" {
200
+ const pool = gp.initGlobalPool(std.testing.allocator);
201
+ defer gp.deinitGlobalPool();
202
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
203
+ defer link.deinitGlobalLinkPool();
204
+
205
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
206
+ defer tb.deinit();
207
+
208
+ var view = try TextBufferView.init(std.testing.allocator, tb);
209
+ defer view.deinit();
210
+
211
+ try tb.setText("First long line that wraps\nSecond long line that also wraps\nThird line");
212
+ view.setWrapMode(.word);
213
+ view.setWrapWidth(15);
214
+ view.updateVirtualLines();
215
+
216
+ var opt_buffer = try OptimizedBuffer.init(
217
+ std.testing.allocator,
218
+ 15,
219
+ 15,
220
+ .{ .pool = pool, .width_method = .unicode },
221
+ );
222
+ defer opt_buffer.deinit();
223
+
224
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
225
+ try opt_buffer.drawTextBuffer(view, 0, 0);
226
+
227
+ const virtual_lines = view.getVirtualLines();
228
+ try std.testing.expect(virtual_lines.len >= 3);
229
+ }
230
+
231
+ test "drawTextBuffer - unicode characters with wrapping" {
232
+ const pool = gp.initGlobalPool(std.testing.allocator);
233
+ defer gp.deinitGlobalPool();
234
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
235
+ defer link.deinitGlobalLinkPool();
236
+
237
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
238
+ defer tb.deinit();
239
+
240
+ var view = try TextBufferView.init(std.testing.allocator, tb);
241
+ defer view.deinit();
242
+
243
+ try tb.setText("Hello 世界 🌟 Test wrapping");
244
+ view.setWrapMode(.word);
245
+ view.setWrapWidth(15);
246
+ view.updateVirtualLines();
247
+
248
+ var opt_buffer = try OptimizedBuffer.init(
249
+ std.testing.allocator,
250
+ 15,
251
+ 10,
252
+ .{ .pool = pool, .width_method = .unicode },
253
+ );
254
+ defer opt_buffer.deinit();
255
+
256
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
257
+ try opt_buffer.drawTextBuffer(view, 0, 0);
258
+
259
+ const virtual_lines = view.getVirtualLines();
260
+ try std.testing.expect(virtual_lines.len > 0);
261
+ }
262
+
263
+ test "drawTextBuffer - wrapping preserves wide characters" {
264
+ const pool = gp.initGlobalPool(std.testing.allocator);
265
+ defer gp.deinitGlobalPool();
266
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
267
+ defer link.deinitGlobalLinkPool();
268
+
269
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
270
+ defer tb.deinit();
271
+
272
+ var view = try TextBufferView.init(std.testing.allocator, tb);
273
+ defer view.deinit();
274
+
275
+ try tb.setText("測試測試測試測試測試");
276
+ view.setWrapMode(.char);
277
+ view.setWrapWidth(10);
278
+ view.updateVirtualLines();
279
+
280
+ var opt_buffer = try OptimizedBuffer.init(
281
+ std.testing.allocator,
282
+ 10,
283
+ 10,
284
+ .{ .pool = pool, .width_method = .unicode },
285
+ );
286
+ defer opt_buffer.deinit();
287
+
288
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
289
+ try opt_buffer.drawTextBuffer(view, 0, 0);
290
+
291
+ const virtual_lines = view.getVirtualLines();
292
+ try std.testing.expect(virtual_lines.len > 1);
293
+ }
294
+
295
+ test "drawTextBuffer - word wrap does not split multi-byte UTF-8 characters" {
296
+ const pool = gp.initGlobalPool(std.testing.allocator);
297
+ defer gp.deinitGlobalPool();
298
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
299
+ defer link.deinitGlobalLinkPool();
300
+
301
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
302
+ defer tb.deinit();
303
+
304
+ var view = try TextBufferView.init(std.testing.allocator, tb);
305
+ defer view.deinit();
306
+
307
+ try tb.setText("🌟 Unicode test: こんにちは世界 Hello World 你好世界");
308
+ view.setWrapMode(.word);
309
+ view.setWrapWidth(35);
310
+ view.updateVirtualLines();
311
+
312
+ const vlines = view.getVirtualLines();
313
+
314
+ for (vlines) |vline| {
315
+ var line_buffer: [200]u8 = undefined;
316
+ const line_start_offset = vline.col_offset;
317
+ const line_end_offset = line_start_offset + vline.width_cols;
318
+ const extracted = tb.getTextRange(line_start_offset, line_end_offset, &line_buffer);
319
+
320
+ const is_valid_utf8 = std.unicode.utf8ValidateSlice(line_buffer[0..extracted]);
321
+ try std.testing.expect(is_valid_utf8);
322
+ }
323
+
324
+ try std.testing.expect(vlines.len == 2);
325
+
326
+ var full_buffer: [200]u8 = undefined;
327
+ const line0_len = tb.getTextRange(vlines[0].col_offset, vlines[0].col_offset + vlines[0].width_cols, &full_buffer);
328
+ const line0_text = full_buffer[0..line0_len];
329
+
330
+ const line1_len = tb.getTextRange(vlines[1].col_offset, vlines[1].col_offset + vlines[1].width_cols, &full_buffer);
331
+ const line1_text = full_buffer[0..line1_len];
332
+
333
+ const line0_ends_with_kai = std.mem.endsWith(u8, line0_text, "界");
334
+ const line1_starts_with_kai = std.mem.startsWith(u8, line1_text, "界");
335
+
336
+ try std.testing.expect(!(line0_ends_with_kai and line1_starts_with_kai));
337
+ }
338
+
339
+ test "drawTextBuffer - wrapped text with offset position" {
340
+ const pool = gp.initGlobalPool(std.testing.allocator);
341
+ defer gp.deinitGlobalPool();
342
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
343
+ defer link.deinitGlobalLinkPool();
344
+
345
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
346
+ defer tb.deinit();
347
+
348
+ var view = try TextBufferView.init(std.testing.allocator, tb);
349
+ defer view.deinit();
350
+
351
+ try tb.setText("Short line that wraps nicely");
352
+ view.setWrapMode(.word);
353
+ view.setWrapWidth(10);
354
+ view.updateVirtualLines();
355
+
356
+ var opt_buffer = try OptimizedBuffer.init(
357
+ std.testing.allocator,
358
+ 20,
359
+ 20,
360
+ .{ .pool = pool, .width_method = .unicode },
361
+ );
362
+ defer opt_buffer.deinit();
363
+
364
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
365
+ try opt_buffer.drawTextBuffer(view, 5, 5);
366
+
367
+ const cell = opt_buffer.get(5, 5);
368
+ try std.testing.expect(cell != null);
369
+ try std.testing.expect(cell.?.char != 32);
370
+ }
371
+
372
+ test "drawTextBuffer - clipping with scrolled view" {
373
+ const pool = gp.initGlobalPool(std.testing.allocator);
374
+ defer gp.deinitGlobalPool();
375
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
376
+ defer link.deinitGlobalLinkPool();
377
+
378
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
379
+ defer tb.deinit();
380
+
381
+ var view = try TextBufferView.init(std.testing.allocator, tb);
382
+ defer view.deinit();
383
+
384
+ try tb.setText("Line 1\nLine 2\nLine 3\nLine 4");
385
+
386
+ var opt_buffer = try OptimizedBuffer.init(
387
+ std.testing.allocator,
388
+ 20,
389
+ 5,
390
+ .{ .pool = pool, .width_method = .unicode },
391
+ );
392
+ defer opt_buffer.deinit();
393
+
394
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
395
+ try opt_buffer.drawTextBuffer(view, 0, 0);
396
+
397
+ const virtual_lines = view.getVirtualLines();
398
+ try std.testing.expect(virtual_lines.len >= 4);
399
+ }
400
+
401
+ test "drawTextBuffer - wrapping with very narrow width" {
402
+ const pool = gp.initGlobalPool(std.testing.allocator);
403
+ defer gp.deinitGlobalPool();
404
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
405
+ defer link.deinitGlobalLinkPool();
406
+
407
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
408
+ defer tb.deinit();
409
+
410
+ var view = try TextBufferView.init(std.testing.allocator, tb);
411
+ defer view.deinit();
412
+
413
+ try tb.setText("Hello");
414
+ view.setWrapMode(.char);
415
+ view.setWrapWidth(3);
416
+ view.updateVirtualLines();
417
+
418
+ var opt_buffer = try OptimizedBuffer.init(
419
+ std.testing.allocator,
420
+ 3,
421
+ 10,
422
+ .{ .pool = pool, .width_method = .unicode },
423
+ );
424
+ defer opt_buffer.deinit();
425
+
426
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
427
+ try opt_buffer.drawTextBuffer(view, 0, 0);
428
+
429
+ const virtual_lines = view.getVirtualLines();
430
+ try std.testing.expect(virtual_lines.len == 2);
431
+ }
432
+
433
+ test "drawTextBuffer - word wrap doesn't break mid-word" {
434
+ const pool = gp.initGlobalPool(std.testing.allocator);
435
+ defer gp.deinitGlobalPool();
436
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
437
+ defer link.deinitGlobalLinkPool();
438
+
439
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
440
+ defer tb.deinit();
441
+
442
+ var view = try TextBufferView.init(std.testing.allocator, tb);
443
+ defer view.deinit();
444
+
445
+ try tb.setText("Hello World");
446
+ view.setWrapMode(.word);
447
+ view.setWrapWidth(8);
448
+ view.updateVirtualLines();
449
+
450
+ var opt_buffer = try OptimizedBuffer.init(
451
+ std.testing.allocator,
452
+ 8,
453
+ 5,
454
+ .{ .pool = pool, .width_method = .unicode },
455
+ );
456
+ defer opt_buffer.deinit();
457
+
458
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
459
+ try opt_buffer.drawTextBuffer(view, 0, 0);
460
+
461
+ const virtual_lines = view.getVirtualLines();
462
+ try std.testing.expect(virtual_lines.len == 2);
463
+ }
464
+
465
+ test "drawTextBuffer - empty lines render correctly" {
466
+ const pool = gp.initGlobalPool(std.testing.allocator);
467
+ defer gp.deinitGlobalPool();
468
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
469
+ defer link.deinitGlobalLinkPool();
470
+
471
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
472
+ defer tb.deinit();
473
+
474
+ var view = try TextBufferView.init(std.testing.allocator, tb);
475
+ defer view.deinit();
476
+
477
+ try tb.setText("Line 1\n\nLine 3");
478
+
479
+ var opt_buffer = try OptimizedBuffer.init(
480
+ std.testing.allocator,
481
+ 20,
482
+ 10,
483
+ .{ .pool = pool, .width_method = .unicode },
484
+ );
485
+ defer opt_buffer.deinit();
486
+
487
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
488
+ try opt_buffer.drawTextBuffer(view, 0, 0);
489
+
490
+ const virtual_lines = view.getVirtualLines();
491
+ try std.testing.expect(virtual_lines.len == 3);
492
+ }
493
+
494
+ test "drawTextBuffer - wrapping with tabs" {
495
+ const pool = gp.initGlobalPool(std.testing.allocator);
496
+ defer gp.deinitGlobalPool();
497
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
498
+ defer link.deinitGlobalLinkPool();
499
+
500
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
501
+ defer tb.deinit();
502
+
503
+ var view = try TextBufferView.init(std.testing.allocator, tb);
504
+ defer view.deinit();
505
+
506
+ try tb.setText("Hello\tWorld\tTest");
507
+ view.setWrapMode(.word);
508
+ view.setWrapWidth(15);
509
+ view.updateVirtualLines();
510
+
511
+ var opt_buffer = try OptimizedBuffer.init(
512
+ std.testing.allocator,
513
+ 15,
514
+ 10,
515
+ .{ .pool = pool, .width_method = .unicode },
516
+ );
517
+ defer opt_buffer.deinit();
518
+
519
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
520
+ try opt_buffer.drawTextBuffer(view, 0, 0);
521
+ }
522
+
523
+ test "drawTextBuffer - very long unwrapped line clipping" {
524
+ const pool = gp.initGlobalPool(std.testing.allocator);
525
+ defer gp.deinitGlobalPool();
526
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
527
+ defer link.deinitGlobalLinkPool();
528
+
529
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
530
+ defer tb.deinit();
531
+
532
+ var view = try TextBufferView.init(std.testing.allocator, tb);
533
+ defer view.deinit();
534
+
535
+ var long_text: std.ArrayListUnmanaged(u8) = .{};
536
+ defer long_text.deinit(std.testing.allocator);
537
+ try long_text.appendNTimes(std.testing.allocator, 'A', 200);
538
+
539
+ try tb.setText(long_text.items);
540
+ view.setWrapMode(.word);
541
+ view.setWrapWidth(null);
542
+
543
+ var opt_buffer = try OptimizedBuffer.init(
544
+ std.testing.allocator,
545
+ 20,
546
+ 5,
547
+ .{ .pool = pool, .width_method = .unicode },
548
+ );
549
+ defer opt_buffer.deinit();
550
+
551
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
552
+ try opt_buffer.drawTextBuffer(view, 0, 0);
553
+
554
+ const virtual_lines = view.getVirtualLines();
555
+ try std.testing.expect(virtual_lines.len == 1);
556
+ }
557
+
558
+ test "drawTextBuffer - wrap mode transitions" {
559
+ const pool = gp.initGlobalPool(std.testing.allocator);
560
+ defer gp.deinitGlobalPool();
561
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
562
+ defer link.deinitGlobalLinkPool();
563
+
564
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
565
+ defer tb.deinit();
566
+
567
+ var view = try TextBufferView.init(std.testing.allocator, tb);
568
+ defer view.deinit();
569
+
570
+ try tb.setText("This is a test line for wrapping");
571
+
572
+ view.setWrapMode(.word);
573
+ view.setWrapWidth(null);
574
+ view.updateVirtualLines();
575
+ const no_wrap_lines = view.getVirtualLines().len;
576
+
577
+ view.setWrapMode(.char);
578
+ view.setWrapWidth(10);
579
+ view.updateVirtualLines();
580
+ const char_lines = view.getVirtualLines().len;
581
+
582
+ view.setWrapMode(.word);
583
+ view.setWrapWidth(10);
584
+ view.updateVirtualLines();
585
+ const word_lines = view.getVirtualLines().len;
586
+
587
+ try std.testing.expect(no_wrap_lines == 1);
588
+ try std.testing.expect(char_lines > 1);
589
+ try std.testing.expect(word_lines > 1);
590
+ }
591
+
592
+ test "drawTextBuffer - changing wrap width updates virtual lines" {
593
+ const pool = gp.initGlobalPool(std.testing.allocator);
594
+ defer gp.deinitGlobalPool();
595
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
596
+ defer link.deinitGlobalLinkPool();
597
+
598
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
599
+ defer tb.deinit();
600
+
601
+ var view = try TextBufferView.init(std.testing.allocator, tb);
602
+ defer view.deinit();
603
+
604
+ try tb.setText("AAAAAAAAAAAAAAAAAAAAAAAAAAAA");
605
+
606
+ view.setWrapMode(.char);
607
+ view.setWrapWidth(10);
608
+ view.updateVirtualLines();
609
+ const lines_10 = view.getVirtualLines().len;
610
+
611
+ view.setWrapWidth(20);
612
+ view.updateVirtualLines();
613
+ const lines_20 = view.getVirtualLines().len;
614
+
615
+ view.setWrapWidth(5);
616
+ view.updateVirtualLines();
617
+ const lines_5 = view.getVirtualLines().len;
618
+
619
+ try std.testing.expect(lines_10 > lines_20);
620
+ try std.testing.expect(lines_5 > lines_10);
621
+ }
622
+
623
+ test "drawTextBuffer - wrapping with mixed ASCII and Unicode" {
624
+ const pool = gp.initGlobalPool(std.testing.allocator);
625
+ defer gp.deinitGlobalPool();
626
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
627
+ defer link.deinitGlobalLinkPool();
628
+
629
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
630
+ defer tb.deinit();
631
+
632
+ var view = try TextBufferView.init(std.testing.allocator, tb);
633
+ defer view.deinit();
634
+
635
+ try tb.setText("ABC測試DEF試験GHI");
636
+ view.setWrapMode(.char);
637
+ view.setWrapWidth(10);
638
+ view.updateVirtualLines();
639
+
640
+ var opt_buffer = try OptimizedBuffer.init(
641
+ std.testing.allocator,
642
+ 10,
643
+ 10,
644
+ .{ .pool = pool, .width_method = .unicode },
645
+ );
646
+ defer opt_buffer.deinit();
647
+
648
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
649
+ try opt_buffer.drawTextBuffer(view, 0, 0);
650
+
651
+ const virtual_lines = view.getVirtualLines();
652
+ try std.testing.expect(virtual_lines.len > 1);
653
+ }
654
+
655
+ test "setStyledText - basic rendering with single chunk" {
656
+ const pool = gp.initGlobalPool(std.testing.allocator);
657
+ defer gp.deinitGlobalPool();
658
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
659
+ defer link.deinitGlobalLinkPool();
660
+
661
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
662
+ defer tb.deinit();
663
+
664
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
665
+ defer style.deinit();
666
+ tb.setSyntaxStyle(style);
667
+
668
+ const text = "Hello World";
669
+ const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
670
+
671
+ const chunks = [_]StyledChunk{.{
672
+ .text_ptr = text.ptr,
673
+ .text_len = text.len,
674
+ .fg_ptr = @ptrCast(&fg_color),
675
+ .bg_ptr = null,
676
+ .attributes = 0,
677
+ }};
678
+
679
+ try tb.setStyledText(&chunks);
680
+
681
+ var out_buffer: [100]u8 = undefined;
682
+ const written = tb.getPlainTextIntoBuffer(&out_buffer);
683
+ const result = out_buffer[0..written];
684
+
685
+ try std.testing.expectEqualStrings("Hello World", result);
686
+ }
687
+
688
+ test "setStyledText - multiple chunks render correctly" {
689
+ const pool = gp.initGlobalPool(std.testing.allocator);
690
+ defer gp.deinitGlobalPool();
691
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
692
+ defer link.deinitGlobalLinkPool();
693
+
694
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
695
+ defer tb.deinit();
696
+
697
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
698
+ defer style.deinit();
699
+ tb.setSyntaxStyle(style);
700
+
701
+ const text0 = "Hello ";
702
+ const text1 = "World";
703
+ const fg_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
704
+
705
+ const chunks = [_]StyledChunk{
706
+ .{ .text_ptr = text0.ptr, .text_len = text0.len, .fg_ptr = @ptrCast(&fg_color), .bg_ptr = null, .attributes = 0 },
707
+ .{ .text_ptr = text1.ptr, .text_len = text1.len, .fg_ptr = @ptrCast(&fg_color), .bg_ptr = null, .attributes = 0 },
708
+ };
709
+
710
+ try tb.setStyledText(&chunks);
711
+
712
+ var out_buffer: [100]u8 = undefined;
713
+ const written = tb.getPlainTextIntoBuffer(&out_buffer);
714
+ const result = out_buffer[0..written];
715
+
716
+ try std.testing.expectEqualStrings("Hello World", result);
717
+ }
718
+
719
+ // Viewport Tests
720
+
721
+ test "viewport - basic vertical scrolling limits returned lines" {
722
+ const pool = gp.initGlobalPool(std.testing.allocator);
723
+ defer gp.deinitGlobalPool();
724
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
725
+ defer link.deinitGlobalLinkPool();
726
+
727
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
728
+ defer tb.deinit();
729
+
730
+ var view = try TextBufferView.init(std.testing.allocator, tb);
731
+ defer view.deinit();
732
+
733
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9");
734
+
735
+ view.setViewport(.{ .x = 0, .y = 2, .width = 20, .height = 5 });
736
+
737
+ const visible_lines = view.getVirtualLines();
738
+
739
+ try std.testing.expectEqual(@as(usize, 5), visible_lines.len);
740
+ try std.testing.expectEqual(@as(usize, 2), visible_lines[0].source_line);
741
+ try std.testing.expectEqual(@as(usize, 6), visible_lines[4].source_line);
742
+ }
743
+
744
+ test "viewport - vertical scrolling at start boundary" {
745
+ const pool = gp.initGlobalPool(std.testing.allocator);
746
+ defer gp.deinitGlobalPool();
747
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
748
+ defer link.deinitGlobalLinkPool();
749
+
750
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
751
+ defer tb.deinit();
752
+
753
+ var view = try TextBufferView.init(std.testing.allocator, tb);
754
+ defer view.deinit();
755
+
756
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
757
+
758
+ view.setViewport(.{ .x = 0, .y = 0, .width = 20, .height = 3 });
759
+
760
+ const visible_lines = view.getVirtualLines();
761
+
762
+ try std.testing.expectEqual(@as(usize, 3), visible_lines.len);
763
+ try std.testing.expectEqual(@as(usize, 0), visible_lines[0].source_line);
764
+ try std.testing.expectEqual(@as(usize, 2), visible_lines[2].source_line);
765
+ }
766
+
767
+ test "viewport - vertical scrolling at end boundary" {
768
+ const pool = gp.initGlobalPool(std.testing.allocator);
769
+ defer gp.deinitGlobalPool();
770
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
771
+ defer link.deinitGlobalLinkPool();
772
+
773
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
774
+ defer tb.deinit();
775
+
776
+ var view = try TextBufferView.init(std.testing.allocator, tb);
777
+ defer view.deinit();
778
+
779
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
780
+
781
+ view.setViewport(.{ .x = 0, .y = 3, .width = 20, .height = 3 });
782
+
783
+ const visible_lines = view.getVirtualLines();
784
+
785
+ try std.testing.expectEqual(@as(usize, 2), visible_lines.len);
786
+ try std.testing.expectEqual(@as(usize, 3), visible_lines[0].source_line);
787
+ try std.testing.expectEqual(@as(usize, 4), visible_lines[1].source_line);
788
+ }
789
+
790
+ test "viewport - vertical scrolling beyond content" {
791
+ const pool = gp.initGlobalPool(std.testing.allocator);
792
+ defer gp.deinitGlobalPool();
793
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
794
+ defer link.deinitGlobalLinkPool();
795
+
796
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
797
+ defer tb.deinit();
798
+
799
+ var view = try TextBufferView.init(std.testing.allocator, tb);
800
+ defer view.deinit();
801
+
802
+ try tb.setText("Line 0\nLine 1\nLine 2");
803
+
804
+ view.setViewport(.{ .x = 0, .y = 10, .width = 20, .height = 5 });
805
+
806
+ const visible_lines = view.getVirtualLines();
807
+
808
+ try std.testing.expectEqual(@as(usize, 0), visible_lines.len);
809
+ }
810
+
811
+ test "viewport - with wrapping vertical scrolling" {
812
+ const pool = gp.initGlobalPool(std.testing.allocator);
813
+ defer gp.deinitGlobalPool();
814
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
815
+ defer link.deinitGlobalLinkPool();
816
+
817
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
818
+ defer tb.deinit();
819
+
820
+ var view = try TextBufferView.init(std.testing.allocator, tb);
821
+ defer view.deinit();
822
+
823
+ try tb.setText("This is a long line that will wrap\nShort\nAnother long line that wraps");
824
+
825
+ view.setWrapMode(.word);
826
+ view.setWrapWidth(15);
827
+ view.updateVirtualLines();
828
+
829
+ const total_vlines = view.getVirtualLineCount();
830
+ try std.testing.expect(total_vlines > 3);
831
+
832
+ view.setViewport(.{ .x = 0, .y = 2, .width = 15, .height = 3 });
833
+
834
+ const visible_lines = view.getVirtualLines();
835
+
836
+ try std.testing.expectEqual(@as(usize, 3), visible_lines.len);
837
+ }
838
+
839
+ test "viewport - getCachedLineInfo returns only viewport lines" {
840
+ const pool = gp.initGlobalPool(std.testing.allocator);
841
+ defer gp.deinitGlobalPool();
842
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
843
+ defer link.deinitGlobalLinkPool();
844
+
845
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
846
+ defer tb.deinit();
847
+
848
+ var view = try TextBufferView.init(std.testing.allocator, tb);
849
+ defer view.deinit();
850
+
851
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5");
852
+
853
+ view.setViewport(.{ .x = 0, .y = 1, .width = 20, .height = 3 });
854
+
855
+ const line_info = view.getCachedLineInfo();
856
+
857
+ try std.testing.expectEqual(@as(usize, 3), line_info.line_start_cols.len);
858
+ try std.testing.expectEqual(@as(usize, 3), line_info.line_width_cols.len);
859
+ }
860
+
861
+ test "viewport - changing viewport updates returned lines" {
862
+ const pool = gp.initGlobalPool(std.testing.allocator);
863
+ defer gp.deinitGlobalPool();
864
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
865
+ defer link.deinitGlobalLinkPool();
866
+
867
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
868
+ defer tb.deinit();
869
+
870
+ var view = try TextBufferView.init(std.testing.allocator, tb);
871
+ defer view.deinit();
872
+
873
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5");
874
+
875
+ view.setViewport(.{ .x = 0, .y = 0, .width = 20, .height = 2 });
876
+ const lines1 = view.getVirtualLines();
877
+ try std.testing.expectEqual(@as(usize, 2), lines1.len);
878
+ try std.testing.expectEqual(@as(usize, 0), lines1[0].source_line);
879
+
880
+ view.setViewport(.{ .x = 0, .y = 3, .width = 20, .height = 2 });
881
+ const lines2 = view.getVirtualLines();
882
+ try std.testing.expectEqual(@as(usize, 2), lines2.len);
883
+ try std.testing.expectEqual(@as(usize, 3), lines2[0].source_line);
884
+
885
+ view.setViewport(.{ .x = 0, .y = 1, .width = 20, .height = 4 });
886
+ const lines3 = view.getVirtualLines();
887
+ try std.testing.expectEqual(@as(usize, 4), lines3.len);
888
+ try std.testing.expectEqual(@as(usize, 1), lines3[0].source_line);
889
+ }
890
+
891
+ test "viewport - null viewport returns all lines" {
892
+ const pool = gp.initGlobalPool(std.testing.allocator);
893
+ defer gp.deinitGlobalPool();
894
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
895
+ defer link.deinitGlobalLinkPool();
896
+
897
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
898
+ defer tb.deinit();
899
+
900
+ var view = try TextBufferView.init(std.testing.allocator, tb);
901
+ defer view.deinit();
902
+
903
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
904
+
905
+ const all_lines = view.getVirtualLines();
906
+ try std.testing.expectEqual(@as(usize, 5), all_lines.len);
907
+
908
+ view.setViewport(.{ .x = 0, .y = 1, .width = 20, .height = 2 });
909
+ const viewport_lines = view.getVirtualLines();
910
+ try std.testing.expectEqual(@as(usize, 2), viewport_lines.len);
911
+
912
+ view.setViewport(null);
913
+ const all_lines_again = view.getVirtualLines();
914
+ try std.testing.expectEqual(@as(usize, 5), all_lines_again.len);
915
+ }
916
+
917
+ test "viewport - setViewportSize convenience method" {
918
+ const pool = gp.initGlobalPool(std.testing.allocator);
919
+ defer gp.deinitGlobalPool();
920
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
921
+ defer link.deinitGlobalLinkPool();
922
+
923
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
924
+ defer tb.deinit();
925
+
926
+ var view = try TextBufferView.init(std.testing.allocator, tb);
927
+ defer view.deinit();
928
+
929
+ try tb.setText("Line 0\nLine 1\nLine 2\nLine 3");
930
+
931
+ view.setViewportSize(20, 2);
932
+ const vp1 = view.getViewport().?;
933
+ try std.testing.expectEqual(@as(u32, 0), vp1.x);
934
+ try std.testing.expectEqual(@as(u32, 0), vp1.y);
935
+ try std.testing.expectEqual(@as(u32, 20), vp1.width);
936
+ try std.testing.expectEqual(@as(u32, 2), vp1.height);
937
+
938
+ view.setViewport(.{ .x = 5, .y = 1, .width = 20, .height = 2 });
939
+
940
+ view.setViewportSize(30, 3);
941
+ const vp2 = view.getViewport().?;
942
+ try std.testing.expectEqual(@as(u32, 5), vp2.x);
943
+ try std.testing.expectEqual(@as(u32, 1), vp2.y);
944
+ try std.testing.expectEqual(@as(u32, 30), vp2.width);
945
+ try std.testing.expectEqual(@as(u32, 3), vp2.height);
946
+ }
947
+
948
+ test "viewport - stores horizontal offset value with no wrapping" {
949
+ const pool = gp.initGlobalPool(std.testing.allocator);
950
+ defer gp.deinitGlobalPool();
951
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
952
+ defer link.deinitGlobalLinkPool();
953
+
954
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
955
+ defer tb.deinit();
956
+
957
+ var view = try TextBufferView.init(std.testing.allocator, tb);
958
+ defer view.deinit();
959
+
960
+ try tb.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
961
+
962
+ view.setWrapMode(.none);
963
+ view.setWrapWidth(null);
964
+
965
+ view.setViewport(.{ .x = 5, .y = 0, .width = 10, .height = 1 });
966
+
967
+ const vp = view.getViewport().?;
968
+ try std.testing.expectEqual(@as(u32, 5), vp.x);
969
+ try std.testing.expectEqual(@as(u32, 0), vp.y);
970
+ try std.testing.expectEqual(@as(u32, 10), vp.width);
971
+ try std.testing.expectEqual(@as(u32, 1), vp.height);
972
+
973
+ const lines = view.getVirtualLines();
974
+ try std.testing.expectEqual(@as(usize, 1), lines.len);
975
+ }
976
+
977
+ test "viewport - preserves horizontal offset when changing vertical (no wrap)" {
978
+ const pool = gp.initGlobalPool(std.testing.allocator);
979
+ defer gp.deinitGlobalPool();
980
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
981
+ defer link.deinitGlobalLinkPool();
982
+
983
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
984
+ defer tb.deinit();
985
+
986
+ var view = try TextBufferView.init(std.testing.allocator, tb);
987
+ defer view.deinit();
988
+
989
+ try tb.setText("ABCDEFGHIJ\nKLMNOPQRST\nUVWXYZ1234");
990
+
991
+ view.setWrapMode(.none);
992
+ view.setWrapWidth(null);
993
+
994
+ view.setViewport(.{ .x = 3, .y = 0, .width = 8, .height = 2 });
995
+
996
+ var vp = view.getViewport().?;
997
+ try std.testing.expectEqual(@as(u32, 3), vp.x);
998
+ try std.testing.expectEqual(@as(u32, 0), vp.y);
999
+
1000
+ view.setViewport(.{ .x = 3, .y = 1, .width = 8, .height = 2 });
1001
+
1002
+ vp = view.getViewport().?;
1003
+ try std.testing.expectEqual(@as(u32, 3), vp.x);
1004
+ try std.testing.expectEqual(@as(u32, 1), vp.y);
1005
+
1006
+ const visible_lines = view.getVirtualLines();
1007
+ try std.testing.expectEqual(@as(usize, 2), visible_lines.len);
1008
+ try std.testing.expectEqual(@as(usize, 1), visible_lines[0].source_line);
1009
+ }
1010
+
1011
+ test "viewport - can set large horizontal offset (no wrap)" {
1012
+ const pool = gp.initGlobalPool(std.testing.allocator);
1013
+ defer gp.deinitGlobalPool();
1014
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1015
+ defer link.deinitGlobalLinkPool();
1016
+
1017
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1018
+ defer tb.deinit();
1019
+
1020
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1021
+ defer view.deinit();
1022
+
1023
+ try tb.setText("Short\nLonger line here\nTiny");
1024
+
1025
+ view.setWrapMode(.none);
1026
+ view.setWrapWidth(null);
1027
+
1028
+ view.setViewport(.{ .x = 10, .y = 0, .width = 10, .height = 3 });
1029
+
1030
+ const vp = view.getViewport().?;
1031
+ try std.testing.expectEqual(@as(u32, 10), vp.x);
1032
+
1033
+ const visible_lines = view.getVirtualLines();
1034
+ try std.testing.expectEqual(@as(usize, 3), visible_lines.len);
1035
+ }
1036
+
1037
+ test "viewport - horizontal and vertical offset combined (no wrap)" {
1038
+ const pool = gp.initGlobalPool(std.testing.allocator);
1039
+ defer gp.deinitGlobalPool();
1040
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1041
+ defer link.deinitGlobalLinkPool();
1042
+
1043
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1044
+ defer tb.deinit();
1045
+
1046
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1047
+ defer view.deinit();
1048
+
1049
+ try tb.setText("Line 0: ABCDEFGHIJ\nLine 1: KLMNOPQRST\nLine 2: UVWXYZ1234\nLine 3: 567890ABCD");
1050
+
1051
+ view.setWrapMode(.none);
1052
+ view.setWrapWidth(null);
1053
+
1054
+ view.setViewport(.{ .x = 8, .y = 1, .width = 15, .height = 2 });
1055
+
1056
+ const vp = view.getViewport().?;
1057
+ try std.testing.expectEqual(@as(u32, 8), vp.x);
1058
+ try std.testing.expectEqual(@as(u32, 1), vp.y);
1059
+
1060
+ const visible_lines = view.getVirtualLines();
1061
+ try std.testing.expectEqual(@as(usize, 2), visible_lines.len);
1062
+ try std.testing.expectEqual(@as(usize, 1), visible_lines[0].source_line);
1063
+ try std.testing.expectEqual(@as(usize, 2), visible_lines[1].source_line);
1064
+ }
1065
+
1066
+ test "viewport - horizontal scrolling only for no-wrap mode" {
1067
+ const pool = gp.initGlobalPool(std.testing.allocator);
1068
+ defer gp.deinitGlobalPool();
1069
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1070
+ defer link.deinitGlobalLinkPool();
1071
+
1072
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1073
+ defer tb.deinit();
1074
+
1075
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1076
+ defer view.deinit();
1077
+
1078
+ const long_text = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1079
+ try tb.setText(long_text);
1080
+
1081
+ view.setWrapMode(.none);
1082
+ view.setWrapWidth(null);
1083
+ view.setViewport(.{ .x = 10, .y = 0, .width = 15, .height = 1 });
1084
+ view.updateVirtualLines();
1085
+
1086
+ var vp = view.getViewport().?;
1087
+ try std.testing.expectEqual(@as(u32, 10), vp.x);
1088
+
1089
+ var lines = view.getVirtualLines();
1090
+ try std.testing.expectEqual(@as(usize, 1), lines.len);
1091
+
1092
+ view.setWrapMode(.char);
1093
+ view.setViewport(.{ .x = 10, .y = 0, .width = 15, .height = 5 });
1094
+ view.updateVirtualLines();
1095
+
1096
+ vp = view.getViewport().?;
1097
+ try std.testing.expectEqual(@as(u32, 10), vp.x);
1098
+
1099
+ lines = view.getVirtualLines();
1100
+ try std.testing.expect(lines.len > 1);
1101
+ }
1102
+
1103
+ test "viewport - horizontal offset irrelevant with wrapping enabled" {
1104
+ const pool = gp.initGlobalPool(std.testing.allocator);
1105
+ defer gp.deinitGlobalPool();
1106
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1107
+ defer link.deinitGlobalLinkPool();
1108
+
1109
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1110
+ defer tb.deinit();
1111
+
1112
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1113
+ defer view.deinit();
1114
+
1115
+ try tb.setText("This is a very long line that will wrap into multiple virtual lines");
1116
+
1117
+ view.setWrapMode(.word);
1118
+ view.setWrapWidth(20);
1119
+ view.updateVirtualLines();
1120
+
1121
+ const total_vlines = view.getVirtualLineCount();
1122
+ try std.testing.expect(total_vlines > 1);
1123
+
1124
+ view.setViewport(.{ .x = 5, .y = 1, .width = 15, .height = 2 });
1125
+
1126
+ const vp = view.getViewport().?;
1127
+ try std.testing.expectEqual(@as(u32, 5), vp.x);
1128
+ try std.testing.expectEqual(@as(u32, 1), vp.y);
1129
+ try std.testing.expectEqual(@as(u32, 15), vp.width);
1130
+
1131
+ const visible_lines = view.getVirtualLines();
1132
+ try std.testing.expectEqual(@as(usize, 2), visible_lines.len);
1133
+ }
1134
+
1135
+ test "viewport - zero width or height" {
1136
+ const pool = gp.initGlobalPool(std.testing.allocator);
1137
+ defer gp.deinitGlobalPool();
1138
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1139
+ defer link.deinitGlobalLinkPool();
1140
+
1141
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1142
+ defer tb.deinit();
1143
+
1144
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1145
+ defer view.deinit();
1146
+
1147
+ try tb.setText("Line 0\nLine 1\nLine 2");
1148
+
1149
+ view.setViewport(.{ .x = 0, .y = 0, .width = 20, .height = 0 });
1150
+ const lines1 = view.getVirtualLines();
1151
+ try std.testing.expectEqual(@as(usize, 0), lines1.len);
1152
+
1153
+ view.setViewport(.{ .x = 0, .y = 0, .width = 0, .height = 2 });
1154
+ const lines2 = view.getVirtualLines();
1155
+ try std.testing.expectEqual(@as(usize, 2), lines2.len);
1156
+ }
1157
+
1158
+ test "viewport - viewport sets wrap width automatically" {
1159
+ const pool = gp.initGlobalPool(std.testing.allocator);
1160
+ defer gp.deinitGlobalPool();
1161
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1162
+ defer link.deinitGlobalLinkPool();
1163
+
1164
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1165
+ defer tb.deinit();
1166
+
1167
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1168
+ defer view.deinit();
1169
+
1170
+ try tb.setText("AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDD");
1171
+
1172
+ view.setWrapMode(.char);
1173
+
1174
+ view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 5 });
1175
+ view.updateVirtualLines();
1176
+
1177
+ const vline_count_10 = view.getVirtualLineCount();
1178
+
1179
+ view.setViewport(.{ .x = 0, .y = 0, .width = 20, .height = 5 });
1180
+ view.updateVirtualLines();
1181
+
1182
+ const vline_count_20 = view.getVirtualLineCount();
1183
+
1184
+ try std.testing.expect(vline_count_10 > vline_count_20);
1185
+ }
1186
+
1187
+ test "viewport - moving viewport dynamically (no wrap)" {
1188
+ const pool = gp.initGlobalPool(std.testing.allocator);
1189
+ defer gp.deinitGlobalPool();
1190
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1191
+ defer link.deinitGlobalLinkPool();
1192
+
1193
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1194
+ defer tb.deinit();
1195
+
1196
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1197
+ defer view.deinit();
1198
+
1199
+ try tb.setText("0123456789\nABCDEFGHIJ\nKLMNOPQRST\nUVWXYZ!@#$");
1200
+
1201
+ view.setWrapMode(.none);
1202
+ view.setWrapWidth(null);
1203
+
1204
+ view.setViewport(.{ .x = 0, .y = 0, .width = 5, .height = 2 });
1205
+ var vp = view.getViewport().?;
1206
+ try std.testing.expectEqual(@as(u32, 0), vp.x);
1207
+ try std.testing.expectEqual(@as(u32, 0), vp.y);
1208
+ const lines1 = view.getVirtualLines();
1209
+ try std.testing.expectEqual(@as(usize, 2), lines1.len);
1210
+ try std.testing.expectEqual(@as(usize, 0), lines1[0].source_line);
1211
+
1212
+ view.setViewport(.{ .x = 0, .y = 1, .width = 5, .height = 2 });
1213
+ vp = view.getViewport().?;
1214
+ try std.testing.expectEqual(@as(u32, 0), vp.x);
1215
+ try std.testing.expectEqual(@as(u32, 1), vp.y);
1216
+ const lines2 = view.getVirtualLines();
1217
+ try std.testing.expectEqual(@as(usize, 2), lines2.len);
1218
+ try std.testing.expectEqual(@as(usize, 1), lines2[0].source_line);
1219
+
1220
+ view.setViewport(.{ .x = 3, .y = 1, .width = 5, .height = 2 });
1221
+ vp = view.getViewport().?;
1222
+ try std.testing.expectEqual(@as(u32, 3), vp.x);
1223
+ try std.testing.expectEqual(@as(u32, 1), vp.y);
1224
+ const lines3 = view.getVirtualLines();
1225
+ try std.testing.expectEqual(@as(usize, 2), lines3.len);
1226
+
1227
+ view.setViewport(.{ .x = 5, .y = 2, .width = 5, .height = 2 });
1228
+ vp = view.getViewport().?;
1229
+ try std.testing.expectEqual(@as(u32, 5), vp.x);
1230
+ try std.testing.expectEqual(@as(u32, 2), vp.y);
1231
+ const lines4 = view.getVirtualLines();
1232
+ try std.testing.expectEqual(@as(usize, 2), lines4.len);
1233
+ try std.testing.expectEqual(@as(usize, 2), lines4[0].source_line);
1234
+ }
1235
+
1236
+ test "loadFile - loads and renders file correctly" {
1237
+ const pool = gp.initGlobalPool(std.testing.allocator);
1238
+ defer gp.deinitGlobalPool();
1239
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1240
+ defer link.deinitGlobalLinkPool();
1241
+
1242
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1243
+ defer tb.deinit();
1244
+
1245
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1246
+ defer view.deinit();
1247
+
1248
+ const test_content = "ABC\nDEF";
1249
+ const tmpdir = std.testing.tmpDir(.{});
1250
+ var tmp = tmpdir;
1251
+ defer tmp.cleanup();
1252
+
1253
+ const file = try tmp.dir.createFile("test.txt", .{});
1254
+ try file.writeAll(test_content);
1255
+ file.close();
1256
+
1257
+ const dir_path = try tmp.dir.realpathAlloc(std.testing.allocator, ".");
1258
+ defer std.testing.allocator.free(dir_path);
1259
+
1260
+ const file_path = try std.fs.path.join(std.testing.allocator, &[_][]const u8{ dir_path, "test.txt" });
1261
+ defer std.testing.allocator.free(file_path);
1262
+
1263
+ try tb.loadFile(file_path);
1264
+
1265
+ const line_count = tb.getLineCount();
1266
+ try std.testing.expectEqual(@as(u32, 2), line_count);
1267
+
1268
+ const char_count = tb.getLength();
1269
+ try std.testing.expectEqual(@as(u32, 6), char_count);
1270
+
1271
+ var opt_buffer = try OptimizedBuffer.init(
1272
+ std.testing.allocator,
1273
+ 20,
1274
+ 5,
1275
+ .{ .pool = pool, .width_method = .unicode },
1276
+ );
1277
+ defer opt_buffer.deinit();
1278
+
1279
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1280
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1281
+
1282
+ var render_buffer: [200]u8 = undefined;
1283
+ const render_written = try opt_buffer.writeResolvedChars(&render_buffer, false);
1284
+ const render_result = render_buffer[0..render_written];
1285
+
1286
+ try std.testing.expect(std.mem.startsWith(u8, render_result, "ABC"));
1287
+ }
1288
+
1289
+ test "drawTextBuffer - horizontal viewport offset renders correctly without wrapping" {
1290
+ const pool = gp.initGlobalPool(std.testing.allocator);
1291
+ defer gp.deinitGlobalPool();
1292
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1293
+ defer link.deinitGlobalLinkPool();
1294
+
1295
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1296
+ defer tb.deinit();
1297
+
1298
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1299
+ defer view.deinit();
1300
+
1301
+ try tb.setText("0123456789ABCDEFGHIJ");
1302
+
1303
+ view.setWrapMode(.none);
1304
+ view.setWrapWidth(null);
1305
+ view.setViewport(.{ .x = 5, .y = 0, .width = 10, .height = 1 });
1306
+
1307
+ var opt_buffer = try OptimizedBuffer.init(
1308
+ std.testing.allocator,
1309
+ 10,
1310
+ 1,
1311
+ .{ .pool = pool, .width_method = .unicode },
1312
+ );
1313
+ defer opt_buffer.deinit();
1314
+
1315
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1316
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1317
+
1318
+ var out_buffer: [100]u8 = undefined;
1319
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1320
+ const result = out_buffer[0..written];
1321
+
1322
+ try std.testing.expect(std.mem.startsWith(u8, result, "56789ABCDE"));
1323
+ }
1324
+
1325
+ test "drawTextBuffer - horizontal viewport offset with multiple lines" {
1326
+ const pool = gp.initGlobalPool(std.testing.allocator);
1327
+ defer gp.deinitGlobalPool();
1328
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1329
+ defer link.deinitGlobalLinkPool();
1330
+
1331
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1332
+ defer tb.deinit();
1333
+
1334
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1335
+ defer view.deinit();
1336
+
1337
+ try tb.setText("ABCDEFGHIJKLMNO\n0123456789!@#$%\nXYZ[\\]^_`{|}~");
1338
+
1339
+ view.setWrapMode(.none);
1340
+ view.setWrapWidth(null);
1341
+ view.setViewport(.{ .x = 3, .y = 0, .width = 8, .height = 3 });
1342
+
1343
+ var opt_buffer = try OptimizedBuffer.init(
1344
+ std.testing.allocator,
1345
+ 8,
1346
+ 3,
1347
+ .{ .pool = pool, .width_method = .unicode },
1348
+ );
1349
+ defer opt_buffer.deinit();
1350
+
1351
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1352
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1353
+
1354
+ var out_buffer: [100]u8 = undefined;
1355
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1356
+ const result = out_buffer[0..written];
1357
+
1358
+ try std.testing.expect(std.mem.indexOf(u8, result, "DEFGHIJK") != null);
1359
+ try std.testing.expect(std.mem.indexOf(u8, result, "3456789!") != null);
1360
+ try std.testing.expect(std.mem.indexOf(u8, result, "[\\]^_`{|") != null);
1361
+ }
1362
+
1363
+ test "drawTextBuffer - combined horizontal and vertical viewport offsets" {
1364
+ const pool = gp.initGlobalPool(std.testing.allocator);
1365
+ defer gp.deinitGlobalPool();
1366
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1367
+ defer link.deinitGlobalLinkPool();
1368
+
1369
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1370
+ defer tb.deinit();
1371
+
1372
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1373
+ defer view.deinit();
1374
+
1375
+ try tb.setText("Line0ABCDEFGHIJ\nLine1KLMNOPQRST\nLine2UVWXYZ0123\nLine3456789!@#$");
1376
+
1377
+ view.setWrapMode(.none);
1378
+ view.setWrapWidth(null);
1379
+ view.setViewport(.{ .x = 5, .y = 1, .width = 10, .height = 2 });
1380
+
1381
+ var opt_buffer = try OptimizedBuffer.init(
1382
+ std.testing.allocator,
1383
+ 10,
1384
+ 2,
1385
+ .{ .pool = pool, .width_method = .unicode },
1386
+ );
1387
+ defer opt_buffer.deinit();
1388
+
1389
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1390
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1391
+
1392
+ var out_buffer: [100]u8 = undefined;
1393
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1394
+ const result = out_buffer[0..written];
1395
+
1396
+ try std.testing.expect(std.mem.indexOf(u8, result, "KLMNOPQRST") != null);
1397
+ try std.testing.expect(std.mem.indexOf(u8, result, "UVWXYZ0123") != null);
1398
+ }
1399
+
1400
+ test "drawTextBuffer - horizontal viewport stops rendering at viewport width" {
1401
+ const pool = gp.initGlobalPool(std.testing.allocator);
1402
+ defer gp.deinitGlobalPool();
1403
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1404
+ defer link.deinitGlobalLinkPool();
1405
+
1406
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1407
+ defer tb.deinit();
1408
+
1409
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1410
+ defer view.deinit();
1411
+
1412
+ try tb.setText("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1413
+
1414
+ view.setWrapMode(.none);
1415
+ view.setWrapWidth(null);
1416
+ view.setViewport(.{ .x = 5, .y = 0, .width = 10, .height = 1 });
1417
+
1418
+ var opt_buffer = try OptimizedBuffer.init(
1419
+ std.testing.allocator,
1420
+ 10,
1421
+ 1,
1422
+ .{ .pool = pool, .width_method = .unicode },
1423
+ );
1424
+ defer opt_buffer.deinit();
1425
+
1426
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1427
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1428
+
1429
+ var out_buffer: [100]u8 = undefined;
1430
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1431
+ const result = out_buffer[0..written];
1432
+
1433
+ try std.testing.expectEqualStrings("56789ABCDE", result[0..10]);
1434
+
1435
+ const cell_9 = opt_buffer.get(9, 0);
1436
+ try std.testing.expect(cell_9 != null);
1437
+ try std.testing.expectEqual(@as(u32, 'E'), cell_9.?.char);
1438
+ }
1439
+
1440
+ test "drawTextBuffer - horizontal viewport with small buffer renders only viewport width" {
1441
+ const pool = gp.initGlobalPool(std.testing.allocator);
1442
+ defer gp.deinitGlobalPool();
1443
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1444
+ defer link.deinitGlobalLinkPool();
1445
+
1446
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1447
+ defer tb.deinit();
1448
+
1449
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1450
+ defer view.deinit();
1451
+
1452
+ try tb.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
1453
+
1454
+ view.setWrapMode(.none);
1455
+ view.setWrapWidth(null);
1456
+ view.setViewport(.{ .x = 10, .y = 0, .width = 5, .height = 1 });
1457
+
1458
+ var opt_buffer = try OptimizedBuffer.init(
1459
+ std.testing.allocator,
1460
+ 20,
1461
+ 1,
1462
+ .{ .pool = pool, .width_method = .unicode },
1463
+ );
1464
+ defer opt_buffer.deinit();
1465
+
1466
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1467
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1468
+
1469
+ const cell_0 = opt_buffer.get(0, 0);
1470
+ try std.testing.expect(cell_0 != null);
1471
+ try std.testing.expectEqual(@as(u32, 'K'), cell_0.?.char);
1472
+
1473
+ const cell_4 = opt_buffer.get(4, 0);
1474
+ try std.testing.expect(cell_4 != null);
1475
+ try std.testing.expectEqual(@as(u32, 'O'), cell_4.?.char);
1476
+
1477
+ const cell_5 = opt_buffer.get(5, 0);
1478
+ try std.testing.expect(cell_5 != null);
1479
+ try std.testing.expectEqual(@as(u32, 32), cell_5.?.char);
1480
+
1481
+ const cell_6 = opt_buffer.get(6, 0);
1482
+ try std.testing.expect(cell_6 != null);
1483
+ try std.testing.expectEqual(@as(u32, 32), cell_6.?.char);
1484
+ }
1485
+
1486
+ test "drawTextBuffer - horizontal viewport width limits rendering (efficiency test)" {
1487
+ const pool = gp.initGlobalPool(std.testing.allocator);
1488
+ defer gp.deinitGlobalPool();
1489
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1490
+ defer link.deinitGlobalLinkPool();
1491
+
1492
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1493
+ defer tb.deinit();
1494
+
1495
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1496
+ defer view.deinit();
1497
+
1498
+ var long_line: std.ArrayListUnmanaged(u8) = .{};
1499
+ defer long_line.deinit(std.testing.allocator);
1500
+ try long_line.appendNTimes(std.testing.allocator, 'A', 1000);
1501
+
1502
+ try tb.setText(long_line.items);
1503
+
1504
+ view.setWrapMode(.none);
1505
+ view.setWrapWidth(null);
1506
+ view.setViewport(.{ .x = 100, .y = 0, .width = 10, .height = 1 });
1507
+
1508
+ var opt_buffer = try OptimizedBuffer.init(
1509
+ std.testing.allocator,
1510
+ 50,
1511
+ 1,
1512
+ .{ .pool = pool, .width_method = .unicode },
1513
+ );
1514
+ defer opt_buffer.deinit();
1515
+
1516
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1517
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1518
+
1519
+ var non_space_count: u32 = 0;
1520
+ var i: u32 = 0;
1521
+ while (i < 50) : (i += 1) {
1522
+ if (opt_buffer.get(i, 0)) |cell| {
1523
+ if (cell.char == 'A') {
1524
+ non_space_count += 1;
1525
+ }
1526
+ }
1527
+ }
1528
+
1529
+ try std.testing.expectEqual(@as(u32, 10), non_space_count);
1530
+ }
1531
+
1532
+ test "drawTextBuffer - overwriting wide grapheme with ASCII leaves no ghost chars" {
1533
+ const pool = gp.initGlobalPool(std.testing.allocator);
1534
+ defer gp.deinitGlobalPool();
1535
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1536
+ defer link.deinitGlobalLinkPool();
1537
+
1538
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1539
+ defer tb.deinit();
1540
+
1541
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1542
+ defer view.deinit();
1543
+
1544
+ var opt_buffer = try OptimizedBuffer.init(
1545
+ std.testing.allocator,
1546
+ 20,
1547
+ 5,
1548
+ .{ .pool = pool, .width_method = .unicode },
1549
+ );
1550
+ defer opt_buffer.deinit();
1551
+
1552
+ try tb.setText("世界");
1553
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1554
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1555
+
1556
+ const first_cell = opt_buffer.get(0, 0) orelse unreachable;
1557
+ try std.testing.expect(gp.isGraphemeChar(first_cell.char));
1558
+ try std.testing.expectEqual(@as(u32, 2), gp.encodedCharWidth(first_cell.char));
1559
+
1560
+ const second_cell = opt_buffer.get(1, 0) orelse unreachable;
1561
+ try std.testing.expect(gp.isContinuationChar(second_cell.char));
1562
+
1563
+ try tb.setText("ABC");
1564
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1565
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1566
+
1567
+ const cell_a = opt_buffer.get(0, 0) orelse unreachable;
1568
+ try std.testing.expectEqual(@as(u32, 'A'), cell_a.char);
1569
+ try std.testing.expect(!gp.isGraphemeChar(cell_a.char));
1570
+ try std.testing.expect(!gp.isContinuationChar(cell_a.char));
1571
+
1572
+ const cell_b = opt_buffer.get(1, 0) orelse unreachable;
1573
+ try std.testing.expectEqual(@as(u32, 'B'), cell_b.char);
1574
+ try std.testing.expect(!gp.isGraphemeChar(cell_b.char));
1575
+ try std.testing.expect(!gp.isContinuationChar(cell_b.char));
1576
+
1577
+ const cell_c = opt_buffer.get(2, 0) orelse unreachable;
1578
+ try std.testing.expectEqual(@as(u32, 'C'), cell_c.char);
1579
+ try std.testing.expect(!gp.isGraphemeChar(cell_c.char));
1580
+ try std.testing.expect(!gp.isContinuationChar(cell_c.char));
1581
+
1582
+ var out_buffer: [100]u8 = undefined;
1583
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1584
+ const result = out_buffer[0..written];
1585
+ try std.testing.expect(std.mem.startsWith(u8, result, "ABC"));
1586
+ }
1587
+
1588
+ test "drawTextBuffer - syntax style destroy does not crash" {
1589
+ const pool = gp.initGlobalPool(std.testing.allocator);
1590
+ defer gp.deinitGlobalPool();
1591
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1592
+ defer link.deinitGlobalLinkPool();
1593
+
1594
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1595
+ defer tb.deinit();
1596
+
1597
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1598
+ defer view.deinit();
1599
+
1600
+ var style = try ss.SyntaxStyle.init(std.testing.allocator);
1601
+ tb.setSyntaxStyle(style);
1602
+
1603
+ const style_id = try style.registerStyle("test", .{ 1.0, 0.0, 0.0, 1.0 }, null, 0);
1604
+ try tb.setText("Hello World");
1605
+ try tb.addHighlightByCharRange(0, 5, style_id, 1, 0);
1606
+
1607
+ var opt_buffer = try OptimizedBuffer.init(
1608
+ std.testing.allocator,
1609
+ 20,
1610
+ 5,
1611
+ .{ .pool = pool, .width_method = .unicode },
1612
+ );
1613
+ defer opt_buffer.deinit();
1614
+
1615
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1616
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1617
+
1618
+ var out_buffer: [100]u8 = undefined;
1619
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1620
+ const result = out_buffer[0..written];
1621
+ try std.testing.expect(std.mem.startsWith(u8, result, "Hello World"));
1622
+
1623
+ style.deinit();
1624
+
1625
+ try std.testing.expect(tb.getSyntaxStyle() == null);
1626
+
1627
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1628
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1629
+
1630
+ const written2 = try opt_buffer.writeResolvedChars(&out_buffer, false);
1631
+ const result2 = out_buffer[0..written2];
1632
+ try std.testing.expect(std.mem.startsWith(u8, result2, "Hello World"));
1633
+ }
1634
+
1635
+ test "drawTextBuffer - tabs are rendered as spaces (empty cells)" {
1636
+ const pool = gp.initGlobalPool(std.testing.allocator);
1637
+ defer gp.deinitGlobalPool();
1638
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1639
+ defer link.deinitGlobalLinkPool();
1640
+
1641
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1642
+ defer tb.deinit();
1643
+
1644
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1645
+ defer view.deinit();
1646
+
1647
+ tb.setTabWidth(4);
1648
+
1649
+ try tb.setText("A\tB");
1650
+
1651
+ var opt_buffer = try OptimizedBuffer.init(
1652
+ std.testing.allocator,
1653
+ 20,
1654
+ 5,
1655
+ .{ .pool = pool, .width_method = .unicode },
1656
+ );
1657
+ defer opt_buffer.deinit();
1658
+
1659
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1660
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1661
+
1662
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
1663
+ try std.testing.expectEqual(@as(u32, 'A'), cell_0.char);
1664
+
1665
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
1666
+ try std.testing.expectEqual(@as(u32, 32), cell_1.char);
1667
+
1668
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
1669
+ try std.testing.expectEqual(@as(u32, 32), cell_2.char);
1670
+
1671
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
1672
+ try std.testing.expectEqual(@as(u32, 32), cell_3.char);
1673
+
1674
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
1675
+ try std.testing.expectEqual(@as(u32, 32), cell_4.char);
1676
+
1677
+ // With static tabs: A at col 0, tab takes 4 cols (1-4), B at col 5
1678
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
1679
+ try std.testing.expectEqual(@as(u32, 'B'), cell_5.char);
1680
+ }
1681
+
1682
+ test "drawTextBuffer - tab indicator renders with correct color" {
1683
+ const pool = gp.initGlobalPool(std.testing.allocator);
1684
+ defer gp.deinitGlobalPool();
1685
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1686
+ defer link.deinitGlobalLinkPool();
1687
+
1688
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1689
+ defer tb.deinit();
1690
+
1691
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1692
+ defer view.deinit();
1693
+
1694
+ tb.setTabWidth(4);
1695
+ try tb.setText("A\tB");
1696
+
1697
+ view.setTabIndicator(@as(u32, '→'));
1698
+ view.setTabIndicatorColor(RGBA{ 0.25, 0.25, 0.25, 1.0 });
1699
+
1700
+ var opt_buffer = try OptimizedBuffer.init(
1701
+ std.testing.allocator,
1702
+ 20,
1703
+ 5,
1704
+ .{ .pool = pool, .width_method = .unicode },
1705
+ );
1706
+ defer opt_buffer.deinit();
1707
+
1708
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1709
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1710
+
1711
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
1712
+ try std.testing.expectEqual(@as(u32, 'A'), cell_0.char);
1713
+
1714
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
1715
+ try std.testing.expectEqual(@as(u32, '→'), cell_1.char);
1716
+ try std.testing.expectEqual(@as(f32, 0.25), cell_1.fg[0]);
1717
+ try std.testing.expectEqual(@as(f32, 0.25), cell_1.fg[1]);
1718
+ try std.testing.expectEqual(@as(f32, 0.25), cell_1.fg[2]);
1719
+
1720
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
1721
+ try std.testing.expectEqual(@as(u32, 32), cell_2.char);
1722
+
1723
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
1724
+ try std.testing.expectEqual(@as(u32, 32), cell_3.char);
1725
+
1726
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
1727
+ try std.testing.expectEqual(@as(u32, 32), cell_4.char);
1728
+
1729
+ // With static tabs: A at col 0, tab takes 4 cols (1-4), B at col 5
1730
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
1731
+ try std.testing.expectEqual(@as(u32, 'B'), cell_5.char);
1732
+ }
1733
+
1734
+ test "drawTextBuffer - tab without indicator renders as spaces" {
1735
+ const pool = gp.initGlobalPool(std.testing.allocator);
1736
+ defer gp.deinitGlobalPool();
1737
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1738
+ defer link.deinitGlobalLinkPool();
1739
+
1740
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1741
+ defer tb.deinit();
1742
+
1743
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1744
+ defer view.deinit();
1745
+
1746
+ tb.setTabWidth(4);
1747
+ try tb.setText("A\tB");
1748
+
1749
+ var opt_buffer = try OptimizedBuffer.init(
1750
+ std.testing.allocator,
1751
+ 20,
1752
+ 5,
1753
+ .{ .pool = pool, .width_method = .unicode },
1754
+ );
1755
+ defer opt_buffer.deinit();
1756
+
1757
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1758
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1759
+
1760
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
1761
+ try std.testing.expectEqual(@as(u32, 'A'), cell_0.char);
1762
+
1763
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
1764
+ try std.testing.expectEqual(@as(u32, 32), cell_1.char);
1765
+
1766
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
1767
+ try std.testing.expectEqual(@as(u32, 32), cell_2.char);
1768
+
1769
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
1770
+ try std.testing.expectEqual(@as(u32, 32), cell_3.char);
1771
+
1772
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
1773
+ try std.testing.expectEqual(@as(u32, 32), cell_4.char);
1774
+
1775
+ // With static tabs: A at col 0, tab takes 4 cols (1-4), B at col 5
1776
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
1777
+ try std.testing.expectEqual(@as(u32, 'B'), cell_5.char);
1778
+ }
1779
+
1780
+ test "drawTextBuffer - mixed ASCII and Unicode with emoji renders completely" {
1781
+ const pool = gp.initGlobalPool(std.testing.allocator);
1782
+ defer gp.deinitGlobalPool();
1783
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1784
+ defer link.deinitGlobalLinkPool();
1785
+
1786
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1787
+ defer tb.deinit();
1788
+
1789
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1790
+ defer view.deinit();
1791
+
1792
+ try tb.setText("- ✅ All 881 native tests passs");
1793
+
1794
+ var opt_buffer = try OptimizedBuffer.init(
1795
+ std.testing.allocator,
1796
+ 50,
1797
+ 5,
1798
+ .{ .pool = pool, .width_method = .unicode },
1799
+ );
1800
+ defer opt_buffer.deinit();
1801
+
1802
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1803
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1804
+
1805
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
1806
+ try std.testing.expectEqual(@as(u32, '-'), cell_0.char);
1807
+
1808
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
1809
+ try std.testing.expectEqual(@as(u32, ' '), cell_1.char);
1810
+
1811
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
1812
+ try std.testing.expect(gp.isGraphemeChar(cell_2.char));
1813
+ const width_2 = gp.encodedCharWidth(cell_2.char);
1814
+ try std.testing.expectEqual(@as(u32, 2), width_2);
1815
+
1816
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
1817
+ try std.testing.expect(gp.isContinuationChar(cell_3.char));
1818
+
1819
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
1820
+ try std.testing.expectEqual(@as(u32, ' '), cell_4.char);
1821
+
1822
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
1823
+ try std.testing.expectEqual(@as(u32, 'A'), cell_5.char);
1824
+
1825
+ const cell_6 = opt_buffer.get(6, 0) orelse unreachable;
1826
+ try std.testing.expectEqual(@as(u32, 'l'), cell_6.char);
1827
+
1828
+ const cell_7 = opt_buffer.get(7, 0) orelse unreachable;
1829
+ try std.testing.expectEqual(@as(u32, 'l'), cell_7.char);
1830
+
1831
+ const cell_8 = opt_buffer.get(8, 0) orelse unreachable;
1832
+ try std.testing.expectEqual(@as(u32, ' '), cell_8.char);
1833
+
1834
+ const cell_9 = opt_buffer.get(9, 0) orelse unreachable;
1835
+ try std.testing.expectEqual(@as(u32, '8'), cell_9.char);
1836
+
1837
+ const cell_10 = opt_buffer.get(10, 0) orelse unreachable;
1838
+ try std.testing.expectEqual(@as(u32, '8'), cell_10.char);
1839
+
1840
+ const cell_11 = opt_buffer.get(11, 0) orelse unreachable;
1841
+ try std.testing.expectEqual(@as(u32, '1'), cell_11.char);
1842
+
1843
+ const cell_12 = opt_buffer.get(12, 0) orelse unreachable;
1844
+ try std.testing.expectEqual(@as(u32, ' '), cell_12.char);
1845
+
1846
+ const cell_13 = opt_buffer.get(13, 0) orelse unreachable;
1847
+ try std.testing.expectEqual(@as(u32, 'n'), cell_13.char);
1848
+
1849
+ const cell_14 = opt_buffer.get(14, 0) orelse unreachable;
1850
+ try std.testing.expectEqual(@as(u32, 'a'), cell_14.char);
1851
+
1852
+ const cell_15 = opt_buffer.get(15, 0) orelse unreachable;
1853
+ try std.testing.expectEqual(@as(u32, 't'), cell_15.char);
1854
+
1855
+ const cell_16 = opt_buffer.get(16, 0) orelse unreachable;
1856
+ try std.testing.expectEqual(@as(u32, 'i'), cell_16.char);
1857
+
1858
+ const cell_17 = opt_buffer.get(17, 0) orelse unreachable;
1859
+ try std.testing.expectEqual(@as(u32, 'v'), cell_17.char);
1860
+
1861
+ const cell_18 = opt_buffer.get(18, 0) orelse unreachable;
1862
+ try std.testing.expectEqual(@as(u32, 'e'), cell_18.char);
1863
+
1864
+ const cell_19 = opt_buffer.get(19, 0) orelse unreachable;
1865
+ try std.testing.expectEqual(@as(u32, ' '), cell_19.char);
1866
+
1867
+ const cell_20 = opt_buffer.get(20, 0) orelse unreachable;
1868
+ try std.testing.expectEqual(@as(u32, 't'), cell_20.char);
1869
+
1870
+ const cell_21 = opt_buffer.get(21, 0) orelse unreachable;
1871
+ try std.testing.expectEqual(@as(u32, 'e'), cell_21.char);
1872
+
1873
+ const cell_22 = opt_buffer.get(22, 0) orelse unreachable;
1874
+ try std.testing.expectEqual(@as(u32, 's'), cell_22.char);
1875
+
1876
+ const cell_23 = opt_buffer.get(23, 0) orelse unreachable;
1877
+ try std.testing.expectEqual(@as(u32, 't'), cell_23.char);
1878
+
1879
+ const cell_24 = opt_buffer.get(24, 0) orelse unreachable;
1880
+ try std.testing.expectEqual(@as(u32, 's'), cell_24.char);
1881
+
1882
+ const cell_25 = opt_buffer.get(25, 0) orelse unreachable;
1883
+ try std.testing.expectEqual(@as(u32, ' '), cell_25.char);
1884
+
1885
+ const cell_26 = opt_buffer.get(26, 0) orelse unreachable;
1886
+ try std.testing.expectEqual(@as(u32, 'p'), cell_26.char);
1887
+
1888
+ const cell_27 = opt_buffer.get(27, 0) orelse unreachable;
1889
+ try std.testing.expectEqual(@as(u32, 'a'), cell_27.char);
1890
+
1891
+ const cell_28 = opt_buffer.get(28, 0) orelse unreachable;
1892
+ try std.testing.expectEqual(@as(u32, 's'), cell_28.char);
1893
+
1894
+ const cell_29 = opt_buffer.get(29, 0) orelse unreachable;
1895
+ try std.testing.expectEqual(@as(u32, 's'), cell_29.char);
1896
+
1897
+ const cell_30 = opt_buffer.get(30, 0) orelse unreachable;
1898
+ try std.testing.expectEqual(@as(u32, 's'), cell_30.char);
1899
+
1900
+ var out_buffer: [500]u8 = undefined;
1901
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
1902
+ const result = out_buffer[0..written];
1903
+
1904
+ try std.testing.expect(std.mem.indexOf(u8, result, "- ✅ All 881 native tests passs") != null);
1905
+
1906
+ const plain_text = tb.getPlainTextIntoBuffer(&out_buffer);
1907
+ const plain_result = out_buffer[0..plain_text];
1908
+ try std.testing.expectEqualStrings("- ✅ All 881 native tests passs", plain_result);
1909
+ }
1910
+
1911
+ test "viewport width = 31 exactly - last character rendering" {
1912
+ const pool = gp.initGlobalPool(std.testing.allocator);
1913
+ defer gp.deinitGlobalPool();
1914
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1915
+ defer link.deinitGlobalLinkPool();
1916
+
1917
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1918
+ defer tb.deinit();
1919
+
1920
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1921
+ defer view.deinit();
1922
+
1923
+ try tb.setText("- ✅ All 881 native tests passs");
1924
+
1925
+ // Set viewport width to EXACTLY 31 (the display width needed)
1926
+ view.setViewport(text_buffer_view.Viewport{ .x = 0, .y = 0, .width = 31, .height = 1 });
1927
+
1928
+ var opt_buffer = try OptimizedBuffer.init(
1929
+ std.testing.allocator,
1930
+ 50,
1931
+ 5,
1932
+ .{ .pool = pool, .width_method = .unicode },
1933
+ );
1934
+ defer opt_buffer.deinit();
1935
+
1936
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1937
+ try opt_buffer.drawTextBuffer(view, 0, 0);
1938
+
1939
+ // BUG CHECK: The last 's' at cell 30 should be present
1940
+ const cell_30 = opt_buffer.get(30, 0);
1941
+ if (cell_30) |c| {
1942
+ try std.testing.expectEqual(@as(u32, 's'), c.char);
1943
+ } else {
1944
+ return error.TestFailed;
1945
+ }
1946
+ }
1947
+
1948
+ test "drawTextBuffer - complex multilingual text with diverse scripts and emojis" {
1949
+ const pool = gp.initGlobalPool(std.testing.allocator);
1950
+ defer gp.deinitGlobalPool();
1951
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
1952
+ defer link.deinitGlobalLinkPool();
1953
+
1954
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
1955
+ defer tb.deinit();
1956
+
1957
+ var view = try TextBufferView.init(std.testing.allocator, tb);
1958
+ defer view.deinit();
1959
+
1960
+ const text =
1961
+ \\# The Celestial Journey of संस्कृति 🌟🔮✨
1962
+ \\In the beginning, there was नमस्ते 🙏 and the ancient wisdom of the ॐ symbol echoing through dimensions. The travelers 🧑‍🚀👨‍🚀👩‍🚀 embarked on their quest through the cosmos, guided by the mysterious རྒྱ་མཚོ and the luminous 🌈🦄🧚‍♀️ beings of light. They encountered the great देवनागरी scribes who wrote in flowing अक्षर characters, documenting everything in their sacred texts 📜📖✍️.
1963
+ \\## Chapter प्रथम: The Eastern Gardens 🏯🎋🌸
1964
+ \\The journey led them to the mystical lands where 漢字 (kanji) danced with ひらがな and カタカナ across ancient scrolls 📯🎴🎎. In the gardens of Seoul, they found 한글 inscriptions speaking of 사랑 (love) and 평화 (peace) 💝🕊️☮️. The monks meditated under the bodhi tree 🧘‍♂️🌳, contemplating the nature of धर्म while drinking matcha 🍵 and eating 餃子 dumplings 🥟.
1965
+ \\Strange creatures emerged from the mist: 🦥🦦🦧🦨🦩🦚🦜🦝🦞🦟. They spoke in riddles about the प्राचीन (ancient) ways and the नवीन (new) paths forward. "भविष्य में क्या है?" they asked, while the ໂຫຍ່າກເຈົ້າ whispered secrets in Lao script 🤫🗣️💬.
1966
+ \\## The संगम (Confluence) of Scripts 🌊📝🎭
1967
+ \\At the great confluence, they witnessed the merger of བོད་ཡིག (Tibetan), ગુજરાતી (Gujarati), and தமிழ் (Tamil) scripts flowing together like rivers 🏞️🌊💧. The scholars debated about ਪੰਜਾਬੀ philosophy while juggling 🤹‍♂️🎪🎨 colorful orbs that represented different తెలుగు concepts.
1968
+ \\The marketplace buzzed with activity 🏪🛒💰: merchants sold বাংলা spices 🌶️🧄🧅, ಕನ್ನಡ silks 🧵👘, and മലയാളം handicrafts 🎨🖼️. Children played with toys shaped like 🦖🦕🐉🐲 while their parents bargained using ancient ଓଡ଼ିଆ numerals and gestures 🤝🤲👐.
1969
+ \\## The Festival of ๑๐๐ Lanterns 🏮🎆🎇
1970
+ \\During the grand festival, they lit exactly ๑๐๐ (100 in Thai numerals) lanterns 🏮🕯️💡 that floated into the night sky like ascending ความหวัง (hopes). The celebration featured dancers 💃🕺🩰 performing classical moves from भरतनाट्यम tradition, their मुद्रा hand gestures telling stories of प्रेम and वीरता.
1971
+ \\Musicians played unusual instruments: the 🎻🎺🎷🎸🪕🪘 ensemble created harmonies that resonated with the वेद chants and མཆོད་རྟེན bells 🔔⛩️. The audience sat mesmerized 😵‍💫🤯✨, some sipping on bubble tea 🧋 while others enjoyed मिठाई sweets 🍬🍭🧁.
1972
+ \\## The འཕྲུལ་དེབ (Machine) Age Arrives ⚙️🤖🦾
1973
+ \\As modernity crept in, the ancient འཁོར་ལོ (wheel) gave way to 🚗🚕🚙🚌🚎 vehicles and eventually to 🚀🛸🛰️ spacecraft. The યુવાન (youth) learned to code in Python 🐍💻⌨️, but still honored their గురువు (teachers) who taught them the old ways of ज्ञान acquisition 🧠📚🎓.
1974
+ \\The সমাজ (society) transformed: robots 🤖🦾🦿 worked alongside humans 👨‍💼👩‍💼👨‍🔬👩‍🔬, and AI learned to read སྐད (languages) from across the planet 🌍🌎🌏. Yet somehow, the essence of मानवता remained intact, preserved in the கவிதை (poetry) and the ກາບແກ້ວ stories passed down through generations 👴👵👨‍👩‍👧‍👦.
1975
+ \\## The Final ಅಧ್ಯಾಯ (Chapter) 🌅🌄🌠
1976
+ \\As the sun set over the പർവ്വതങ്ങൾ (mountains) 🏔️⛰️🗻, our travelers realized that every script, every symbol—from ا to ㄱ to অ to अ—represented not just sounds, but entire civilizations' worth of विचार (thoughts) and ಕನಸು (dreams) 💭💤🌌.
1977
+ \\They gathered around the final campfire 🔥🏕️, sharing stories in ภาษา (languages) both ancient and new. Someone brought out a guitar 🎸 and started singing in ગીત form, while others prepared ආහාර (food) 🍛🍲🥘 seasoned with love ❤️💕💖 and memories 📸🎞️📹.
1978
+ \\And so they learned that whether written in দেবনাগরী, 中文, 한글, or ไทย, the human experience transcends boundaries 🌐🤝🌈. The weird emojis 🦩🧿🪬🫀🫁🧠 and complex scripts were all part of the same beautiful བསྟན་པ (teaching): that diversity is our greatest strength 💪✊🙌.
1979
+ \\The end. समाप्त. 끝. จบ. முடிவு. ముగింపు. সমাপ্তি. ഒടുക്കം. ಅಂತ್ಯ. અંત. 🎬🎭🎪✨🌟⭐
1980
+ \\
1981
+ ;
1982
+
1983
+ try tb.setText(text);
1984
+
1985
+ // Test with word wrapping
1986
+ view.setWrapMode(.word);
1987
+ view.setWrapWidth(80);
1988
+ view.updateVirtualLines();
1989
+
1990
+ var opt_buffer = try OptimizedBuffer.init(
1991
+ std.testing.allocator,
1992
+ 80,
1993
+ 100,
1994
+ .{ .pool = pool, .width_method = .unicode },
1995
+ );
1996
+ defer opt_buffer.deinit();
1997
+
1998
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
1999
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2000
+
2001
+ // Verify the text buffer can handle complex multilingual content
2002
+ const virtual_lines = view.getVirtualLines();
2003
+ try std.testing.expect(virtual_lines.len > 0);
2004
+
2005
+ // Test that we can get the plain text back
2006
+ var plain_buffer: [10000]u8 = undefined;
2007
+ const plain_len = tb.getPlainTextIntoBuffer(&plain_buffer);
2008
+ const plain_text = plain_buffer[0..plain_len];
2009
+
2010
+ // Verify some key multilingual content is present
2011
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "संस्कृति") != null);
2012
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "नमस्ते") != null);
2013
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "漢字") != null);
2014
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "한글") != null);
2015
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "தமிழ்") != null);
2016
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "বাংলা") != null);
2017
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "ಕನ್ನಡ") != null);
2018
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "മലയാളം") != null);
2019
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "🌟") != null);
2020
+ try std.testing.expect(std.mem.indexOf(u8, plain_text, "🙏") != null);
2021
+
2022
+ // Test with no wrapping
2023
+ view.setWrapMode(.none);
2024
+ view.setWrapWidth(null);
2025
+ view.updateVirtualLines();
2026
+
2027
+ const no_wrap_lines = view.getVirtualLines();
2028
+ // Should have one line per actual newline in the text
2029
+ try std.testing.expect(no_wrap_lines.len > 10);
2030
+
2031
+ // Test with character wrapping on narrow width
2032
+ view.setWrapMode(.char);
2033
+ view.setWrapWidth(40);
2034
+ view.updateVirtualLines();
2035
+
2036
+ const char_wrap_lines = view.getVirtualLines();
2037
+ // Should wrap into many more lines
2038
+ try std.testing.expect(char_wrap_lines.len > virtual_lines.len);
2039
+
2040
+ // Test viewport scrolling through the content
2041
+ view.setWrapMode(.word);
2042
+ view.setWrapWidth(80);
2043
+ view.setViewport(.{ .x = 0, .y = 10, .width = 80, .height = 20 });
2044
+ view.updateVirtualLines();
2045
+
2046
+ const viewport_lines = view.getVirtualLines();
2047
+ try std.testing.expect(viewport_lines.len <= 20);
2048
+
2049
+ // Verify rendering doesn't crash with complex emoji sequences
2050
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2051
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2052
+
2053
+ // Test that line count is reasonable
2054
+ const line_count = tb.getLineCount();
2055
+ try std.testing.expect(line_count > 15);
2056
+ }
2057
+
2058
+ test "setStyledText - highlight positioning with Unicode text" {
2059
+ const pool = gp.initGlobalPool(std.testing.allocator);
2060
+ defer gp.deinitGlobalPool();
2061
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2062
+ defer link.deinitGlobalLinkPool();
2063
+
2064
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2065
+ defer tb.deinit();
2066
+
2067
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2068
+ defer view.deinit();
2069
+
2070
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2071
+ defer style.deinit();
2072
+ tb.setSyntaxStyle(style);
2073
+
2074
+ // Text: "Say नमस्ते please."
2075
+ // Layout: "Say " (4 cols) + "नमस्ते" (4 cols) + " " (1 col) + "please" (6 cols) + "." (1 col)
2076
+ // We highlight "please" with a green background to verify correct positioning
2077
+ const text_part1 = "Say ";
2078
+ const text_part2 = "नमस्ते";
2079
+ const text_part3 = " ";
2080
+ const text_part4 = "please";
2081
+ const text_part5 = ".";
2082
+
2083
+ const fg_normal = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
2084
+ const bg_highlight = [4]f32{ 0.0, 1.0, 0.0, 1.0 }; // Green background
2085
+
2086
+ const chunks = [_]StyledChunk{
2087
+ .{ .text_ptr = text_part1.ptr, .text_len = text_part1.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = null, .attributes = 0 },
2088
+ .{ .text_ptr = text_part2.ptr, .text_len = text_part2.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = null, .attributes = 0 },
2089
+ .{ .text_ptr = text_part3.ptr, .text_len = text_part3.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = null, .attributes = 0 },
2090
+ .{ .text_ptr = text_part4.ptr, .text_len = text_part4.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = @ptrCast(&bg_highlight), .attributes = 0 },
2091
+ .{ .text_ptr = text_part5.ptr, .text_len = text_part5.len, .fg_ptr = @ptrCast(&fg_normal), .bg_ptr = null, .attributes = 0 },
2092
+ };
2093
+
2094
+ try tb.setStyledText(&chunks);
2095
+
2096
+ // Verify the text content
2097
+ var out_buffer: [100]u8 = undefined;
2098
+ const written = tb.getPlainTextIntoBuffer(&out_buffer);
2099
+ const result = out_buffer[0..written];
2100
+ try std.testing.expectEqualStrings("Say नमस्ते please.", result);
2101
+
2102
+ // Calculate expected positions using measureText
2103
+ const part1_width = tb.measureText(text_part1);
2104
+ const part2_width = tb.measureText(text_part2);
2105
+ const part3_width = tb.measureText(text_part3);
2106
+ const please_start_col = part1_width + part2_width + part3_width;
2107
+
2108
+ // Render to buffer and check colors
2109
+ var opt_buffer = try OptimizedBuffer.init(
2110
+ std.testing.allocator,
2111
+ 30,
2112
+ 5,
2113
+ .{ .pool = pool, .width_method = .unicode },
2114
+ );
2115
+ defer opt_buffer.deinit();
2116
+
2117
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2118
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2119
+
2120
+ // Check that "please" (6 characters) all have the green background
2121
+ const epsilon: f32 = 0.01;
2122
+ var i: u32 = 0;
2123
+ while (i < 6) : (i += 1) {
2124
+ const cell_col = please_start_col + i;
2125
+ const cell = opt_buffer.get(cell_col, 0) orelse return error.TestFailed;
2126
+
2127
+ // Verify green background (R=0, G=1, B=0)
2128
+ try std.testing.expect(@abs(cell.bg[0] - 0.0) < epsilon);
2129
+ try std.testing.expect(@abs(cell.bg[1] - 1.0) < epsilon);
2130
+ try std.testing.expect(@abs(cell.bg[2] - 0.0) < epsilon);
2131
+ }
2132
+
2133
+ // Check that text before "please" does NOT have green background
2134
+ i = 0;
2135
+ while (i < please_start_col) : (i += 1) {
2136
+ const cell = opt_buffer.get(i, 0) orelse unreachable;
2137
+ const has_green_bg = @abs(cell.bg[1] - 1.0) < epsilon and @abs(cell.bg[0] - 0.0) < epsilon;
2138
+ try std.testing.expect(!has_green_bg);
2139
+ }
2140
+
2141
+ // Check that "." after "please" does NOT have green background
2142
+ const period_col = please_start_col + 6;
2143
+ const period_cell = opt_buffer.get(period_col, 0) orelse unreachable;
2144
+ const has_green_bg = @abs(period_cell.bg[1] - 1.0) < epsilon and @abs(period_cell.bg[0] - 0.0) < epsilon;
2145
+ try std.testing.expect(!has_green_bg);
2146
+ }
2147
+
2148
+ test "drawTextBuffer - multiple syntax highlights with various horizontal viewport offsets" {
2149
+ const pool = gp.initGlobalPool(std.testing.allocator);
2150
+ defer gp.deinitGlobalPool();
2151
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2152
+ defer link.deinitGlobalLinkPool();
2153
+
2154
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2155
+ defer tb.deinit();
2156
+
2157
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2158
+ defer view.deinit();
2159
+
2160
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2161
+ defer style.deinit();
2162
+ tb.setSyntaxStyle(style);
2163
+
2164
+ // Register different color styles
2165
+ const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0);
2166
+ const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0);
2167
+ const blue_style = try style.registerStyle("blue", RGBA{ 0.0, 0.0, 1.0, 1.0 }, null, 0);
2168
+ const yellow_style = try style.registerStyle("yellow", RGBA{ 1.0, 1.0, 0.0, 1.0 }, null, 0);
2169
+
2170
+ // Text: "const x = function(y) { return y * 2; }"
2171
+ const test_text = "const x = function(y) { return y * 2; }";
2172
+ // Positions (0-indexed):
2173
+ // "const" is at 0-5 (exclusive end, so 0,1,2,3,4)
2174
+ // "function" is at 10-18 (chars 10-17)
2175
+ // "return" is at 24-30 (chars 24-29)
2176
+ // "2" is at 35-36 (char 35)
2177
+
2178
+ try tb.setText(test_text);
2179
+
2180
+ try tb.addHighlightByCharRange(0, 5, red_style, 1, 0); // "const"
2181
+ try tb.addHighlightByCharRange(10, 18, green_style, 1, 0); // "function"
2182
+ try tb.addHighlightByCharRange(24, 30, blue_style, 1, 0); // "return"
2183
+ try tb.addHighlightByCharRange(35, 36, yellow_style, 1, 0); // "2"
2184
+
2185
+ view.setWrapMode(.none);
2186
+ view.setWrapWidth(null);
2187
+
2188
+ const epsilon: f32 = 0.01;
2189
+
2190
+ // Test 1: Viewport at x=0 (no scroll)
2191
+ {
2192
+ view.setViewport(.{ .x = 0, .y = 0, .width = 40, .height = 1 });
2193
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 40, 1, .{ .pool = pool, .width_method = .unicode });
2194
+ defer opt_buffer.deinit();
2195
+
2196
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2197
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2198
+
2199
+ // Check "const" is red
2200
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2201
+ try std.testing.expectEqual(@as(u32, 'c'), cell_0.char);
2202
+ try std.testing.expect(@abs(cell_0.fg[0] - 1.0) < epsilon); // Red
2203
+
2204
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
2205
+ try std.testing.expectEqual(@as(u32, 't'), cell_4.char);
2206
+ try std.testing.expect(@abs(cell_4.fg[0] - 1.0) < epsilon); // Red
2207
+
2208
+ // Check "function" is green
2209
+ const cell_10 = opt_buffer.get(10, 0) orelse unreachable;
2210
+ try std.testing.expectEqual(@as(u32, 'f'), cell_10.char);
2211
+ try std.testing.expect(@abs(cell_10.fg[1] - 1.0) < epsilon); // Green
2212
+
2213
+ const cell_17 = opt_buffer.get(17, 0) orelse unreachable;
2214
+ try std.testing.expectEqual(@as(u32, 'n'), cell_17.char);
2215
+ try std.testing.expect(@abs(cell_17.fg[1] - 1.0) < epsilon); // Green
2216
+ }
2217
+
2218
+ // Test 2: Viewport scrolled to x=3 (showing "st x = fun...")
2219
+ {
2220
+ view.setViewport(.{ .x = 3, .y = 0, .width = 20, .height = 1 });
2221
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode });
2222
+ defer opt_buffer.deinit();
2223
+
2224
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2225
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2226
+
2227
+ // Buffer shows characters 3-22 from source: "st x = function(y) {"
2228
+ // Position 0: 's' (source 3) - should be RED (part of "const" 0-5)
2229
+ // Position 1: 't' (source 4) - should be RED (part of "const" 0-5)
2230
+ // Position 2: ' ' (source 5) - NOT red (outside "const")
2231
+ // Position 7: 'f' (source 10) - should be GREEN (start of "function" 10-18)
2232
+ // Position 14: 'n' (source 17) - should be GREEN (part of "function")
2233
+
2234
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2235
+ try std.testing.expectEqual(@as(u32, 's'), cell_0.char);
2236
+ try std.testing.expect(@abs(cell_0.fg[0] - 1.0) < epsilon); // Red
2237
+ try std.testing.expect(@abs(cell_0.fg[1] - 0.0) < epsilon);
2238
+ try std.testing.expect(@abs(cell_0.fg[2] - 0.0) < epsilon);
2239
+
2240
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
2241
+ try std.testing.expectEqual(@as(u32, 't'), cell_1.char);
2242
+ try std.testing.expect(@abs(cell_1.fg[0] - 1.0) < epsilon); // Red
2243
+ try std.testing.expect(@abs(cell_1.fg[1] - 0.0) < epsilon);
2244
+ try std.testing.expect(@abs(cell_1.fg[2] - 0.0) < epsilon);
2245
+
2246
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
2247
+ try std.testing.expectEqual(@as(u32, ' '), cell_2.char);
2248
+ try std.testing.expect(@abs(cell_2.fg[0] - 1.0) < epsilon); // White (default)
2249
+ try std.testing.expect(@abs(cell_2.fg[1] - 1.0) < epsilon);
2250
+ try std.testing.expect(@abs(cell_2.fg[2] - 1.0) < epsilon);
2251
+
2252
+ const cell_7 = opt_buffer.get(7, 0) orelse unreachable;
2253
+ try std.testing.expectEqual(@as(u32, 'f'), cell_7.char);
2254
+ try std.testing.expect(@abs(cell_7.fg[0] - 0.0) < epsilon); // Green
2255
+ try std.testing.expect(@abs(cell_7.fg[1] - 1.0) < epsilon);
2256
+ try std.testing.expect(@abs(cell_7.fg[2] - 0.0) < epsilon);
2257
+
2258
+ const cell_14 = opt_buffer.get(14, 0) orelse unreachable;
2259
+ try std.testing.expectEqual(@as(u32, 'n'), cell_14.char);
2260
+ try std.testing.expect(@abs(cell_14.fg[0] - 0.0) < epsilon); // Green
2261
+ try std.testing.expect(@abs(cell_14.fg[1] - 1.0) < epsilon);
2262
+ try std.testing.expect(@abs(cell_14.fg[2] - 0.0) < epsilon);
2263
+ }
2264
+
2265
+ // Test 4: Viewport scrolled to x=30 (showing "y * 2; }" based on 40 char text)
2266
+ {
2267
+ view.setViewport(.{ .x = 30, .y = 0, .width = 20, .height = 1 });
2268
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode });
2269
+ defer opt_buffer.deinit();
2270
+
2271
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2272
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2273
+
2274
+ // Actual rendering shows: " y * 2; }"
2275
+ // Source chars 30-38 are shown
2276
+ // Position 0: ' ' (source 30) - white
2277
+ // Position 5: '2' (source 35) - should be YELLOW (highlighted 35-36)
2278
+
2279
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
2280
+ try std.testing.expectEqual(@as(u32, '2'), cell_5.char);
2281
+ try std.testing.expect(@abs(cell_5.fg[0] - 1.0) < epsilon); // Yellow
2282
+ try std.testing.expect(@abs(cell_5.fg[1] - 1.0) < epsilon);
2283
+ try std.testing.expect(@abs(cell_5.fg[2] - 0.0) < epsilon);
2284
+ }
2285
+ }
2286
+
2287
+ test "drawTextBuffer - syntax highlighting with horizontal viewport offset" {
2288
+ const pool = gp.initGlobalPool(std.testing.allocator);
2289
+ defer gp.deinitGlobalPool();
2290
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2291
+ defer link.deinitGlobalLinkPool();
2292
+
2293
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2294
+ defer tb.deinit();
2295
+
2296
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2297
+ defer view.deinit();
2298
+
2299
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2300
+ defer style.deinit();
2301
+ tb.setSyntaxStyle(style);
2302
+
2303
+ // Register a red style
2304
+ const red_style_id = try style.registerStyle("keyword", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0);
2305
+
2306
+ // Text: "const x = 1"
2307
+ // Highlight "const" (characters 0-5) in red
2308
+ try tb.setText("const x = 1");
2309
+ try tb.addHighlightByCharRange(0, 5, red_style_id, 1, 0);
2310
+
2311
+ // Set viewport to skip first 3 characters, showing "st x = 1"
2312
+ view.setWrapMode(.none);
2313
+ view.setWrapWidth(null);
2314
+ view.setViewport(.{ .x = 3, .y = 0, .width = 10, .height = 1 });
2315
+
2316
+ var opt_buffer = try OptimizedBuffer.init(
2317
+ std.testing.allocator,
2318
+ 10,
2319
+ 1,
2320
+ .{ .pool = pool, .width_method = .unicode },
2321
+ );
2322
+ defer opt_buffer.deinit();
2323
+
2324
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2325
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2326
+
2327
+ const epsilon: f32 = 0.01;
2328
+ const red_fg = RGBA{ 1.0, 0.0, 0.0, 1.0 };
2329
+
2330
+ // Check that 's' at buffer position 0 is RED
2331
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2332
+ try std.testing.expectEqual(@as(u32, 's'), cell_0.char);
2333
+ const is_red_0 = @abs(cell_0.fg[0] - red_fg[0]) < epsilon and
2334
+ @abs(cell_0.fg[1] - red_fg[1]) < epsilon and
2335
+ @abs(cell_0.fg[2] - red_fg[2]) < epsilon;
2336
+ try std.testing.expect(is_red_0);
2337
+
2338
+ // Check that 't' at buffer position 1 is RED
2339
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
2340
+ try std.testing.expectEqual(@as(u32, 't'), cell_1.char);
2341
+ const is_red_1 = @abs(cell_1.fg[0] - red_fg[0]) < epsilon and
2342
+ @abs(cell_1.fg[1] - red_fg[1]) < epsilon and
2343
+ @abs(cell_1.fg[2] - red_fg[2]) < epsilon;
2344
+ try std.testing.expect(is_red_1);
2345
+
2346
+ // Check that ' ' at buffer position 2 is NOT RED
2347
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
2348
+ try std.testing.expectEqual(@as(u32, ' '), cell_2.char);
2349
+ const is_red_2 = @abs(cell_2.fg[0] - red_fg[0]) < epsilon and
2350
+ @abs(cell_2.fg[1] - red_fg[1]) < epsilon and
2351
+ @abs(cell_2.fg[2] - red_fg[2]) < epsilon;
2352
+ try std.testing.expect(!is_red_2);
2353
+
2354
+ // Check that 'x' at buffer position 3 is NOT RED
2355
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
2356
+ try std.testing.expectEqual(@as(u32, 'x'), cell_3.char);
2357
+ const is_red_3 = @abs(cell_3.fg[0] - red_fg[0]) < epsilon and
2358
+ @abs(cell_3.fg[1] - red_fg[1]) < epsilon and
2359
+ @abs(cell_3.fg[2] - red_fg[2]) < epsilon;
2360
+ try std.testing.expect(!is_red_3);
2361
+ }
2362
+
2363
+ test "drawTextBuffer - setStyledText with multiple colors and horizontal scrolling" {
2364
+ const pool = gp.initGlobalPool(std.testing.allocator);
2365
+ defer gp.deinitGlobalPool();
2366
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2367
+ defer link.deinitGlobalLinkPool();
2368
+
2369
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2370
+ defer tb.deinit();
2371
+
2372
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2373
+ defer view.deinit();
2374
+
2375
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2376
+ defer style.deinit();
2377
+ tb.setSyntaxStyle(style);
2378
+
2379
+ // Simulate what code renderable does with setStyledText
2380
+ // Text will be: "const x = function(y) { return y * 2; }"
2381
+ // But split into colored chunks like syntax highlighting
2382
+
2383
+ const chunk1_text = "const";
2384
+ const chunk2_text = " x = ";
2385
+ const chunk3_text = "function";
2386
+ const chunk4_text = "(y) { ";
2387
+ const chunk5_text = "return";
2388
+ const chunk6_text = " y * ";
2389
+ const chunk7_text = "2";
2390
+ const chunk8_text = "; }";
2391
+
2392
+ const red_color = [4]f32{ 1.0, 0.0, 0.0, 1.0 };
2393
+ const white_color = [4]f32{ 1.0, 1.0, 1.0, 1.0 };
2394
+ const green_color = [4]f32{ 0.0, 1.0, 0.0, 1.0 };
2395
+ const blue_color = [4]f32{ 0.0, 0.0, 1.0, 1.0 };
2396
+ const yellow_color = [4]f32{ 1.0, 1.0, 0.0, 1.0 };
2397
+
2398
+ const chunks = [_]StyledChunk{
2399
+ .{ .text_ptr = chunk1_text.ptr, .text_len = chunk1_text.len, .fg_ptr = @ptrCast(&red_color), .bg_ptr = null, .attributes = 0 },
2400
+ .{ .text_ptr = chunk2_text.ptr, .text_len = chunk2_text.len, .fg_ptr = @ptrCast(&white_color), .bg_ptr = null, .attributes = 0 },
2401
+ .{ .text_ptr = chunk3_text.ptr, .text_len = chunk3_text.len, .fg_ptr = @ptrCast(&green_color), .bg_ptr = null, .attributes = 0 },
2402
+ .{ .text_ptr = chunk4_text.ptr, .text_len = chunk4_text.len, .fg_ptr = @ptrCast(&white_color), .bg_ptr = null, .attributes = 0 },
2403
+ .{ .text_ptr = chunk5_text.ptr, .text_len = chunk5_text.len, .fg_ptr = @ptrCast(&blue_color), .bg_ptr = null, .attributes = 0 },
2404
+ .{ .text_ptr = chunk6_text.ptr, .text_len = chunk6_text.len, .fg_ptr = @ptrCast(&white_color), .bg_ptr = null, .attributes = 0 },
2405
+ .{ .text_ptr = chunk7_text.ptr, .text_len = chunk7_text.len, .fg_ptr = @ptrCast(&yellow_color), .bg_ptr = null, .attributes = 0 },
2406
+ .{ .text_ptr = chunk8_text.ptr, .text_len = chunk8_text.len, .fg_ptr = @ptrCast(&white_color), .bg_ptr = null, .attributes = 0 },
2407
+ };
2408
+
2409
+ try tb.setStyledText(&chunks);
2410
+
2411
+ view.setWrapMode(.none);
2412
+ view.setWrapWidth(null);
2413
+
2414
+ const epsilon: f32 = 0.01;
2415
+
2416
+ // Helper to check if color matches
2417
+ const isRed = struct {
2418
+ fn check(fg: RGBA, eps: f32) bool {
2419
+ return @abs(fg[0] - 1.0) < eps and @abs(fg[1] - 0.0) < eps and @abs(fg[2] - 0.0) < eps;
2420
+ }
2421
+ }.check;
2422
+
2423
+ const isGreen = struct {
2424
+ fn check(fg: RGBA, eps: f32) bool {
2425
+ return @abs(fg[0] - 0.0) < eps and @abs(fg[1] - 1.0) < eps and @abs(fg[2] - 0.0) < eps;
2426
+ }
2427
+ }.check;
2428
+
2429
+ const isBlue = struct {
2430
+ fn check(fg: RGBA, eps: f32) bool {
2431
+ return @abs(fg[0] - 0.0) < eps and @abs(fg[1] - 0.0) < eps and @abs(fg[2] - 1.0) < eps;
2432
+ }
2433
+ }.check;
2434
+
2435
+ const isYellow = struct {
2436
+ fn check(fg: RGBA, eps: f32) bool {
2437
+ return @abs(fg[0] - 1.0) < eps and @abs(fg[1] - 1.0) < eps and @abs(fg[2] - 0.0) < eps;
2438
+ }
2439
+ }.check;
2440
+
2441
+ const isWhite = struct {
2442
+ fn check(fg: RGBA, eps: f32) bool {
2443
+ return @abs(fg[0] - 1.0) < eps and @abs(fg[1] - 1.0) < eps and @abs(fg[2] - 1.0) < eps;
2444
+ }
2445
+ }.check;
2446
+
2447
+ // Test at x=0 (no scroll)
2448
+ {
2449
+ view.setViewport(.{ .x = 0, .y = 0, .width = 40, .height = 1 });
2450
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 40, 1, .{ .pool = pool, .width_method = .unicode });
2451
+ defer opt_buffer.deinit();
2452
+
2453
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2454
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2455
+
2456
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable; // 'c' from "const"
2457
+ try std.testing.expectEqual(@as(u32, 'c'), cell_0.char);
2458
+ try std.testing.expect(isRed(cell_0.fg, epsilon));
2459
+
2460
+ const cell_10 = opt_buffer.get(10, 0) orelse unreachable; // 'f' from "function"
2461
+ try std.testing.expectEqual(@as(u32, 'f'), cell_10.char);
2462
+ try std.testing.expect(isGreen(cell_10.fg, epsilon));
2463
+ }
2464
+
2465
+ // Test at x=5 (scrolled past "const")
2466
+ {
2467
+ view.setViewport(.{ .x = 5, .y = 0, .width = 20, .height = 1 });
2468
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode });
2469
+ defer opt_buffer.deinit();
2470
+
2471
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2472
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2473
+
2474
+ // At x=5, showing chars 5-24: " x = function(y) { "
2475
+ // Position 0: ' ' (source 5) - should be white
2476
+ // Position 5: 'f' (source 10) - should be GREEN
2477
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2478
+ try std.testing.expectEqual(@as(u32, ' '), cell_0.char);
2479
+ try std.testing.expect(isWhite(cell_0.fg, epsilon));
2480
+
2481
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
2482
+ try std.testing.expectEqual(@as(u32, 'f'), cell_5.char);
2483
+ try std.testing.expect(isGreen(cell_5.fg, epsilon));
2484
+ }
2485
+
2486
+ // Test at x=15 (in middle of "function")
2487
+ {
2488
+ view.setViewport(.{ .x = 15, .y = 0, .width = 20, .height = 1 });
2489
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode });
2490
+ defer opt_buffer.deinit();
2491
+
2492
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2493
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2494
+
2495
+ // At x=15, showing chars 15-34: "ion(y) { return y * "
2496
+ // "const x = function..."
2497
+ // 0123456789012345678...
2498
+ // Position 0: 'i' (source 15) - should be GREEN (part of "function" 10-18)
2499
+ // Position 1: 'o' (source 16) - should be GREEN
2500
+ // Position 2: 'n' (source 17) - should be GREEN
2501
+ // Position 3: '(' (source 18) - should be WHITE (end of "function")
2502
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2503
+ try std.testing.expectEqual(@as(u32, 'i'), cell_0.char);
2504
+ try std.testing.expect(isGreen(cell_0.fg, epsilon));
2505
+
2506
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
2507
+ try std.testing.expectEqual(@as(u32, 'o'), cell_1.char);
2508
+ try std.testing.expect(isGreen(cell_1.fg, epsilon));
2509
+
2510
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
2511
+ try std.testing.expectEqual(@as(u32, 'n'), cell_2.char);
2512
+ try std.testing.expect(isGreen(cell_2.fg, epsilon));
2513
+
2514
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
2515
+ try std.testing.expectEqual(@as(u32, '('), cell_3.char);
2516
+ try std.testing.expect(isWhite(cell_3.fg, epsilon));
2517
+
2518
+ // Position 9: 'r' (source 24) - should be BLUE (start of "return" 24-30)
2519
+ const cell_9 = opt_buffer.get(9, 0) orelse unreachable;
2520
+ try std.testing.expectEqual(@as(u32, 'r'), cell_9.char);
2521
+ try std.testing.expect(isBlue(cell_9.fg, epsilon));
2522
+ }
2523
+
2524
+ // Test at x=25 (past "return")
2525
+ {
2526
+ view.setViewport(.{ .x = 25, .y = 0, .width = 20, .height = 1 });
2527
+ var opt_buffer = try OptimizedBuffer.init(std.testing.allocator, 20, 1, .{ .pool = pool, .width_method = .unicode });
2528
+ defer opt_buffer.deinit();
2529
+
2530
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2531
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2532
+
2533
+ // At x=25, showing chars 25-44: "eturn y * 2; }"
2534
+ // Position 0: 'e' (source 25) - should be BLUE (part of "return" 24-30)
2535
+ // Position 4: 'n' (source 29) - should be BLUE
2536
+ // Position 5: ' ' (source 30) - should be WHITE (end of "return")
2537
+ // Position 10: '2' (source 35) - should be YELLOW
2538
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2539
+ try std.testing.expectEqual(@as(u32, 'e'), cell_0.char);
2540
+ try std.testing.expect(isBlue(cell_0.fg, epsilon));
2541
+
2542
+ const cell_4 = opt_buffer.get(4, 0) orelse unreachable;
2543
+ try std.testing.expectEqual(@as(u32, 'n'), cell_4.char);
2544
+ try std.testing.expect(isBlue(cell_4.fg, epsilon));
2545
+
2546
+ const cell_5 = opt_buffer.get(5, 0) orelse unreachable;
2547
+ try std.testing.expectEqual(@as(u32, ' '), cell_5.char);
2548
+ try std.testing.expect(isWhite(cell_5.fg, epsilon));
2549
+
2550
+ const cell_10 = opt_buffer.get(10, 0) orelse unreachable;
2551
+ try std.testing.expectEqual(@as(u32, '2'), cell_10.char);
2552
+ try std.testing.expect(isYellow(cell_10.fg, epsilon));
2553
+ }
2554
+ }
2555
+
2556
+ test "drawTextBuffer - selection with horizontal viewport offset" {
2557
+ const pool = gp.initGlobalPool(std.testing.allocator);
2558
+ defer gp.deinitGlobalPool();
2559
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2560
+ defer link.deinitGlobalLinkPool();
2561
+
2562
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2563
+ defer tb.deinit();
2564
+
2565
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2566
+ defer view.deinit();
2567
+
2568
+ // Text: "0123456789ABCDEFGHIJ"
2569
+ // We'll set viewport to x=5, showing "56789ABCDE"
2570
+ // Then we'll select characters 7-12 (which are "789AB")
2571
+ // Expected: in the rendered buffer, "789AB" should be highlighted
2572
+ try tb.setText("0123456789ABCDEFGHIJ");
2573
+
2574
+ view.setWrapMode(.none);
2575
+ view.setWrapWidth(null);
2576
+ view.setViewport(.{ .x = 5, .y = 0, .width = 10, .height = 1 });
2577
+
2578
+ // Select characters at positions 7-12 in the original text ("789AB")
2579
+ view.setSelection(7, 12, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 });
2580
+
2581
+ var opt_buffer = try OptimizedBuffer.init(
2582
+ std.testing.allocator,
2583
+ 10,
2584
+ 1,
2585
+ .{ .pool = pool, .width_method = .unicode },
2586
+ );
2587
+ defer opt_buffer.deinit();
2588
+
2589
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2590
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2591
+
2592
+ // The viewport shows positions 5-14 of the text
2593
+ // Characters 7-11 (0-indexed) should be highlighted
2594
+ // In the buffer:
2595
+ // Position 0: '5' - not highlighted
2596
+ // Position 1: '6' - not highlighted
2597
+ // Position 2: '7' - HIGHLIGHTED (char pos 7)
2598
+ // Position 3: '8' - HIGHLIGHTED (char pos 8)
2599
+ // Position 4: '9' - HIGHLIGHTED (char pos 9)
2600
+ // Position 5: 'A' - HIGHLIGHTED (char pos 10)
2601
+ // Position 6: 'B' - HIGHLIGHTED (char pos 11)
2602
+ // Position 7: 'C' - not highlighted (char pos 12, selection end is exclusive)
2603
+ // Position 8: 'D' - not highlighted
2604
+ // Position 9: 'E' - not highlighted
2605
+
2606
+ const epsilon: f32 = 0.01;
2607
+ const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 };
2608
+
2609
+ // Check non-highlighted cells
2610
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2611
+ try std.testing.expectEqual(@as(u32, '5'), cell_0.char);
2612
+ const has_yellow_0 = @abs(cell_0.bg[0] - yellow_bg[0]) < epsilon and
2613
+ @abs(cell_0.bg[1] - yellow_bg[1]) < epsilon and
2614
+ @abs(cell_0.bg[2] - yellow_bg[2]) < epsilon;
2615
+ try std.testing.expect(!has_yellow_0);
2616
+
2617
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
2618
+ try std.testing.expectEqual(@as(u32, '6'), cell_1.char);
2619
+ const has_yellow_1 = @abs(cell_1.bg[0] - yellow_bg[0]) < epsilon and
2620
+ @abs(cell_1.bg[1] - yellow_bg[1]) < epsilon and
2621
+ @abs(cell_1.bg[2] - yellow_bg[2]) < epsilon;
2622
+ try std.testing.expect(!has_yellow_1);
2623
+
2624
+ // Check highlighted cells
2625
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
2626
+ try std.testing.expectEqual(@as(u32, '7'), cell_2.char);
2627
+ const has_yellow_2 = @abs(cell_2.bg[0] - yellow_bg[0]) < epsilon and
2628
+ @abs(cell_2.bg[1] - yellow_bg[1]) < epsilon and
2629
+ @abs(cell_2.bg[2] - yellow_bg[2]) < epsilon;
2630
+ try std.testing.expect(has_yellow_2);
2631
+
2632
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
2633
+ try std.testing.expectEqual(@as(u32, '8'), cell_3.char);
2634
+ const has_yellow_3 = @abs(cell_3.bg[0] - yellow_bg[0]) < epsilon and
2635
+ @abs(cell_3.bg[1] - yellow_bg[1]) < epsilon and
2636
+ @abs(cell_3.bg[2] - yellow_bg[2]) < epsilon;
2637
+ try std.testing.expect(has_yellow_3);
2638
+
2639
+ const cell_6 = opt_buffer.get(6, 0) orelse unreachable;
2640
+ try std.testing.expectEqual(@as(u32, 'B'), cell_6.char);
2641
+ const has_yellow_6 = @abs(cell_6.bg[0] - yellow_bg[0]) < epsilon and
2642
+ @abs(cell_6.bg[1] - yellow_bg[1]) < epsilon and
2643
+ @abs(cell_6.bg[2] - yellow_bg[2]) < epsilon;
2644
+ try std.testing.expect(has_yellow_6);
2645
+
2646
+ // Check cells after selection
2647
+ const cell_7 = opt_buffer.get(7, 0) orelse unreachable;
2648
+ try std.testing.expectEqual(@as(u32, 'C'), cell_7.char);
2649
+ const has_yellow_7 = @abs(cell_7.bg[0] - yellow_bg[0]) < epsilon and
2650
+ @abs(cell_7.bg[1] - yellow_bg[1]) < epsilon and
2651
+ @abs(cell_7.bg[2] - yellow_bg[2]) < epsilon;
2652
+ try std.testing.expect(!has_yellow_7);
2653
+ }
2654
+
2655
+ test "drawTextBuffer - syntax highlight respects truncation" {
2656
+ const pool = gp.initGlobalPool(std.testing.allocator);
2657
+ defer gp.deinitGlobalPool();
2658
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2659
+ defer link.deinitGlobalLinkPool();
2660
+
2661
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2662
+ defer tb.deinit();
2663
+
2664
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2665
+ defer view.deinit();
2666
+
2667
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2668
+ defer style.deinit();
2669
+ tb.setSyntaxStyle(style);
2670
+
2671
+ const red_style = try style.registerStyle("red", RGBA{ 1.0, 0.0, 0.0, 1.0 }, null, 0);
2672
+ const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0);
2673
+
2674
+ try tb.setText("0123456789ABCDEFGHIJ");
2675
+ try tb.addHighlightByCharRange(4, 7, red_style, 1, 0); // highlight "456"
2676
+ try tb.addHighlightByCharRange(16, 20, green_style, 1, 0); // highlight "GHIJ"
2677
+
2678
+ view.setWrapMode(.none);
2679
+ view.setWrapWidth(null);
2680
+ view.setTruncate(true);
2681
+ view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 1 });
2682
+
2683
+ var opt_buffer = try OptimizedBuffer.init(
2684
+ std.testing.allocator,
2685
+ 10,
2686
+ 1,
2687
+ .{ .pool = pool, .width_method = .unicode },
2688
+ );
2689
+ defer opt_buffer.deinit();
2690
+
2691
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2692
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2693
+
2694
+ const epsilon: f32 = 0.01;
2695
+
2696
+ const prefix_cell = opt_buffer.get(1, 0) orelse unreachable;
2697
+ try std.testing.expectEqual(@as(u32, '1'), prefix_cell.char);
2698
+ try std.testing.expect(@abs(prefix_cell.fg[0] - 1.0) < epsilon);
2699
+ try std.testing.expect(@abs(prefix_cell.fg[1] - 1.0) < epsilon);
2700
+ try std.testing.expect(@abs(prefix_cell.fg[2] - 1.0) < epsilon);
2701
+
2702
+ const ellipsis_cell = opt_buffer.get(3, 0) orelse unreachable;
2703
+ try std.testing.expectEqual(@as(u32, '.'), ellipsis_cell.char);
2704
+ try std.testing.expect(@abs(ellipsis_cell.fg[0] - 1.0) < epsilon);
2705
+ try std.testing.expect(@abs(ellipsis_cell.fg[1] - 1.0) < epsilon);
2706
+ try std.testing.expect(@abs(ellipsis_cell.fg[2] - 1.0) < epsilon);
2707
+
2708
+ const suffix_cell = opt_buffer.get(6, 0) orelse unreachable;
2709
+ try std.testing.expectEqual(@as(u32, 'G'), suffix_cell.char);
2710
+ try std.testing.expect(@abs(suffix_cell.fg[0] - 0.0) < epsilon);
2711
+ try std.testing.expect(@abs(suffix_cell.fg[1] - 1.0) < epsilon);
2712
+ try std.testing.expect(@abs(suffix_cell.fg[2] - 0.0) < epsilon);
2713
+ }
2714
+
2715
+ test "drawTextBuffer - highlight spanning ellipsis continues on suffix" {
2716
+ const pool = gp.initGlobalPool(std.testing.allocator);
2717
+ defer gp.deinitGlobalPool();
2718
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2719
+ defer link.deinitGlobalLinkPool();
2720
+
2721
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2722
+ defer tb.deinit();
2723
+
2724
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2725
+ defer view.deinit();
2726
+
2727
+ const style = try ss.SyntaxStyle.init(std.testing.allocator);
2728
+ defer style.deinit();
2729
+ tb.setSyntaxStyle(style);
2730
+
2731
+ const magenta_style = try style.registerStyle("magenta", RGBA{ 1.0, 0.0, 1.0, 1.0 }, null, 0);
2732
+ const green_style = try style.registerStyle("green", RGBA{ 0.0, 1.0, 0.0, 1.0 }, null, 0);
2733
+
2734
+ try tb.setText("0123456789ABCDEFGHIJ");
2735
+ try tb.addHighlightByCharRange(2, 18, magenta_style, 1, 0); // spans through ellipsis
2736
+ try tb.addHighlightByCharRange(18, 20, green_style, 2, 0); // suffix highlight
2737
+
2738
+ view.setWrapMode(.none);
2739
+ view.setWrapWidth(null);
2740
+ view.setTruncate(true);
2741
+ view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 1 });
2742
+
2743
+ var opt_buffer = try OptimizedBuffer.init(
2744
+ std.testing.allocator,
2745
+ 10,
2746
+ 1,
2747
+ .{ .pool = pool, .width_method = .unicode },
2748
+ );
2749
+ defer opt_buffer.deinit();
2750
+
2751
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2752
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2753
+
2754
+ const epsilon: f32 = 0.01;
2755
+
2756
+ const ellipsis_cell = opt_buffer.get(3, 0) orelse unreachable;
2757
+ try std.testing.expectEqual(@as(u32, '.'), ellipsis_cell.char);
2758
+ try std.testing.expect(@abs(ellipsis_cell.fg[0] - 1.0) < epsilon);
2759
+ try std.testing.expect(@abs(ellipsis_cell.fg[1] - 1.0) < epsilon);
2760
+ try std.testing.expect(@abs(ellipsis_cell.fg[2] - 1.0) < epsilon);
2761
+
2762
+ const suffix_magenta = opt_buffer.get(6, 0) orelse unreachable;
2763
+ try std.testing.expectEqual(@as(u32, 'G'), suffix_magenta.char);
2764
+ try std.testing.expect(@abs(suffix_magenta.fg[0] - 1.0) < epsilon);
2765
+ try std.testing.expect(@abs(suffix_magenta.fg[1] - 0.0) < epsilon);
2766
+ try std.testing.expect(@abs(suffix_magenta.fg[2] - 1.0) < epsilon);
2767
+
2768
+ const suffix_green = opt_buffer.get(8, 0) orelse unreachable;
2769
+ try std.testing.expectEqual(@as(u32, 'I'), suffix_green.char);
2770
+ try std.testing.expect(@abs(suffix_green.fg[0] - 0.0) < epsilon);
2771
+ try std.testing.expect(@abs(suffix_green.fg[1] - 1.0) < epsilon);
2772
+ try std.testing.expect(@abs(suffix_green.fg[2] - 0.0) < epsilon);
2773
+ }
2774
+
2775
+ test "drawTextBuffer - selection respects truncation" {
2776
+ const pool = gp.initGlobalPool(std.testing.allocator);
2777
+ defer gp.deinitGlobalPool();
2778
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2779
+ defer link.deinitGlobalLinkPool();
2780
+
2781
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2782
+ defer tb.deinit();
2783
+
2784
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2785
+ defer view.deinit();
2786
+
2787
+ // Text: "0123456789ABCDEFGHIJ" (len 20)
2788
+ // With width 10, truncation should render: "012...GHIJ"
2789
+ try tb.setText("0123456789ABCDEFGHIJ");
2790
+
2791
+ view.setWrapMode(.none);
2792
+ view.setWrapWidth(null);
2793
+ view.setTruncate(true);
2794
+ view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 1 });
2795
+
2796
+ // Select across the ellipsis and suffix
2797
+ view.setSelection(2, 19, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 });
2798
+
2799
+ var opt_buffer = try OptimizedBuffer.init(
2800
+ std.testing.allocator,
2801
+ 10,
2802
+ 1,
2803
+ .{ .pool = pool, .width_method = .unicode },
2804
+ );
2805
+ defer opt_buffer.deinit();
2806
+
2807
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2808
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2809
+
2810
+ const epsilon: f32 = 0.01;
2811
+ const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 };
2812
+
2813
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
2814
+ try std.testing.expectEqual(@as(u32, '0'), cell_0.char);
2815
+ const has_yellow_0 = @abs(cell_0.bg[0] - yellow_bg[0]) < epsilon and
2816
+ @abs(cell_0.bg[1] - yellow_bg[1]) < epsilon and
2817
+ @abs(cell_0.bg[2] - yellow_bg[2]) < epsilon;
2818
+ try std.testing.expect(!has_yellow_0);
2819
+
2820
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
2821
+ try std.testing.expectEqual(@as(u32, '.'), cell_3.char);
2822
+ const has_yellow_3 = @abs(cell_3.bg[0] - yellow_bg[0]) < epsilon and
2823
+ @abs(cell_3.bg[1] - yellow_bg[1]) < epsilon and
2824
+ @abs(cell_3.bg[2] - yellow_bg[2]) < epsilon;
2825
+ try std.testing.expect(has_yellow_3);
2826
+
2827
+ const cell_6 = opt_buffer.get(6, 0) orelse unreachable;
2828
+ try std.testing.expectEqual(@as(u32, 'G'), cell_6.char);
2829
+ const has_yellow_6 = @abs(cell_6.bg[0] - yellow_bg[0]) < epsilon and
2830
+ @abs(cell_6.bg[1] - yellow_bg[1]) < epsilon and
2831
+ @abs(cell_6.bg[2] - yellow_bg[2]) < epsilon;
2832
+ try std.testing.expect(has_yellow_6);
2833
+
2834
+ const cell_8 = opt_buffer.get(8, 0) orelse unreachable;
2835
+ try std.testing.expectEqual(@as(u32, 'I'), cell_8.char);
2836
+ const has_yellow_8 = @abs(cell_8.bg[0] - yellow_bg[0]) < epsilon and
2837
+ @abs(cell_8.bg[1] - yellow_bg[1]) < epsilon and
2838
+ @abs(cell_8.bg[2] - yellow_bg[2]) < epsilon;
2839
+ try std.testing.expect(has_yellow_8);
2840
+
2841
+ const cell_9 = opt_buffer.get(9, 0) orelse unreachable;
2842
+ try std.testing.expectEqual(@as(u32, 'J'), cell_9.char);
2843
+ const has_yellow_9 = @abs(cell_9.bg[0] - yellow_bg[0]) < epsilon and
2844
+ @abs(cell_9.bg[1] - yellow_bg[1]) < epsilon and
2845
+ @abs(cell_9.bg[2] - yellow_bg[2]) < epsilon;
2846
+ try std.testing.expect(!has_yellow_9);
2847
+ }
2848
+
2849
+ test "drawTextBuffer - truncation selection does not overshoot multiline" {
2850
+ const pool = gp.initGlobalPool(std.testing.allocator);
2851
+ defer gp.deinitGlobalPool();
2852
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2853
+ defer link.deinitGlobalLinkPool();
2854
+
2855
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2856
+ defer tb.deinit();
2857
+
2858
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2859
+ defer view.deinit();
2860
+
2861
+ try tb.setText(
2862
+ "abcdefghijABCDEFGHIJ\n" ++
2863
+ "klmnopqrstKLMNOPQRST",
2864
+ );
2865
+
2866
+ view.setWrapMode(.none);
2867
+ view.setWrapWidth(null);
2868
+ view.setTruncate(true);
2869
+ view.setViewport(.{ .x = 0, .y = 0, .width = 10, .height = 2 });
2870
+
2871
+ // Select from line 1 col 2 through line 2 col 5 (exclusive)
2872
+ view.setSelection(2, 26, RGBA{ 1.0, 1.0, 0.0, 1.0 }, RGBA{ 0.0, 0.0, 0.0, 1.0 });
2873
+
2874
+ var opt_buffer = try OptimizedBuffer.init(
2875
+ std.testing.allocator,
2876
+ 10,
2877
+ 2,
2878
+ .{ .pool = pool, .width_method = .unicode },
2879
+ );
2880
+ defer opt_buffer.deinit();
2881
+
2882
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2883
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2884
+
2885
+ const epsilon: f32 = 0.01;
2886
+ const yellow_bg = RGBA{ 1.0, 1.0, 0.0, 1.0 };
2887
+
2888
+ const line2_cell_0 = opt_buffer.get(0, 1) orelse unreachable;
2889
+ try std.testing.expectEqual(@as(u32, 'k'), line2_cell_0.char);
2890
+ const has_yellow_line2_0 = @abs(line2_cell_0.bg[0] - yellow_bg[0]) < epsilon and
2891
+ @abs(line2_cell_0.bg[1] - yellow_bg[1]) < epsilon and
2892
+ @abs(line2_cell_0.bg[2] - yellow_bg[2]) < epsilon;
2893
+ try std.testing.expect(has_yellow_line2_0);
2894
+
2895
+ const line2_cell_2 = opt_buffer.get(2, 1) orelse unreachable;
2896
+ try std.testing.expectEqual(@as(u32, 'm'), line2_cell_2.char);
2897
+ const has_yellow_line2_2 = @abs(line2_cell_2.bg[0] - yellow_bg[0]) < epsilon and
2898
+ @abs(line2_cell_2.bg[1] - yellow_bg[1]) < epsilon and
2899
+ @abs(line2_cell_2.bg[2] - yellow_bg[2]) < epsilon;
2900
+ try std.testing.expect(has_yellow_line2_2);
2901
+
2902
+ const line2_cell_6 = opt_buffer.get(6, 1) orelse unreachable;
2903
+ try std.testing.expectEqual(@as(u32, 'Q'), line2_cell_6.char);
2904
+ const has_yellow_line2_6 = @abs(line2_cell_6.bg[0] - yellow_bg[0]) < epsilon and
2905
+ @abs(line2_cell_6.bg[1] - yellow_bg[1]) < epsilon and
2906
+ @abs(line2_cell_6.bg[2] - yellow_bg[2]) < epsilon;
2907
+ try std.testing.expect(!has_yellow_line2_6);
2908
+ }
2909
+
2910
+ test "drawTextBuffer - Chinese text with wrapping no stray bytes" {
2911
+ const pool = gp.initGlobalPool(std.testing.allocator);
2912
+ defer gp.deinitGlobalPool();
2913
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2914
+ defer link.deinitGlobalLinkPool();
2915
+
2916
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2917
+ defer tb.deinit();
2918
+
2919
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2920
+ defer view.deinit();
2921
+
2922
+ const text =
2923
+ \\前后端分离 - TypeScript逻辑 + Go TUI界面
2924
+ \\组件化设计 - 基于tview的可复用组件
2925
+ \\渐进式交互 - 逐步披露避免信息过载
2926
+ \\智能上下文 - 基于项目状态动态生成问题
2927
+ \\丰富的问题类型 - 支持6种不同的交互形式
2928
+ \\完整的验证 - 实时输入验证和错误处理
2929
+ ;
2930
+
2931
+ try tb.setText(text);
2932
+
2933
+ // Try word wrapping with a width that might split multibyte chars
2934
+ view.setWrapMode(.word);
2935
+ view.setWrapWidth(35);
2936
+ view.updateVirtualLines();
2937
+
2938
+ var opt_buffer = try OptimizedBuffer.init(
2939
+ std.testing.allocator,
2940
+ 40,
2941
+ 20,
2942
+ .{ .pool = pool, .width_method = .unicode },
2943
+ );
2944
+ defer opt_buffer.deinit();
2945
+
2946
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
2947
+ try opt_buffer.drawTextBuffer(view, 0, 0);
2948
+
2949
+ // Write the rendered buffer to check for stray bytes
2950
+ var out_buffer: [2000]u8 = undefined;
2951
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
2952
+ const result = out_buffer[0..written];
2953
+
2954
+ // Verify the output is valid UTF-8
2955
+ try std.testing.expect(std.unicode.utf8ValidateSlice(result));
2956
+
2957
+ // Verify that the original text is contained in the output (with possible spaces/newlines from wrapping)
2958
+ try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证") != null);
2959
+ try std.testing.expect(std.mem.indexOf(u8, result, "实时输入验证和错误处理") != null);
2960
+
2961
+ // Check specific problematic line - should NOT contain stray bytes
2962
+ // The line should be present correctly (possibly wrapped with spaces)
2963
+ // But there should be NO stray å character or partial UTF-8 sequences
2964
+ try std.testing.expect(std.mem.indexOf(u8, result, "å式") == null); // This should NOT appear
2965
+ try std.testing.expect(std.mem.indexOf(u8, result, "å") == null); // No stray partial bytes
2966
+
2967
+ // Verify the problematic characters appear correctly
2968
+ try std.testing.expect(std.mem.indexOf(u8, result, "形式") != null);
2969
+ }
2970
+
2971
+ test "drawTextBuffer - Chinese text WITHOUT wrapping no duplicate chunks" {
2972
+ const pool = gp.initGlobalPool(std.testing.allocator);
2973
+ defer gp.deinitGlobalPool();
2974
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
2975
+ defer link.deinitGlobalLinkPool();
2976
+
2977
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
2978
+ defer tb.deinit();
2979
+
2980
+ var view = try TextBufferView.init(std.testing.allocator, tb);
2981
+ defer view.deinit();
2982
+
2983
+ const text =
2984
+ \\前后端分离 - TypeScript逻辑 + Go TUI界面
2985
+ \\组件化设计 - 基于tview的可复用组件
2986
+ \\渐进式交互 - 逐步披露避免信息过载
2987
+ \\智能上下文 - 基于项目状态动态生成问题
2988
+ \\丰富的问题类型 - 支持6种不同的交互形式
2989
+ \\完整的验证 - 实时输入验证和错误处理
2990
+ ;
2991
+
2992
+ try tb.setText(text);
2993
+
2994
+ // Word wrap mode but with wide width so nothing actually wraps
2995
+ view.setWrapMode(.word);
2996
+ view.setWrapWidth(80);
2997
+ view.updateVirtualLines();
2998
+
2999
+ const vlines = view.getVirtualLines();
3000
+
3001
+ // Check each virtual line - should have exactly ONE chunk when width is large enough
3002
+ for (vlines) |vline| {
3003
+ // Each line should have exactly ONE chunk when not actually wrapping
3004
+ try std.testing.expectEqual(@as(usize, 1), vline.chunks.items.len);
3005
+ }
3006
+
3007
+ var opt_buffer = try OptimizedBuffer.init(
3008
+ std.testing.allocator,
3009
+ 80,
3010
+ 10,
3011
+ .{ .pool = pool, .width_method = .unicode },
3012
+ );
3013
+ defer opt_buffer.deinit();
3014
+
3015
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
3016
+ try opt_buffer.drawTextBuffer(view, 0, 0);
3017
+
3018
+ // Write the rendered buffer
3019
+ var out_buffer: [2000]u8 = undefined;
3020
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
3021
+ const result = out_buffer[0..written];
3022
+
3023
+ // Verify the output is valid UTF-8
3024
+ try std.testing.expect(std.unicode.utf8ValidateSlice(result));
3025
+
3026
+ // Should NOT contain stray bytes
3027
+ try std.testing.expect(std.mem.indexOf(u8, result, "å") == null);
3028
+
3029
+ // All text should be present
3030
+ try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证 - 实时输入验证和错误处理") != null);
3031
+ }
3032
+
3033
+ test "drawTextBuffer - Chinese text with CHAR wrapping no stray bytes" {
3034
+ const pool = gp.initGlobalPool(std.testing.allocator);
3035
+ defer gp.deinitGlobalPool();
3036
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
3037
+ defer link.deinitGlobalLinkPool();
3038
+
3039
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
3040
+ defer tb.deinit();
3041
+
3042
+ var view = try TextBufferView.init(std.testing.allocator, tb);
3043
+ defer view.deinit();
3044
+
3045
+ const text =
3046
+ \\前后端分离 - TypeScript逻辑 + Go TUI界面
3047
+ \\组件化设计 - 基于tview的可复用组件
3048
+ \\渐进式交互 - 逐步披露避免信息过载
3049
+ \\智能上下文 - 基于项目状态动态生成问题
3050
+ \\丰富的问题类型 - 支持6种不同的交互形式
3051
+ \\完整的验证 - 实时输入验证和错误处理
3052
+ ;
3053
+
3054
+ try tb.setText(text);
3055
+
3056
+ // Char wrapping with a width that might split multibyte chars
3057
+ view.setWrapMode(.char);
3058
+ view.setWrapWidth(35);
3059
+ view.updateVirtualLines();
3060
+
3061
+ var opt_buffer = try OptimizedBuffer.init(
3062
+ std.testing.allocator,
3063
+ 35,
3064
+ 20,
3065
+ .{ .pool = pool, .width_method = .unicode },
3066
+ );
3067
+ defer opt_buffer.deinit();
3068
+
3069
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
3070
+ try opt_buffer.drawTextBuffer(view, 0, 0);
3071
+
3072
+ // Write the rendered buffer to check for stray bytes
3073
+ var out_buffer: [2000]u8 = undefined;
3074
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
3075
+ const result = out_buffer[0..written];
3076
+
3077
+ // Verify the output is valid UTF-8
3078
+ try std.testing.expect(std.unicode.utf8ValidateSlice(result));
3079
+
3080
+ // Should NOT contain stray bytes
3081
+ try std.testing.expect(std.mem.indexOf(u8, result, "å") == null);
3082
+
3083
+ // Verify the problematic characters appear correctly
3084
+ try std.testing.expect(std.mem.indexOf(u8, result, "形式") != null);
3085
+ try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证") != null);
3086
+ }
3087
+
3088
+ test "drawTextBuffer - word wrap CJK mixed text without break points" {
3089
+ const pool = gp.initGlobalPool(std.testing.allocator);
3090
+ defer gp.deinitGlobalPool();
3091
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
3092
+ defer link.deinitGlobalLinkPool();
3093
+
3094
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
3095
+ defer tb.deinit();
3096
+
3097
+ var view = try TextBufferView.init(std.testing.allocator, tb);
3098
+ defer view.deinit();
3099
+
3100
+ try tb.setText("한글,English,中文,日本語,混合,Test,測試,テスト,가나다,ABC,一二三,あいう,라마바,DEF,四五六,えおか");
3101
+
3102
+ view.setWrapMode(.word);
3103
+ view.setWrapWidth(20);
3104
+ view.updateVirtualLines();
3105
+
3106
+ var opt_buffer = try OptimizedBuffer.init(
3107
+ std.testing.allocator,
3108
+ 30,
3109
+ 20,
3110
+ .{ .pool = pool, .width_method = .unicode },
3111
+ );
3112
+ defer opt_buffer.deinit();
3113
+
3114
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
3115
+ try opt_buffer.drawTextBuffer(view, 0, 0);
3116
+
3117
+ var out_buffer: [1000]u8 = undefined;
3118
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
3119
+ const result = out_buffer[0..written];
3120
+
3121
+ const vlines = view.getVirtualLines();
3122
+ try std.testing.expect(vlines.len > 1);
3123
+
3124
+ var y: u32 = 0;
3125
+ while (y < vlines.len) : (y += 1) {
3126
+ const first_cell = opt_buffer.get(0, y);
3127
+ if (first_cell) |cell| {
3128
+ try std.testing.expect(!gp.isContinuationChar(cell.char));
3129
+ }
3130
+ }
3131
+
3132
+ try std.testing.expect(std.unicode.utf8ValidateSlice(result));
3133
+ }
3134
+
3135
+ test "drawTextBuffer - word wrap CJK text preserves UTF-8 boundaries" {
3136
+ const pool = gp.initGlobalPool(std.testing.allocator);
3137
+ defer gp.deinitGlobalPool();
3138
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
3139
+ defer link.deinitGlobalLinkPool();
3140
+
3141
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
3142
+ defer tb.deinit();
3143
+
3144
+ var view = try TextBufferView.init(std.testing.allocator, tb);
3145
+ defer view.deinit();
3146
+
3147
+ try tb.setText("한글,English,中文,日本語,混合,Test,測試,テスト,가나다,ABC,一二三,あいう,라마바,DEF,四五六,えおか");
3148
+
3149
+ view.setWrapMode(.word);
3150
+ view.setWrapWidth(20);
3151
+ view.updateVirtualLines();
3152
+
3153
+ var opt_buffer = try OptimizedBuffer.init(
3154
+ std.testing.allocator,
3155
+ 30,
3156
+ 20,
3157
+ .{ .pool = pool, .width_method = .unicode },
3158
+ );
3159
+ defer opt_buffer.deinit();
3160
+
3161
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
3162
+ try opt_buffer.drawTextBuffer(view, 0, 0);
3163
+
3164
+ var out_buffer: [1000]u8 = undefined;
3165
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
3166
+ const result = out_buffer[0..written];
3167
+
3168
+ try std.testing.expect(std.unicode.utf8ValidateSlice(result));
3169
+ try std.testing.expect(std.mem.indexOf(u8, result, "ä") == null);
3170
+
3171
+ var i: usize = 0;
3172
+ while (i < result.len) : (i += 1) {
3173
+ if (result[i] == 0xE4) {
3174
+ if (i + 1 >= result.len) {
3175
+ return error.TestFailed;
3176
+ }
3177
+ const next_byte = result[i + 1];
3178
+ if (next_byte < 0x80 or next_byte > 0xBF) {
3179
+ return error.TestFailed;
3180
+ }
3181
+ }
3182
+ }
3183
+
3184
+ const vlines = view.getVirtualLines();
3185
+ var y: u32 = 0;
3186
+ while (y < vlines.len) : (y += 1) {
3187
+ const first_cell = opt_buffer.get(0, y);
3188
+ if (first_cell) |cell| {
3189
+ try std.testing.expect(!gp.isContinuationChar(cell.char));
3190
+ }
3191
+ }
3192
+ }
3193
+
3194
+ test "drawTextBuffer - Thai ว่ grapheme in quotes occupies one cell" {
3195
+ const pool = gp.initGlobalPool(std.testing.allocator);
3196
+ defer gp.deinitGlobalPool();
3197
+ const link_pool = link.initGlobalLinkPool(std.testing.allocator);
3198
+ defer link.deinitGlobalLinkPool();
3199
+
3200
+ var tb = try TextBuffer.init(std.testing.allocator, pool, link_pool, .unicode);
3201
+ defer tb.deinit();
3202
+
3203
+ var view = try TextBufferView.init(std.testing.allocator, tb);
3204
+ defer view.deinit();
3205
+
3206
+ try tb.setText("\"ว่\"");
3207
+
3208
+ var opt_buffer = try OptimizedBuffer.init(
3209
+ std.testing.allocator,
3210
+ 10,
3211
+ 1,
3212
+ .{ .pool = pool, .width_method = .unicode },
3213
+ );
3214
+ defer opt_buffer.deinit();
3215
+
3216
+ try opt_buffer.clear(.{ 0.0, 0.0, 0.0, 1.0 }, 32);
3217
+ try opt_buffer.drawTextBuffer(view, 0, 0);
3218
+
3219
+ const cell_0 = opt_buffer.get(0, 0) orelse unreachable;
3220
+ try std.testing.expectEqual(@as(u32, '"'), cell_0.char);
3221
+
3222
+ const cell_1 = opt_buffer.get(1, 0) orelse unreachable;
3223
+ try std.testing.expect(cell_1.char != ' ');
3224
+ try std.testing.expect(cell_1.char != '"');
3225
+
3226
+ const cell_2 = opt_buffer.get(2, 0) orelse unreachable;
3227
+ try std.testing.expectEqual(@as(u32, '"'), cell_2.char);
3228
+
3229
+ const cell_3 = opt_buffer.get(3, 0) orelse unreachable;
3230
+ try std.testing.expectEqual(@as(u32, ' '), cell_3.char);
3231
+
3232
+ var out_buffer: [100]u8 = undefined;
3233
+ const written = try opt_buffer.writeResolvedChars(&out_buffer, false);
3234
+ const result = out_buffer[0..written];
3235
+
3236
+ try std.testing.expect(std.mem.indexOf(u8, result, "\"ว่\"") != null);
3237
+ }