@xincli/opentui-core 0.4.6

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 (476) hide show
  1. package/README.md +63 -0
  2. package/dev/keypress-debug-renderer.ts +148 -0
  3. package/dev/keypress-debug.ts +43 -0
  4. package/dev/print-env-vars.ts +32 -0
  5. package/dev/test-tmux-graphics-334.sh +68 -0
  6. package/dev/test-tmux-theme-queries.sh +151 -0
  7. package/dev/thai-debug-test.ts +68 -0
  8. package/dev/theme-mode.ts +290 -0
  9. package/docs/development.md +144 -0
  10. package/package.json +102 -0
  11. package/prebuilt/aarch64-android/libopentui.so +0 -0
  12. package/scripts/build-android.ts +226 -0
  13. package/scripts/build-native-termux.sh +525 -0
  14. package/scripts/build.ts +530 -0
  15. package/scripts/dist-test.ts +312 -0
  16. package/scripts/package-prebuilt.ts +110 -0
  17. package/scripts/publish.ts +60 -0
  18. package/scripts/test-node-hook.mjs +16 -0
  19. package/scripts/test-node.ts +241 -0
  20. package/scripts/vendor-deps.sh +73 -0
  21. package/src/NativeSpanFeed.ts +375 -0
  22. package/src/Renderable.ts +1878 -0
  23. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  24. package/src/animation/Timeline.test.ts +2709 -0
  25. package/src/animation/Timeline.ts +598 -0
  26. package/src/ansi.ts +22 -0
  27. package/src/audio.ts +479 -0
  28. package/src/benchmark/attenuation-benchmark.ts +81 -0
  29. package/src/benchmark/audio-playback-benchmark.ts +344 -0
  30. package/src/benchmark/box-draw-benchmark.ts +1042 -0
  31. package/src/benchmark/colormatrix-benchmark.ts +123 -0
  32. package/src/benchmark/gain-benchmark.ts +80 -0
  33. package/src/benchmark/latest-all-bench-run.json +707 -0
  34. package/src/benchmark/latest-async-bench-run.json +336 -0
  35. package/src/benchmark/latest-default-bench-run.json +657 -0
  36. package/src/benchmark/latest-large-bench-run.json +707 -0
  37. package/src/benchmark/latest-quick-bench-run.json +207 -0
  38. package/src/benchmark/layout-benchmark.ts +2547 -0
  39. package/src/benchmark/markdown-benchmark.ts +1796 -0
  40. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  41. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  42. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  43. package/src/benchmark/native-span-feed-compare.ts +280 -0
  44. package/src/benchmark/render-traversal-benchmark.ts +928 -0
  45. package/src/benchmark/text-buffer-render-benchmark.ts +874 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +292 -0
  48. package/src/buffer.ts +564 -0
  49. package/src/console.test.ts +629 -0
  50. package/src/console.ts +1253 -0
  51. package/src/edit-buffer.test.ts +1852 -0
  52. package/src/edit-buffer.ts +417 -0
  53. package/src/editor-view.test.ts +1056 -0
  54. package/src/editor-view.ts +290 -0
  55. package/src/index.ts +25 -0
  56. package/src/lib/KeyHandler.integration.test.ts +292 -0
  57. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  58. package/src/lib/KeyHandler.test.ts +662 -0
  59. package/src/lib/KeyHandler.ts +222 -0
  60. package/src/lib/RGBA.test.ts +60 -0
  61. package/src/lib/RGBA.ts +343 -0
  62. package/src/lib/ascii.font.ts +330 -0
  63. package/src/lib/border.test.ts +83 -0
  64. package/src/lib/border.ts +170 -0
  65. package/src/lib/bunfs.test.ts +27 -0
  66. package/src/lib/bunfs.ts +18 -0
  67. package/src/lib/clipboard.test.ts +63 -0
  68. package/src/lib/clipboard.ts +41 -0
  69. package/src/lib/clock.ts +35 -0
  70. package/src/lib/data-paths.test.ts +133 -0
  71. package/src/lib/data-paths.ts +109 -0
  72. package/src/lib/debounce.ts +106 -0
  73. package/src/lib/detect-links.test.ts +98 -0
  74. package/src/lib/detect-links.ts +56 -0
  75. package/src/lib/env.test.ts +228 -0
  76. package/src/lib/env.ts +209 -0
  77. package/src/lib/extmarks-history.ts +51 -0
  78. package/src/lib/extmarks-multiwidth.test.ts +324 -0
  79. package/src/lib/extmarks.test.ts +3547 -0
  80. package/src/lib/extmarks.ts +861 -0
  81. package/src/lib/fonts/block.json +405 -0
  82. package/src/lib/fonts/grid.json +265 -0
  83. package/src/lib/fonts/huge.json +741 -0
  84. package/src/lib/fonts/pallet.json +314 -0
  85. package/src/lib/fonts/shade.json +591 -0
  86. package/src/lib/fonts/slick.json +321 -0
  87. package/src/lib/fonts/tiny.json +69 -0
  88. package/src/lib/hast-styled-text.ts +59 -0
  89. package/src/lib/index.ts +22 -0
  90. package/src/lib/keybinding.internal.test.ts +342 -0
  91. package/src/lib/keybinding.internal.ts +182 -0
  92. package/src/lib/objects-in-viewport.test.ts +789 -0
  93. package/src/lib/objects-in-viewport.ts +153 -0
  94. package/src/lib/output.capture.ts +58 -0
  95. package/src/lib/parse.keypress-kitty.protocol.test.ts +347 -0
  96. package/src/lib/parse.keypress-kitty.test.ts +712 -0
  97. package/src/lib/parse.keypress-kitty.ts +470 -0
  98. package/src/lib/parse.keypress.test.ts +2020 -0
  99. package/src/lib/parse.keypress.ts +480 -0
  100. package/src/lib/parse.mouse.test.ts +552 -0
  101. package/src/lib/parse.mouse.ts +232 -0
  102. package/src/lib/paste.ts +18 -0
  103. package/src/lib/queue.ts +65 -0
  104. package/src/lib/render-geometry.ts +36 -0
  105. package/src/lib/renderable.validations.test.ts +87 -0
  106. package/src/lib/renderable.validations.ts +83 -0
  107. package/src/lib/scroll-acceleration.ts +98 -0
  108. package/src/lib/selection.ts +261 -0
  109. package/src/lib/singleton.ts +34 -0
  110. package/src/lib/stdin-parser.test.ts +2439 -0
  111. package/src/lib/stdin-parser.ts +2007 -0
  112. package/src/lib/styled-text.ts +178 -0
  113. package/src/lib/terminal-capability-detection.test.ts +226 -0
  114. package/src/lib/terminal-capability-detection.ts +98 -0
  115. package/src/lib/terminal-palette.test.ts +954 -0
  116. package/src/lib/terminal-palette.ts +441 -0
  117. package/src/lib/tree-sitter/assets/README.md +119 -0
  118. package/src/lib/tree-sitter/assets/javascript/highlights.scm +205 -0
  119. package/src/lib/tree-sitter/assets/javascript/tree-sitter-javascript.wasm +0 -0
  120. package/src/lib/tree-sitter/assets/markdown/highlights.scm +150 -0
  121. package/src/lib/tree-sitter/assets/markdown/injections.scm +27 -0
  122. package/src/lib/tree-sitter/assets/markdown/tree-sitter-markdown.wasm +0 -0
  123. package/src/lib/tree-sitter/assets/markdown_inline/highlights.scm +115 -0
  124. package/src/lib/tree-sitter/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  125. package/src/lib/tree-sitter/assets/typescript/highlights.scm +604 -0
  126. package/src/lib/tree-sitter/assets/typescript/tree-sitter-typescript.wasm +0 -0
  127. package/src/lib/tree-sitter/assets/update.ts +360 -0
  128. package/src/lib/tree-sitter/assets/zig/highlights.scm +284 -0
  129. package/src/lib/tree-sitter/assets/zig/tree-sitter-zig.wasm +0 -0
  130. package/src/lib/tree-sitter/assets.d.ts +9 -0
  131. package/src/lib/tree-sitter/cache.test.ts +291 -0
  132. package/src/lib/tree-sitter/client.test.ts +1434 -0
  133. package/src/lib/tree-sitter/client.ts +847 -0
  134. package/src/lib/tree-sitter/default-parsers.ts +133 -0
  135. package/src/lib/tree-sitter/download-utils.ts +148 -0
  136. package/src/lib/tree-sitter/index.ts +75 -0
  137. package/src/lib/tree-sitter/parser.worker.ts +1098 -0
  138. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  139. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  140. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  141. package/src/lib/tree-sitter/types.ts +132 -0
  142. package/src/lib/tree-sitter/update-assets.ts +10 -0
  143. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  144. package/src/lib/tree-sitter-styled-text.ts +314 -0
  145. package/src/lib/validate-dir-name.ts +55 -0
  146. package/src/lib/yoga.options.test.ts +628 -0
  147. package/src/lib/yoga.options.ts +346 -0
  148. package/src/native-event-worker-repro.test.ts +48 -0
  149. package/src/native-event-worker-repro.worker.ts +9 -0
  150. package/src/native-handle.test.ts +221 -0
  151. package/src/platform/ffi.test.ts +516 -0
  152. package/src/platform/ffi.ts +653 -0
  153. package/src/platform/runtime.test.ts +52 -0
  154. package/src/platform/runtime.ts +121 -0
  155. package/src/platform/test.ts +45 -0
  156. package/src/platform/worker-handler-cleanup.fixture.ts +12 -0
  157. package/src/platform/worker-onmessage-startup.fixture.ts +10 -0
  158. package/src/platform/worker-startup.fixture.ts +9 -0
  159. package/src/platform/worker.node-test.ts +153 -0
  160. package/src/platform/worker.ts +425 -0
  161. package/src/plugins/core-slot.ts +579 -0
  162. package/src/plugins/registry.ts +402 -0
  163. package/src/plugins/types.ts +46 -0
  164. package/src/post/effects.ts +948 -0
  165. package/src/post/filters.ts +512 -0
  166. package/src/post/matrices.ts +288 -0
  167. package/src/renderables/ASCIIFont.ts +219 -0
  168. package/src/renderables/Box.test.ts +474 -0
  169. package/src/renderables/Box.ts +353 -0
  170. package/src/renderables/Code.test.ts +2388 -0
  171. package/src/renderables/Code.ts +555 -0
  172. package/src/renderables/Diff.regression.test.ts +381 -0
  173. package/src/renderables/Diff.test.ts +3212 -0
  174. package/src/renderables/Diff.ts +1278 -0
  175. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  176. package/src/renderables/EditBufferRenderable.ts +1152 -0
  177. package/src/renderables/FrameBuffer.ts +47 -0
  178. package/src/renderables/Input.test.ts +1310 -0
  179. package/src/renderables/Input.ts +273 -0
  180. package/src/renderables/LineNumberRenderable.ts +725 -0
  181. package/src/renderables/Markdown.ts +2144 -0
  182. package/src/renderables/ScrollBar.ts +423 -0
  183. package/src/renderables/ScrollBox.ts +903 -0
  184. package/src/renderables/Select.test.ts +1134 -0
  185. package/src/renderables/Select.ts +535 -0
  186. package/src/renderables/Slider.test.ts +456 -0
  187. package/src/renderables/Slider.ts +342 -0
  188. package/src/renderables/TabSelect.test.ts +238 -0
  189. package/src/renderables/TabSelect.ts +447 -0
  190. package/src/renderables/Text.selection-buffer.test.ts +118 -0
  191. package/src/renderables/Text.test.ts +2662 -0
  192. package/src/renderables/Text.ts +147 -0
  193. package/src/renderables/TextBufferRenderable.ts +504 -0
  194. package/src/renderables/TextNode.test.ts +1137 -0
  195. package/src/renderables/TextNode.ts +379 -0
  196. package/src/renderables/TextTable.test.ts +1411 -0
  197. package/src/renderables/TextTable.ts +1434 -0
  198. package/src/renderables/Textarea.ts +425 -0
  199. package/src/renderables/TimeToFirstDraw.ts +89 -0
  200. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  201. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  202. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  203. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  204. package/src/renderables/__tests__/Code.test.ts +55 -0
  205. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  206. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +827 -0
  207. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1906 -0
  208. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  209. package/src/renderables/__tests__/Markdown.code-colors.test.ts +288 -0
  210. package/src/renderables/__tests__/Markdown.test.ts +4175 -0
  211. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  212. package/src/renderables/__tests__/Textarea.buffer.test.ts +683 -0
  213. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  214. package/src/renderables/__tests__/Textarea.editing.test.ts +2120 -0
  215. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  216. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  217. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  218. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3275 -0
  219. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  220. package/src/renderables/__tests__/Textarea.rendering.test.ts +1927 -0
  221. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  222. package/src/renderables/__tests__/Textarea.selection.test.ts +1589 -0
  223. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  224. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  225. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  226. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  227. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  228. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  229. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  230. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  231. package/src/renderables/__tests__/markdown-parser.test.ts +241 -0
  232. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  233. package/src/renderables/composition/README.md +8 -0
  234. package/src/renderables/composition/VRenderable.ts +32 -0
  235. package/src/renderables/composition/constructs.ts +127 -0
  236. package/src/renderables/composition/vnode.ts +277 -0
  237. package/src/renderables/index.ts +23 -0
  238. package/src/renderables/markdown-parser.ts +79 -0
  239. package/src/renderer-theme-mode.ts +199 -0
  240. package/src/renderer.ts +4975 -0
  241. package/src/runtime-plugin-support-configure.ts +63 -0
  242. package/src/runtime-plugin-support.ts +13 -0
  243. package/src/runtime-plugin.ts +626 -0
  244. package/src/specs/terminal-startup.md +64 -0
  245. package/src/syntax-style.test.ts +841 -0
  246. package/src/syntax-style.ts +274 -0
  247. package/src/testing/README.md +219 -0
  248. package/src/testing/bun-test-node.ts +880 -0
  249. package/src/testing/capture-spans.test.ts +216 -0
  250. package/src/testing/integration.test.ts +276 -0
  251. package/src/testing/manual-clock.ts +117 -0
  252. package/src/testing/mock-keys.test.ts +1378 -0
  253. package/src/testing/mock-keys.ts +457 -0
  254. package/src/testing/mock-mouse.test.ts +218 -0
  255. package/src/testing/mock-mouse.ts +247 -0
  256. package/src/testing/mock-tree-sitter-client.ts +81 -0
  257. package/src/testing/spy.ts +13 -0
  258. package/src/testing/terminal-capabilities.ts +47 -0
  259. package/src/testing/test-recorder.test.ts +410 -0
  260. package/src/testing/test-recorder.ts +133 -0
  261. package/src/testing/test-renderer.ts +375 -0
  262. package/src/testing/test-renderer.wait.test.ts +278 -0
  263. package/src/testing/test-streams.ts +31 -0
  264. package/src/testing.ts +9 -0
  265. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  266. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  267. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  268. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  269. package/src/tests/allocator-stats.test.ts +53 -0
  270. package/src/tests/audio.test.ts +303 -0
  271. package/src/tests/destroy-during-render.test.ts +200 -0
  272. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  273. package/src/tests/destroy-on-exit.test.ts +62 -0
  274. package/src/tests/ffi-borrowed-pointer-callsites.test.ts +153 -0
  275. package/src/tests/hover-cursor.test.ts +98 -0
  276. package/src/tests/native-backed-measurement-lifecycle.test.ts +133 -0
  277. package/src/tests/native-backed-measurement-parity.test.ts +322 -0
  278. package/src/tests/native-span-feed-async.test.ts +173 -0
  279. package/src/tests/native-span-feed-close.test.ts +120 -0
  280. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  281. package/src/tests/native-span-feed-edge-cases.test.ts +384 -0
  282. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  283. package/src/tests/opacity.test.ts +123 -0
  284. package/src/tests/renderable.snapshot.test.ts +524 -0
  285. package/src/tests/renderable.test.ts +1851 -0
  286. package/src/tests/renderer.clock.test.ts +270 -0
  287. package/src/tests/renderer.console-startup.test.ts +2151 -0
  288. package/src/tests/renderer.control.test.ts +458 -0
  289. package/src/tests/renderer.core-slot-binding.test.ts +962 -0
  290. package/src/tests/renderer.cursor.test.ts +26 -0
  291. package/src/tests/renderer.custom-stdout.test.ts +1112 -0
  292. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  293. package/src/tests/renderer.focus-restore.test.ts +257 -0
  294. package/src/tests/renderer.focus.test.ts +329 -0
  295. package/src/tests/renderer.idle.test.ts +219 -0
  296. package/src/tests/renderer.input.test.ts +2615 -0
  297. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  298. package/src/tests/renderer.lifecycle.test.ts +34 -0
  299. package/src/tests/renderer.mouse.test.ts +1315 -0
  300. package/src/tests/renderer.notifications.test.ts +27 -0
  301. package/src/tests/renderer.palette.test.ts +1199 -0
  302. package/src/tests/renderer.remote.test.ts +247 -0
  303. package/src/tests/renderer.render-stats.test.ts +146 -0
  304. package/src/tests/renderer.scrollback-surface.test.ts +474 -0
  305. package/src/tests/renderer.selection.test.ts +138 -0
  306. package/src/tests/renderer.slot-registry.test.ts +684 -0
  307. package/src/tests/renderer.tracker.test.ts +202 -0
  308. package/src/tests/renderer.useMouse.test.ts +83 -0
  309. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  310. package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +48 -0
  311. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  312. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  313. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  314. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  315. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  316. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  317. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  318. package/src/tests/runtime-plugin-support-configure.fixture.ts +31 -0
  319. package/src/tests/runtime-plugin-support-late-addition.fixture.ts +17 -0
  320. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  321. package/src/tests/runtime-plugin-support.test.ts +54 -0
  322. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  323. package/src/tests/runtime-plugin.fixture.ts +40 -0
  324. package/src/tests/runtime-plugin.test.ts +369 -0
  325. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  326. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  327. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  328. package/src/tests/scrollbox.test.ts +1895 -0
  329. package/src/tests/wrap-resize-perf.test.ts +276 -0
  330. package/src/tests/yoga-api-coverage.test.ts +409 -0
  331. package/src/tests/yoga-callback-stress.test.ts +122 -0
  332. package/src/tests/yoga-ffi.test.ts +117 -0
  333. package/src/tests/yoga-setters.test.ts +921 -0
  334. package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +124 -0
  335. package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +27 -0
  336. package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +27 -0
  337. package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +27 -0
  338. package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +157 -0
  339. package/src/tests/yoga-upstream/YGErrataTest.test.ts +40 -0
  340. package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +23 -0
  341. package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +81 -0
  342. package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +31 -0
  343. package/src/tests/yoga-upstream/YGMeasureTest.test.ts +70 -0
  344. package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +1864 -0
  345. package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +5829 -0
  346. package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +2444 -0
  347. package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +293 -0
  348. package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +315 -0
  349. package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +231 -0
  350. package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +347 -0
  351. package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +256 -0
  352. package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +2747 -0
  353. package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +129 -0
  354. package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +109 -0
  355. package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +1097 -0
  356. package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +4562 -0
  357. package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +677 -0
  358. package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +2071 -0
  359. package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +3303 -0
  360. package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +582 -0
  361. package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +2150 -0
  362. package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +1963 -0
  363. package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +1446 -0
  364. package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +354 -0
  365. package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +1760 -0
  366. package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +1161 -0
  367. package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +207 -0
  368. package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +6338 -0
  369. package/src/tests/yoga-upstream/tools/MeasureCounter.ts +54 -0
  370. package/src/tests/yoga-upstream/tools/utils.ts +81 -0
  371. package/src/text-buffer-view.test.ts +722 -0
  372. package/src/text-buffer-view.ts +193 -0
  373. package/src/text-buffer.test.ts +512 -0
  374. package/src/text-buffer.ts +250 -0
  375. package/src/types.ts +199 -0
  376. package/src/utils.ts +88 -0
  377. package/src/yoga.ts +1146 -0
  378. package/src/zig/ansi.zig +502 -0
  379. package/src/zig/audio.zig +1206 -0
  380. package/src/zig/bench/README.md +50 -0
  381. package/src/zig/bench/buffer-color-blending_bench.zig +350 -0
  382. package/src/zig/bench/buffer-draw-box_bench.zig +786 -0
  383. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +894 -0
  384. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  385. package/src/zig/bench/editor-view_bench.zig +169 -0
  386. package/src/zig/bench/native-span-feed_bench.zig +107 -0
  387. package/src/zig/bench/rope-markers_bench.zig +713 -0
  388. package/src/zig/bench/rope_bench.zig +514 -0
  389. package/src/zig/bench/styled-text_bench.zig +476 -0
  390. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  391. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  392. package/src/zig/bench/text-chunk-graphemes_bench.zig +272 -0
  393. package/src/zig/bench/utf8_bench.zig +799 -0
  394. package/src/zig/bench-utils.zig +431 -0
  395. package/src/zig/bench.zig +223 -0
  396. package/src/zig/buffer-methods.zig +215 -0
  397. package/src/zig/buffer.zig +2515 -0
  398. package/src/zig/build.zig +700 -0
  399. package/src/zig/build.zig.zon +19 -0
  400. package/src/zig/edit-buffer.zig +900 -0
  401. package/src/zig/editor-view.zig +812 -0
  402. package/src/zig/event-bus.zig +28 -0
  403. package/src/zig/event-emitter.zig +65 -0
  404. package/src/zig/file-logger.zig +92 -0
  405. package/src/zig/grapheme.zig +838 -0
  406. package/src/zig/handles.zig +267 -0
  407. package/src/zig/lib.zig +2813 -0
  408. package/src/zig/link.zig +349 -0
  409. package/src/zig/logger.zig +43 -0
  410. package/src/zig/mem-registry.zig +126 -0
  411. package/src/zig/miniaudio_shim.c +21 -0
  412. package/src/zig/native-renderable.zig +112 -0
  413. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  414. package/src/zig/native-span-feed.zig +747 -0
  415. package/src/zig/renderer-output.zig +677 -0
  416. package/src/zig/renderer.zig +2106 -0
  417. package/src/zig/rope.zig +1220 -0
  418. package/src/zig/split-scrollback.zig +88 -0
  419. package/src/zig/syntax-style.zig +174 -0
  420. package/src/zig/terminal.zig +1652 -0
  421. package/src/zig/test.zig +82 -0
  422. package/src/zig/tests/README.md +18 -0
  423. package/src/zig/tests/ansi_test.zig +18 -0
  424. package/src/zig/tests/audio_test.zig +439 -0
  425. package/src/zig/tests/buffer-methods_test.zig +1111 -0
  426. package/src/zig/tests/buffer_test.zig +2926 -0
  427. package/src/zig/tests/edit-buffer-history_test.zig +299 -0
  428. package/src/zig/tests/edit-buffer_test.zig +1688 -0
  429. package/src/zig/tests/editor-view_test.zig +3454 -0
  430. package/src/zig/tests/event-emitter_test.zig +249 -0
  431. package/src/zig/tests/grapheme_test.zig +1329 -0
  432. package/src/zig/tests/handles_test.zig +123 -0
  433. package/src/zig/tests/link_test.zig +220 -0
  434. package/src/zig/tests/mem-registry_test.zig +473 -0
  435. package/src/zig/tests/memory_leak_regression_test.zig +234 -0
  436. package/src/zig/tests/native-renderable_test.zig +16 -0
  437. package/src/zig/tests/native-span-feed_test.zig +1266 -0
  438. package/src/zig/tests/renderer_test.zig +2604 -0
  439. package/src/zig/tests/rope-nested_test.zig +712 -0
  440. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  441. package/src/zig/tests/rope_test.zig +2362 -0
  442. package/src/zig/tests/segment-merge.test.zig +148 -0
  443. package/src/zig/tests/split-scrollback_test.zig +70 -0
  444. package/src/zig/tests/syntax-style_test.zig +558 -0
  445. package/src/zig/tests/terminal_test.zig +1489 -0
  446. package/src/zig/tests/test-renderer.zig +126 -0
  447. package/src/zig/tests/text-buffer-drawing_test.zig +3291 -0
  448. package/src/zig/tests/text-buffer-highlights_test.zig +706 -0
  449. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  450. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  451. package/src/zig/tests/text-buffer-selection_test.zig +1056 -0
  452. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  453. package/src/zig/tests/text-buffer-view_test.zig +3672 -0
  454. package/src/zig/tests/text-buffer_test.zig +2191 -0
  455. package/src/zig/tests/unicode-width-map.zon +3909 -0
  456. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  457. package/src/zig/tests/utf8_test.zig +4060 -0
  458. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  459. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  460. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  461. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  462. package/src/zig/tests/yoga_test.zig +65 -0
  463. package/src/zig/text-buffer-iterators.zig +496 -0
  464. package/src/zig/text-buffer-segment.zig +403 -0
  465. package/src/zig/text-buffer-view.zig +1428 -0
  466. package/src/zig/text-buffer.zig +1272 -0
  467. package/src/zig/utf8.zig +1954 -0
  468. package/src/zig/utils.zig +12 -0
  469. package/src/zig/uucode-stub.zig +76 -0
  470. package/src/zig/vendor/miniaudio/miniaudio.h +93468 -0
  471. package/src/zig/yoga.zig +658 -0
  472. package/src/zig-structs.ts +381 -0
  473. package/src/zig.ts +5260 -0
  474. package/tsconfig.build.json +17 -0
  475. package/tsconfig.json +27 -0
  476. package/tsconfig.node-test.json +138 -0
@@ -0,0 +1,2813 @@
1
+ const std = @import("std");
2
+ const build_options = @import("build_options");
3
+ const Allocator = std.mem.Allocator;
4
+
5
+ const ansi = @import("ansi.zig");
6
+ const buffer = @import("buffer.zig");
7
+ const renderer = @import("renderer.zig");
8
+ const gp = @import("grapheme.zig");
9
+ const link = @import("link.zig");
10
+ const text_buffer = @import("text-buffer.zig");
11
+ const text_buffer_view = @import("text-buffer-view.zig");
12
+ const text_buffer_iterators = @import("text-buffer-iterators.zig");
13
+ const edit_buffer_mod = @import("edit-buffer.zig");
14
+ const editor_view = @import("editor-view.zig");
15
+ const syntax_style = @import("syntax-style.zig");
16
+ const terminal = @import("terminal.zig");
17
+ const utf8 = @import("utf8.zig");
18
+ const logger = @import("logger.zig");
19
+ const event_bus = @import("event-bus.zig");
20
+ const native_span_feed = @import("native-span-feed.zig");
21
+ const native_audio = @import("audio.zig");
22
+ const native_renderable = @import("native-renderable.zig");
23
+ const buffer_effects = @import("buffer-methods.zig");
24
+ const handles = @import("handles.zig");
25
+ const native_yoga = @import("yoga.zig");
26
+
27
+ pub const OptimizedBuffer = buffer.OptimizedBuffer;
28
+ pub const CliRenderer = renderer.CliRenderer;
29
+ pub const Terminal = terminal.Terminal;
30
+ pub const RGBA = buffer.RGBA;
31
+ pub const NativeHandle = handles.Handle;
32
+
33
+ const INVALID_HANDLE: NativeHandle = 0;
34
+ const EMPTY_U8 = [_]u8{0};
35
+ const EMPTY_U32 = [_]u32{0};
36
+
37
+ fn ptrToRGBA(color: [*]const u16) RGBA {
38
+ return .{ color[0], color[1], color[2], color[3] };
39
+ }
40
+
41
+ fn optionalPtrToRGBA(color: ?[*]const u16) ?RGBA {
42
+ if (color) |packed_color| {
43
+ return ptrToRGBA(packed_color);
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ fn erasePtr(ptr_value: anytype) *anyopaque {
50
+ return @ptrCast(ptr_value);
51
+ }
52
+
53
+ fn acquireRenderer(handle: NativeHandle) ?*renderer.CliRenderer {
54
+ return handles.acquire(handle, .renderer, renderer.CliRenderer);
55
+ }
56
+
57
+ fn acquireBuffer(handle: NativeHandle) ?*buffer.OptimizedBuffer {
58
+ return handles.acquire(handle, .optimized_buffer, buffer.OptimizedBuffer);
59
+ }
60
+
61
+ fn acquireTextBuffer(handle: NativeHandle) ?*text_buffer.UnifiedTextBuffer {
62
+ return handles.acquire(handle, .text_buffer, text_buffer.UnifiedTextBuffer);
63
+ }
64
+
65
+ fn acquireTextBufferView(handle: NativeHandle) ?*text_buffer_view.UnifiedTextBufferView {
66
+ return handles.acquire(handle, .text_buffer_view, text_buffer_view.UnifiedTextBufferView);
67
+ }
68
+
69
+ fn acquireEditBuffer(handle: NativeHandle) ?*edit_buffer_mod.EditBuffer {
70
+ return handles.acquire(handle, .edit_buffer, edit_buffer_mod.EditBuffer);
71
+ }
72
+
73
+ fn acquireEditorView(handle: NativeHandle) ?*editor_view.EditorView {
74
+ return handles.acquire(handle, .editor_view, editor_view.EditorView);
75
+ }
76
+
77
+ fn acquireSyntaxStyle(handle: NativeHandle) ?*syntax_style.SyntaxStyle {
78
+ return handles.acquire(handle, .syntax_style, syntax_style.SyntaxStyle);
79
+ }
80
+
81
+ fn acquireEventSink(handle: NativeHandle) ?*event_bus.EventSink {
82
+ return handles.acquire(handle, .event_sink, event_bus.EventSink);
83
+ }
84
+
85
+ fn acquireAudioEngine(handle: NativeHandle) ?*native_audio.Engine {
86
+ return handles.acquire(handle, .audio_engine, native_audio.Engine);
87
+ }
88
+
89
+ fn acquireNativeRenderable(handle: NativeHandle) ?*native_renderable.NativeRenderable {
90
+ return handles.acquire(handle, .native_renderable, native_renderable.NativeRenderable);
91
+ }
92
+
93
+ fn emptyLineInfo(outPtr: *ExternalLineInfo) void {
94
+ outPtr.* = .{
95
+ .start_cols_ptr = EMPTY_U32[0..].ptr,
96
+ .start_cols_len = 0,
97
+ .width_cols_ptr = EMPTY_U32[0..].ptr,
98
+ .width_cols_len = 0,
99
+ .sources_ptr = EMPTY_U32[0..].ptr,
100
+ .sources_len = 0,
101
+ .wraps_ptr = EMPTY_U32[0..].ptr,
102
+ .wraps_len = 0,
103
+ .width_cols_max = 0,
104
+ };
105
+ }
106
+
107
+ fn sliceFromPtrLen(ptr: ?[*]const u8, len: u32) []const u8 {
108
+ if (len == 0) {
109
+ return "";
110
+ }
111
+
112
+ return ptr.?[0..@as(usize, len)];
113
+ }
114
+
115
+ inline fn selectionStyle(bg: ?RGBA, fg: ?RGBA) text_buffer_view.SelectionStyle {
116
+ return .{
117
+ .bgColor = bg,
118
+ .fgColor = fg,
119
+ };
120
+ }
121
+
122
+ comptime {
123
+ _ = native_span_feed;
124
+ _ = native_audio;
125
+ _ = native_renderable;
126
+ _ = native_yoga;
127
+ }
128
+
129
+ export fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: u32) callconv(.c) void) void {
130
+ logger.setLogCallback(callback);
131
+ }
132
+
133
+ export fn createEventSink(callback: ?event_bus.EventCallback) NativeHandle {
134
+ const sink = event_bus.createEventSink(globalAllocator, callback orelse return INVALID_HANDLE) catch return INVALID_HANDLE;
135
+ return handles.insert(.event_sink, erasePtr(sink)) catch {
136
+ event_bus.destroyEventSink(globalAllocator, sink);
137
+ return INVALID_HANDLE;
138
+ };
139
+ }
140
+
141
+ fn clearEditBufferEventSinkRefs(sink: *event_bus.EventSink) void {
142
+ var cursor: usize = 1;
143
+ while (handles.nextByKind(.edit_buffer, &cursor)) |edit_handle| {
144
+ const token = handles.pause(edit_handle, .edit_buffer, edit_buffer_mod.EditBuffer) orelse continue;
145
+ if (token.ptr.event_sink == sink) {
146
+ token.ptr.event_sink = null;
147
+ }
148
+ handles.unpause(token.handle);
149
+ }
150
+ }
151
+
152
+ export fn createNativeRenderable() NativeHandle {
153
+ const renderable = globalAllocator.create(native_renderable.NativeRenderable) catch return INVALID_HANDLE;
154
+ renderable.* = .{};
155
+ return handles.insert(.native_renderable, erasePtr(renderable)) catch {
156
+ globalAllocator.destroy(renderable);
157
+ return INVALID_HANDLE;
158
+ };
159
+ }
160
+
161
+ export fn destroyNativeRenderable(native_renderable_handle: NativeHandle) void {
162
+ const token = handles.beginDestroy(native_renderable_handle, .native_renderable, native_renderable.NativeRenderable) orelse return;
163
+ token.ptr.deinit();
164
+ globalAllocator.destroy(token.ptr);
165
+ handles.finishDestroy(token.handle);
166
+ }
167
+
168
+ export fn nativeRenderableAttachYogaNode(native_renderable_handle: NativeHandle, node: native_yoga.YGNodeRef) bool {
169
+ // Temporary bridge: JS-created Renderables still own Yoga nodes, so native
170
+ // renderables borrow and attach them after construction. This should go away
171
+ // when the renderable tree and Yoga ownership move native-side.
172
+ const renderable = acquireNativeRenderable(native_renderable_handle) orelse return false;
173
+ if (node == null) return false;
174
+ renderable.attachYogaNode(node);
175
+ return true;
176
+ }
177
+
178
+ export fn nativeRenderableSetMeasureTarget(
179
+ native_renderable_handle: NativeHandle,
180
+ kind: u32,
181
+ target_handle: NativeHandle,
182
+ ) bool {
183
+ const renderable = acquireNativeRenderable(native_renderable_handle) orelse return false;
184
+
185
+ // Resolve handles once at setup time. The Yoga measure callback then uses raw
186
+ // native pointers and does not pay handle-registry lookup cost on the hot path.
187
+ // Kind `none` clears the measure target; unknown kinds are rejected.
188
+ const measure_kind: native_renderable.MeasureTargetKind = switch (kind) {
189
+ @intFromEnum(native_renderable.MeasureTargetKind.none) => .none,
190
+ @intFromEnum(native_renderable.MeasureTargetKind.text_buffer_view) => .text_buffer_view,
191
+ @intFromEnum(native_renderable.MeasureTargetKind.editor_view) => .editor_view,
192
+ else => return false,
193
+ };
194
+
195
+ const target: native_renderable.MeasureTarget = switch (measure_kind) {
196
+ .none => .none,
197
+ .text_buffer_view => .{ .text_buffer_view = acquireTextBufferView(target_handle) orelse return false },
198
+ .editor_view => .{ .editor_view = acquireEditorView(target_handle) orelse return false },
199
+ };
200
+
201
+ renderable.setMeasureTarget(target);
202
+ return true;
203
+ }
204
+
205
+ export fn destroyEventSink(sink_handle: NativeHandle) void {
206
+ const token = handles.beginDestroy(sink_handle, .event_sink, event_bus.EventSink) orelse return;
207
+ clearEditBufferEventSinkRefs(token.ptr);
208
+ event_bus.destroyEventSink(globalAllocator, token.ptr);
209
+ handles.finishDestroy(token.handle);
210
+ }
211
+
212
+ var gpa: std.heap.GeneralPurposeAllocator(.{
213
+ .enable_memory_limit = build_options.gpa_safe_stats,
214
+ .safety = build_options.gpa_safe_stats,
215
+ }) = .{};
216
+ const globalAllocator = gpa.allocator();
217
+ var arena = std.heap.ArenaAllocator.init(globalAllocator);
218
+ const globalArena = arena.allocator();
219
+
220
+ pub const ExternalBuildOptions = extern struct {
221
+ gpa_safe_stats: bool,
222
+ gpa_memory_limit_tracking: bool,
223
+ };
224
+
225
+ pub const ExternalAllocatorStats = extern struct {
226
+ total_requested_bytes: u64,
227
+ active_allocations: u64,
228
+ small_allocations: u64,
229
+ large_allocations: u64,
230
+ requested_bytes_valid: bool,
231
+ };
232
+
233
+ pub const ExternalRenderStats = extern struct {
234
+ last_frame_time: f64,
235
+ average_frame_time: f64,
236
+ render_time: f64,
237
+ // ABI names keep stdout terminology for compatibility; the value is the
238
+ // backend output write time for stdout, memory, or feed output.
239
+ stdout_write_time: f64,
240
+ frame_count: u64,
241
+ cells_updated: u32,
242
+ average_cells_updated: u32,
243
+ render_time_valid: bool,
244
+ stdout_write_time_valid: bool,
245
+ };
246
+
247
+ fn toNonNegativeU64(value: anytype) u64 {
248
+ const ValueType = @TypeOf(value);
249
+
250
+ return switch (@typeInfo(ValueType)) {
251
+ .int => |int_info| if (int_info.signedness == .signed) blk: {
252
+ const signed_value: i64 = @intCast(value);
253
+ if (signed_value <= 0) break :blk 0;
254
+ break :blk @intCast(signed_value);
255
+ } else @intCast(value),
256
+ .comptime_int => blk: {
257
+ if (value <= 0) break :blk 0;
258
+ break :blk @intCast(value);
259
+ },
260
+ else => 0,
261
+ };
262
+ }
263
+
264
+ const RequestedBytesInfo = struct {
265
+ bytes: u64,
266
+ valid: bool,
267
+ };
268
+
269
+ fn sanitizeRequestedBytes(value: u64) RequestedBytesInfo {
270
+ const signed_value: i64 = @bitCast(value);
271
+ if (signed_value < 0) {
272
+ return .{ .bytes = 0, .valid = false };
273
+ }
274
+
275
+ return .{ .bytes = @intCast(signed_value), .valid = true };
276
+ }
277
+
278
+ fn queryStatsField(comptime field_names: []const []const u8) ?u64 {
279
+ if (!@hasDecl(@TypeOf(gpa), "queryStats")) {
280
+ return null;
281
+ }
282
+
283
+ const stats = gpa.queryStats();
284
+ const StatsType = @TypeOf(stats);
285
+
286
+ inline for (field_names) |field_name| {
287
+ if (@hasField(StatsType, field_name)) {
288
+ return toNonNegativeU64(@field(stats, field_name));
289
+ }
290
+ }
291
+
292
+ return null;
293
+ }
294
+
295
+ fn getTotalRequestedBytesInfo() RequestedBytesInfo {
296
+ if (!build_options.gpa_safe_stats) {
297
+ return .{ .bytes = 0, .valid = false };
298
+ }
299
+
300
+ if (queryStatsField(&.{"total_requested_bytes"})) |value| {
301
+ return sanitizeRequestedBytes(value);
302
+ }
303
+
304
+ if (@hasField(@TypeOf(gpa), "total_requested_bytes")) {
305
+ if (@TypeOf(gpa.total_requested_bytes) == void) {
306
+ return .{ .bytes = 0, .valid = false };
307
+ }
308
+
309
+ return sanitizeRequestedBytes(toNonNegativeU64(gpa.total_requested_bytes));
310
+ }
311
+
312
+ return .{ .bytes = 0, .valid = false };
313
+ }
314
+
315
+ fn getSmallAllocationCount() u64 {
316
+ if (queryStatsField(&.{ "small_allocations", "small_allocation_count" })) |value| {
317
+ return value;
318
+ }
319
+
320
+ var total: u64 = 0;
321
+ for (gpa.buckets) |bucket_head| {
322
+ var current = bucket_head;
323
+ while (current) |bucket| {
324
+ const allocated: u64 = @intCast(bucket.allocated_count);
325
+ const freed: u64 = @intCast(bucket.freed_count);
326
+ if (allocated >= freed) {
327
+ total += allocated - freed;
328
+ }
329
+ current = bucket.next;
330
+ }
331
+ }
332
+
333
+ return total;
334
+ }
335
+
336
+ fn getLargeAllocationCount() u64 {
337
+ if (queryStatsField(&.{ "large_allocations", "large_allocation_count" })) |value| {
338
+ return value;
339
+ }
340
+
341
+ return @intCast(gpa.large_allocations.count());
342
+ }
343
+
344
+ export fn createNativeSpanFeed(options_ptr: ?*const native_span_feed.Options) ?*native_span_feed.Stream {
345
+ return native_span_feed.createNativeSpanFeedWithAllocator(globalAllocator, options_ptr);
346
+ }
347
+
348
+ export fn createAudioEngine(options_ptr: ?*const native_audio.CreateOptions) NativeHandle {
349
+ const engine = native_audio.create(globalAllocator, options_ptr) orelse return INVALID_HANDLE;
350
+ return handles.insert(.audio_engine, erasePtr(engine)) catch {
351
+ native_audio.destroy(engine);
352
+ return INVALID_HANDLE;
353
+ };
354
+ }
355
+
356
+ export fn destroyAudioEngine(engine_handle: NativeHandle) void {
357
+ const token = handles.beginDestroy(engine_handle, .audio_engine, native_audio.Engine) orelse return;
358
+ native_audio.destroy(token.ptr);
359
+ handles.finishDestroy(token.handle);
360
+ }
361
+
362
+ export fn audioRefreshPlaybackDevices(engine_handle: NativeHandle) i32 {
363
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
364
+ return native_audio.refreshPlaybackDevices(object_ptr);
365
+ }
366
+
367
+ export fn audioGetPlaybackDeviceCount(engine_handle: NativeHandle) u32 {
368
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return 0;
369
+ return native_audio.getPlaybackDeviceCount(object_ptr);
370
+ }
371
+
372
+ export fn audioGetPlaybackDeviceName(engine_handle: NativeHandle, index: u32, out_ptr: [*]u8, max_len: u32) u32 {
373
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return 0;
374
+ return @intCast(native_audio.getPlaybackDeviceName(object_ptr, index, out_ptr, @as(usize, max_len)));
375
+ }
376
+
377
+ export fn audioIsPlaybackDeviceDefault(engine_handle: NativeHandle, index: u32) bool {
378
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return false;
379
+ return native_audio.isPlaybackDeviceDefault(object_ptr, index);
380
+ }
381
+
382
+ export fn audioSelectPlaybackDevice(engine_handle: NativeHandle, index: u32) i32 {
383
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
384
+ return native_audio.selectPlaybackDevice(object_ptr, index);
385
+ }
386
+
387
+ export fn audioClearPlaybackDeviceSelection(engine_handle: NativeHandle) void {
388
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return;
389
+ native_audio.clearPlaybackDeviceSelection(object_ptr);
390
+ }
391
+
392
+ export fn audioStart(engine_handle: NativeHandle, options_ptr: ?*const native_audio.StartOptions) i32 {
393
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
394
+ return native_audio.start(object_ptr, options_ptr);
395
+ }
396
+
397
+ export fn audioStartMixer(engine_handle: NativeHandle) i32 {
398
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
399
+ return native_audio.startMixer(object_ptr);
400
+ }
401
+
402
+ export fn audioStop(engine_handle: NativeHandle) i32 {
403
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
404
+ return native_audio.stop(object_ptr);
405
+ }
406
+
407
+ export fn audioLoad(engine_handle: NativeHandle, data_ptr: ?[*]const u8, data_len: u32, out_sound_id: ?*u32) i32 {
408
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
409
+ return native_audio.load(object_ptr, data_ptr, @as(usize, data_len), out_sound_id);
410
+ }
411
+
412
+ export fn audioUnload(engine_handle: NativeHandle, sound_id: u32) i32 {
413
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
414
+ return native_audio.unload(object_ptr, sound_id);
415
+ }
416
+
417
+ export fn audioPlay(engine_handle: NativeHandle, sound_id: u32, options_ptr: ?*const native_audio.VoiceOptions, out_voice_id: ?*u32) i32 {
418
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
419
+ return native_audio.play(object_ptr, sound_id, options_ptr, out_voice_id);
420
+ }
421
+
422
+ export fn audioStopVoice(engine_handle: NativeHandle, voice_id: u32) i32 {
423
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
424
+ return native_audio.stopVoice(object_ptr, voice_id);
425
+ }
426
+
427
+ export fn audioSetVoiceGroup(engine_handle: NativeHandle, voice_id: u32, group_id: u32) i32 {
428
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
429
+ return native_audio.setVoiceGroup(object_ptr, voice_id, group_id);
430
+ }
431
+
432
+ export fn audioCreateGroup(engine_handle: NativeHandle, name_ptr: ?[*]const u8, name_len: u32, out_group_id: ?*u32) i32 {
433
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
434
+ return native_audio.createGroup(object_ptr, name_ptr, @as(usize, name_len), out_group_id);
435
+ }
436
+
437
+ export fn audioSetGroupVolume(engine_handle: NativeHandle, group_id: u32, volume: f32) i32 {
438
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
439
+ return native_audio.setGroupVolume(object_ptr, group_id, volume);
440
+ }
441
+
442
+ export fn audioSetMasterVolume(engine_handle: NativeHandle, volume: f32) i32 {
443
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
444
+ return native_audio.setMasterVolume(object_ptr, volume);
445
+ }
446
+
447
+ export fn audioMixToBuffer(engine_handle: NativeHandle, out_ptr: ?[*]f32, frame_count: u32, channels: u8) i32 {
448
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
449
+ return native_audio.mixToBuffer(object_ptr, out_ptr, frame_count, channels);
450
+ }
451
+
452
+ export fn audioEnableTap(engine_handle: NativeHandle, enabled: bool, capacity_frames: u32) i32 {
453
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
454
+ return native_audio.enableTap(object_ptr, enabled, capacity_frames);
455
+ }
456
+
457
+ export fn audioReadTap(engine_handle: NativeHandle, out_ptr: ?[*]f32, frame_count: u32, channels: u8, out_frames_read: ?*u32) i32 {
458
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
459
+ return native_audio.readTap(object_ptr, out_ptr, frame_count, channels, out_frames_read);
460
+ }
461
+
462
+ export fn audioGetStats(engine_handle: NativeHandle, out_stats: ?*native_audio.Stats) i32 {
463
+ const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid;
464
+ return native_audio.getStats(object_ptr, out_stats);
465
+ }
466
+
467
+ export fn getArenaAllocatedBytes() u64 {
468
+ return @intCast(arena.queryCapacity());
469
+ }
470
+
471
+ export fn getBuildOptions(out_ptr: *ExternalBuildOptions) void {
472
+ out_ptr.* = .{
473
+ .gpa_safe_stats = build_options.gpa_safe_stats,
474
+ .gpa_memory_limit_tracking = build_options.gpa_safe_stats,
475
+ };
476
+ }
477
+
478
+ export fn getAllocatorStats(out_ptr: *ExternalAllocatorStats) void {
479
+ const small_allocations = getSmallAllocationCount();
480
+ const large_allocations = getLargeAllocationCount();
481
+ const active_allocations = small_allocations + large_allocations;
482
+ const requested_bytes = getTotalRequestedBytesInfo();
483
+
484
+ out_ptr.* = .{
485
+ .total_requested_bytes = requested_bytes.bytes,
486
+ .active_allocations = active_allocations,
487
+ .small_allocations = small_allocations,
488
+ .large_allocations = large_allocations,
489
+ .requested_bytes_valid = requested_bytes.valid,
490
+ };
491
+ }
492
+
493
+ /// Create a renderer.
494
+ ///
495
+ /// Output transport selection:
496
+ /// - `feedPtr != null`: writes go to the provided NativeSpanFeed stream
497
+ /// (FeedBackend), which the TS side pipes onward to a user-supplied Writable
498
+ /// - `feedPtr == null`: writes go through a buffered backend selected by
499
+ /// `bufferedDestinationKind` (0 = process stdout, 1 = memory)
500
+ ///
501
+ /// `remoteModeValue` is 0 = auto, 1 = local, 2 = remote. The TS side decides
502
+ /// the appropriate default for process stdout, memory output, and feed output.
503
+ fn registerRendererBufferHandles(renderer_handle: NativeHandle, rendererPtr: *renderer.CliRenderer) bool {
504
+ _ = handles.getOrInsertBorrowed(.optimized_buffer, erasePtr(rendererPtr.getCurrentBuffer()), renderer_handle) catch return false;
505
+ _ = handles.getOrInsertBorrowed(.optimized_buffer, erasePtr(rendererPtr.getNextBuffer()), renderer_handle) catch return false;
506
+ return true;
507
+ }
508
+
509
+ export fn createRenderer(
510
+ width: u32,
511
+ height: u32,
512
+ bufferedDestinationKind: u8,
513
+ remoteModeValue: u8,
514
+ feedPtr: ?*native_span_feed.Stream,
515
+ ) NativeHandle {
516
+ if (width == 0 or height == 0) {
517
+ logger.warn("Invalid renderer dimensions: {}x{}", .{ width, height });
518
+ return INVALID_HANDLE;
519
+ }
520
+
521
+ const remote_mode: terminal.Terminal.RemoteMode = switch (remoteModeValue) {
522
+ 0 => .auto,
523
+ 1 => .local,
524
+ 2 => .remote,
525
+ else => .local,
526
+ };
527
+
528
+ const pool = gp.initGlobalPool(globalArena);
529
+ _ = link.initGlobalLinkPool(globalArena);
530
+ const output_target: renderer.CliRenderer.OutputTarget = if (feedPtr) |feed|
531
+ .{ .feed = feed }
532
+ else switch (bufferedDestinationKind) {
533
+ 0 => .stdout,
534
+ 1 => .memory,
535
+ else => {
536
+ logger.warn("Invalid buffered destination kind: {}", .{bufferedDestinationKind});
537
+ return INVALID_HANDLE;
538
+ },
539
+ };
540
+
541
+ const rendererPtr = renderer.CliRenderer.createWithOptions(globalAllocator, width, height, pool, .{
542
+ .remote_mode = remote_mode,
543
+ .output = output_target,
544
+ }) catch |err| {
545
+ logger.err("Failed to create renderer: {}", .{err});
546
+ return INVALID_HANDLE;
547
+ };
548
+
549
+ const renderer_handle = handles.insert(.renderer, erasePtr(rendererPtr)) catch {
550
+ rendererPtr.destroy();
551
+ return INVALID_HANDLE;
552
+ };
553
+ if (!registerRendererBufferHandles(renderer_handle, rendererPtr)) {
554
+ if (handles.beginDestroy(renderer_handle, .renderer, renderer.CliRenderer)) |token| {
555
+ handles.invalidateChildren(token.handle);
556
+ rendererPtr.destroy();
557
+ handles.finishDestroy(token.handle);
558
+ }
559
+ return INVALID_HANDLE;
560
+ }
561
+
562
+ return renderer_handle;
563
+ }
564
+
565
+ export fn setTerminalEnvVar(renderer_handle: NativeHandle, keyPtr: ?[*]const u8, keyLen: u32, valuePtr: ?[*]const u8, valueLen: u32) bool {
566
+ const object_ptr = acquireRenderer(renderer_handle) orelse return false;
567
+ const key = sliceFromPtrLen(keyPtr, keyLen);
568
+ const value = sliceFromPtrLen(valuePtr, valueLen);
569
+ return object_ptr.setTerminalEnvVar(key, value);
570
+ }
571
+
572
+ export fn setUseThread(renderer_handle: NativeHandle, useThread: bool) void {
573
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
574
+ object_ptr.setUseThread(useThread);
575
+ }
576
+
577
+ export fn setClearOnShutdown(renderer_handle: NativeHandle, clear: bool) void {
578
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
579
+ object_ptr.setClearOnShutdown(clear);
580
+ }
581
+
582
+ export fn destroyRenderer(renderer_handle: NativeHandle) void {
583
+ const token = handles.beginDestroy(renderer_handle, .renderer, renderer.CliRenderer) orelse return;
584
+ handles.invalidateChildren(token.handle);
585
+ token.ptr.destroy();
586
+ handles.finishDestroy(token.handle);
587
+ }
588
+
589
+ export fn setBackgroundColor(renderer_handle: NativeHandle, color: [*]const u16) void {
590
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
591
+ object_ptr.setBackgroundColor(ptrToRGBA(color));
592
+ }
593
+
594
+ export fn setRenderOffset(renderer_handle: NativeHandle, offset: u32) void {
595
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
596
+ object_ptr.setRenderOffset(offset);
597
+ }
598
+
599
+ export fn resetSplitScrollback(renderer_handle: NativeHandle, seedRows: u32, pinnedRenderOffset: u32) u32 {
600
+ const object_ptr = acquireRenderer(renderer_handle) orelse return 0;
601
+ return object_ptr.resetSplitScrollback(seedRows, pinnedRenderOffset);
602
+ }
603
+
604
+ export fn syncSplitScrollback(renderer_handle: NativeHandle, pinnedRenderOffset: u32) u32 {
605
+ const object_ptr = acquireRenderer(renderer_handle) orelse return 0;
606
+ return object_ptr.syncSplitScrollback(pinnedRenderOffset);
607
+ }
608
+
609
+ export fn getSplitOutputOffset(renderer_handle: NativeHandle, surfaceOffset: u32) u32 {
610
+ const object_ptr = acquireRenderer(renderer_handle) orelse return 0;
611
+ return object_ptr.getSplitOutputOffset(surfaceOffset);
612
+ }
613
+
614
+ export fn setPendingSplitFooterTransition(
615
+ renderer_handle: NativeHandle,
616
+ mode: u8,
617
+ sourceTopLine: u32,
618
+ sourceHeight: u32,
619
+ targetTopLine: u32,
620
+ targetHeight: u32,
621
+ scrollLines: u32,
622
+ ) void {
623
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
624
+ object_ptr.setPendingSplitFooterTransition(
625
+ @enumFromInt(mode),
626
+ sourceTopLine,
627
+ sourceHeight,
628
+ targetTopLine,
629
+ targetHeight,
630
+ scrollLines,
631
+ );
632
+ }
633
+
634
+ export fn clearPendingSplitFooterTransition(renderer_handle: NativeHandle) void {
635
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
636
+ object_ptr.clearPendingSplitFooterTransition();
637
+ }
638
+
639
+ export fn updateStats(renderer_handle: NativeHandle, time: f64, fps: u32, frameCallbackTime: f64) void {
640
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
641
+ object_ptr.updateStats(time, fps, frameCallbackTime);
642
+ }
643
+
644
+ export fn updateMemoryStats(renderer_handle: NativeHandle, heapUsed: u32, heapTotal: u32, arrayBuffers: u32) void {
645
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
646
+ object_ptr.updateMemoryStats(heapUsed, heapTotal, arrayBuffers);
647
+ }
648
+
649
+ export fn getRenderStats(renderer_handle: NativeHandle, outPtr: *ExternalRenderStats) void {
650
+ const object_ptr = acquireRenderer(renderer_handle) orelse {
651
+ outPtr.* = std.mem.zeroes(ExternalRenderStats);
652
+ return;
653
+ };
654
+ const stats = object_ptr.getRenderStats();
655
+
656
+ outPtr.* = .{
657
+ .last_frame_time = stats.lastFrameTime,
658
+ .average_frame_time = stats.averageFrameTime,
659
+ .render_time = stats.renderTime orelse 0,
660
+ .stdout_write_time = stats.outputWriteTime orelse 0,
661
+ .frame_count = stats.frameCount,
662
+ .cells_updated = stats.cellsUpdated,
663
+ .average_cells_updated = stats.averageCellsUpdated,
664
+ .render_time_valid = stats.renderTime != null,
665
+ .stdout_write_time_valid = stats.outputWriteTime != null,
666
+ };
667
+ }
668
+
669
+ export fn getNextBuffer(renderer_handle: NativeHandle) NativeHandle {
670
+ const object_ptr = acquireRenderer(renderer_handle) orelse return INVALID_HANDLE;
671
+ return handles.getOrInsertBorrowed(.optimized_buffer, erasePtr(object_ptr.getNextBuffer()), renderer_handle) catch INVALID_HANDLE;
672
+ }
673
+
674
+ export fn getCurrentBuffer(renderer_handle: NativeHandle) NativeHandle {
675
+ const object_ptr = acquireRenderer(renderer_handle) orelse return INVALID_HANDLE;
676
+ return handles.getOrInsertBorrowed(.optimized_buffer, erasePtr(object_ptr.getCurrentBuffer()), renderer_handle) catch INVALID_HANDLE;
677
+ }
678
+
679
+ export fn setHyperlinksCapability(renderer_handle: NativeHandle, enabled: bool) void {
680
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
681
+ object_ptr.terminal.caps.hyperlinks = enabled;
682
+ }
683
+
684
+ export fn clearGlobalLinkPool() void {
685
+ link.deinitGlobalLinkPool();
686
+ }
687
+
688
+ export fn getBufferWidth(buffer_handle: NativeHandle) u32 {
689
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 0;
690
+ return object_ptr.width;
691
+ }
692
+
693
+ export fn getBufferHeight(buffer_handle: NativeHandle) u32 {
694
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 0;
695
+ return object_ptr.height;
696
+ }
697
+
698
+ fn packRenderResult(result: renderer.RenderResult) u64 {
699
+ return @as(u64, result.renderOffset) | (@as(u64, @intFromEnum(result.status)) << 32);
700
+ }
701
+
702
+ fn packFailedRenderResult() u64 {
703
+ return packRenderResult(.{ .renderOffset = 0, .status = .failed });
704
+ }
705
+
706
+ export fn render(renderer_handle: NativeHandle, force: bool) u8 {
707
+ const object_ptr = acquireRenderer(renderer_handle) orelse return @intFromEnum(renderer.RenderStatus.failed);
708
+ return @intFromEnum(object_ptr.render(force));
709
+ }
710
+
711
+ export fn repaintSplitFooter(
712
+ renderer_handle: NativeHandle,
713
+ pinnedRenderOffset: u32,
714
+ force: bool,
715
+ ) u64 {
716
+ const object_ptr = acquireRenderer(renderer_handle) orelse return packFailedRenderResult();
717
+ return packRenderResult(object_ptr.repaintSplitFooter(pinnedRenderOffset, force));
718
+ }
719
+
720
+ export fn commitSplitFooterSnapshot(
721
+ renderer_handle: NativeHandle,
722
+ snapshot_buffer_handle: NativeHandle,
723
+ rowColumns: u32,
724
+ startOnNewLine: bool,
725
+ trailingNewline: bool,
726
+ pinnedRenderOffset: u32,
727
+ force: bool,
728
+ beginFrame: bool,
729
+ finalizeFrame: bool,
730
+ ) u64 {
731
+ const renderer_ptr = acquireRenderer(renderer_handle) orelse return packFailedRenderResult();
732
+ const snapshot_ptr = acquireBuffer(snapshot_buffer_handle) orelse return packFailedRenderResult();
733
+
734
+ // JS passes rowColumns/startOnNewLine/trailingNewline per commit from
735
+ // writeToScrollback or captured stdout chunking. This entrypoint is the ABI
736
+ // boundary where that metadata enters the native split append algorithm.
737
+ // Route all commits through the batched renderer path so sync/cursor framing
738
+ // happens exactly once per JS flush cycle.
739
+ if (beginFrame and finalizeFrame) {
740
+ return packRenderResult(renderer_ptr.commitSplitFooterSnapshotBatched(
741
+ snapshot_ptr,
742
+ rowColumns,
743
+ startOnNewLine,
744
+ trailingNewline,
745
+ pinnedRenderOffset,
746
+ force,
747
+ true,
748
+ true,
749
+ ));
750
+ }
751
+
752
+ return packRenderResult(renderer_ptr.commitSplitFooterSnapshotBatched(
753
+ snapshot_ptr,
754
+ rowColumns,
755
+ startOnNewLine,
756
+ trailingNewline,
757
+ pinnedRenderOffset,
758
+ force,
759
+ beginFrame,
760
+ finalizeFrame,
761
+ ));
762
+ }
763
+
764
+ export fn createOptimizedBuffer(width: u32, height: u32, respectAlpha: bool, widthMethod: u8, idPtr: ?[*]const u8, idLen: u32) NativeHandle {
765
+ if (width == 0 or height == 0) {
766
+ logger.warn("Invalid buffer dimensions: {}x{}", .{ width, height });
767
+ return INVALID_HANDLE;
768
+ }
769
+
770
+ const pool = gp.initGlobalPool(globalArena);
771
+ const link_pool = link.initGlobalLinkPool(globalArena);
772
+ const wMethod: utf8.WidthMethod = if (widthMethod == 0) .wcwidth else .unicode;
773
+ const id = sliceFromPtrLen(idPtr, idLen);
774
+
775
+ const bufferPtr = buffer.OptimizedBuffer.init(globalAllocator, width, height, .{
776
+ .respectAlpha = respectAlpha,
777
+ .pool = pool,
778
+ .width_method = wMethod,
779
+ .id = id,
780
+ .link_pool = link_pool,
781
+ }) catch |err| {
782
+ logger.err("Failed to create optimized buffer: {}", .{err});
783
+ return INVALID_HANDLE;
784
+ };
785
+
786
+ return handles.insert(.optimized_buffer, erasePtr(bufferPtr)) catch {
787
+ bufferPtr.deinit();
788
+ return INVALID_HANDLE;
789
+ };
790
+ }
791
+
792
+ export fn destroyOptimizedBuffer(buffer_handle: NativeHandle) void {
793
+ const token = handles.beginDestroy(buffer_handle, .optimized_buffer, buffer.OptimizedBuffer) orelse return;
794
+ token.ptr.deinit();
795
+ handles.finishDestroy(token.handle);
796
+ }
797
+
798
+ export fn destroyFrameBuffer(frame_buffer_handle: NativeHandle) void {
799
+ destroyOptimizedBuffer(frame_buffer_handle);
800
+ }
801
+
802
+ export fn drawFrameBuffer(target_handle: NativeHandle, destX: i32, destY: i32, frame_buffer_handle: NativeHandle, sourceX: u32, sourceY: u32, sourceWidth: u32, sourceHeight: u32) void {
803
+ const target_ptr = acquireBuffer(target_handle) orelse return;
804
+ const frame_ptr = acquireBuffer(frame_buffer_handle) orelse return;
805
+ const srcX = if (sourceX == 0) null else sourceX;
806
+ const srcY = if (sourceY == 0) null else sourceY;
807
+ const srcWidth = if (sourceWidth == 0) null else sourceWidth;
808
+ const srcHeight = if (sourceHeight == 0) null else sourceHeight;
809
+
810
+ target_ptr.drawFrameBuffer(destX, destY, frame_ptr, srcX, srcY, srcWidth, srcHeight);
811
+ }
812
+
813
+ export fn setCursorPosition(renderer_handle: NativeHandle, x: i32, y: i32, visible: bool) void {
814
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
815
+ object_ptr.terminal.setCursorPosition(@intCast(@max(1, x)), @intCast(@max(1, y)), visible);
816
+ }
817
+
818
+ pub const ExternalCapabilities = extern struct {
819
+ kitty_keyboard: bool,
820
+ kitty_graphics: bool,
821
+ rgb: bool,
822
+ ansi256: bool,
823
+ unicode: u8, // 0 = wcwidth, 1 = unicode
824
+ sgr_pixels: bool,
825
+ color_scheme_updates: bool,
826
+ explicit_width: bool,
827
+ scaled_text: bool,
828
+ sixel: bool,
829
+ focus_tracking: bool,
830
+ sync: bool,
831
+ bracketed_paste: bool,
832
+ hyperlinks: bool,
833
+ osc52: bool,
834
+ notifications: bool,
835
+ explicit_cursor_positioning: bool,
836
+ remote: bool,
837
+ multiplexer: u8,
838
+ term_name_ptr: [*]const u8,
839
+ term_name_len: usize,
840
+ term_version_ptr: [*]const u8,
841
+ term_version_len: usize,
842
+ term_from_xtversion: bool,
843
+ osc52_support: u8,
844
+ };
845
+
846
+ export fn getTerminalCapabilities(renderer_handle: NativeHandle, capsPtr: *ExternalCapabilities) void {
847
+ const object_ptr = acquireRenderer(renderer_handle) orelse {
848
+ capsPtr.* = std.mem.zeroes(ExternalCapabilities);
849
+ capsPtr.term_name_ptr = EMPTY_U8[0..].ptr;
850
+ capsPtr.term_version_ptr = EMPTY_U8[0..].ptr;
851
+ return;
852
+ };
853
+ const caps = object_ptr.getTerminalCapabilities();
854
+ const term = &object_ptr.terminal;
855
+
856
+ capsPtr.* = .{
857
+ .kitty_keyboard = caps.kitty_keyboard,
858
+ .kitty_graphics = caps.kitty_graphics,
859
+ .rgb = caps.rgb,
860
+ .ansi256 = caps.ansi256,
861
+ .unicode = if (caps.unicode == .wcwidth) 0 else 1,
862
+ .sgr_pixels = caps.sgr_pixels,
863
+ .color_scheme_updates = caps.color_scheme_updates,
864
+ .explicit_width = caps.explicit_width,
865
+ .scaled_text = caps.scaled_text,
866
+ .sixel = caps.sixel,
867
+ .focus_tracking = caps.focus_tracking,
868
+ .sync = caps.sync,
869
+ .bracketed_paste = caps.bracketed_paste,
870
+ .hyperlinks = caps.hyperlinks,
871
+ .osc52 = caps.osc52,
872
+ .osc52_support = @intFromEnum(term.osc52_support),
873
+ .notifications = caps.notifications,
874
+ .explicit_cursor_positioning = caps.explicit_cursor_positioning,
875
+ .remote = caps.remote,
876
+ .multiplexer = @intFromEnum(term.multiplexer),
877
+ .term_name_ptr = &term.term_info.name,
878
+ .term_name_len = term.term_info.name_len,
879
+ .term_version_ptr = &term.term_info.version,
880
+ .term_version_len = term.term_info.version_len,
881
+ .term_from_xtversion = term.term_info.from_xtversion,
882
+ };
883
+ }
884
+
885
+ export fn processCapabilityResponse(renderer_handle: NativeHandle, responsePtr: ?[*]const u8, responseLen: u32) void {
886
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
887
+ const response = sliceFromPtrLen(responsePtr, responseLen);
888
+ object_ptr.processCapabilityResponse(response);
889
+ }
890
+
891
+ export fn setCursorColor(renderer_handle: NativeHandle, color: [*]const u16) void {
892
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
893
+ object_ptr.terminal.setCursorColor(ptrToRGBA(color));
894
+ }
895
+
896
+ export fn rendererSetPaletteState(
897
+ renderer_handle: NativeHandle,
898
+ palettePtr: [*]const u16,
899
+ paletteLen: u32,
900
+ defaultFgPtr: [*]const u16,
901
+ defaultBgPtr: [*]const u16,
902
+ paletteEpoch: u32,
903
+ ) void {
904
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
905
+ if (paletteLen < 256) return;
906
+
907
+ var palette: [256]renderer.RGBA = undefined;
908
+ var index: usize = 0;
909
+ while (index < palette.len) : (index += 1) {
910
+ const base = index * 4;
911
+ palette[index] = .{ palettePtr[base], palettePtr[base + 1], palettePtr[base + 2], palettePtr[base + 3] };
912
+ }
913
+
914
+ object_ptr.setPaletteState(palette[0..], ptrToRGBA(defaultFgPtr), ptrToRGBA(defaultBgPtr), paletteEpoch);
915
+ }
916
+
917
+ pub const CursorStyleOptions = extern struct {
918
+ style: u8,
919
+ blinking: u8,
920
+ color: ?[*]const u16,
921
+ cursor: u8,
922
+ };
923
+
924
+ export fn setCursorStyleOptions(renderer_handle: NativeHandle, options: *const CursorStyleOptions) void {
925
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
926
+ const current = object_ptr.terminal.getCursorStyle();
927
+
928
+ const style = if (options.style <= 3) @as(terminal.CursorStyle, @enumFromInt(options.style)) else current.style;
929
+ const blinking = if (options.blinking <= 1) options.blinking == 1 else current.blinking;
930
+
931
+ if (options.style <= 3 or options.blinking <= 1) {
932
+ object_ptr.terminal.setCursorStyle(style, blinking);
933
+ }
934
+ if (options.color) |rgba| {
935
+ object_ptr.terminal.setCursorColor(ptrToRGBA(rgba));
936
+ }
937
+ if (options.cursor <= 5) {
938
+ object_ptr.terminal.setMousePointerStyle(@enumFromInt(options.cursor));
939
+ }
940
+ }
941
+
942
+ pub const ExternalCursorState = extern struct {
943
+ x: u32,
944
+ y: u32,
945
+ visible: bool,
946
+ style: u8,
947
+ blinking: bool,
948
+ r: f32,
949
+ g: f32,
950
+ b: f32,
951
+ a: f32,
952
+ };
953
+
954
+ export fn getCursorState(renderer_handle: NativeHandle, outPtr: *ExternalCursorState) void {
955
+ const object_ptr = acquireRenderer(renderer_handle) orelse {
956
+ outPtr.* = std.mem.zeroes(ExternalCursorState);
957
+ return;
958
+ };
959
+ const pos = object_ptr.terminal.getCursorPosition();
960
+ const style = object_ptr.terminal.getCursorStyle();
961
+ const color = object_ptr.terminal.getCursorColor();
962
+
963
+ const styleTag: u8 = switch (style.style) {
964
+ .block => 0,
965
+ .line => 1,
966
+ .underline => 2,
967
+ .default => 3,
968
+ };
969
+
970
+ outPtr.* = .{
971
+ .x = pos.x,
972
+ .y = pos.y,
973
+ .visible = pos.visible,
974
+ .style = styleTag,
975
+ .blinking = style.blinking,
976
+ .r = ansi.redF(color),
977
+ .g = ansi.greenF(color),
978
+ .b = ansi.blueF(color),
979
+ .a = ansi.alphaF(color),
980
+ };
981
+ }
982
+
983
+ export fn setDebugOverlay(renderer_handle: NativeHandle, enabled: bool, corner: u8) void {
984
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
985
+ const cornerEnum: renderer.DebugOverlayCorner = switch (corner) {
986
+ 0 => .topLeft,
987
+ 1 => .topRight,
988
+ 2 => .bottomLeft,
989
+ else => .bottomRight,
990
+ };
991
+
992
+ object_ptr.setDebugOverlay(enabled, cornerEnum);
993
+ }
994
+
995
+ export fn clearTerminal(renderer_handle: NativeHandle) void {
996
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
997
+ object_ptr.clearTerminal();
998
+ }
999
+
1000
+ export fn setTerminalTitle(renderer_handle: NativeHandle, titlePtr: ?[*]const u8, titleLen: u32) void {
1001
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1002
+ const title = sliceFromPtrLen(titlePtr, titleLen);
1003
+ object_ptr.setTerminalTitle(title);
1004
+ }
1005
+
1006
+ export fn copyToClipboardOSC52(renderer_handle: NativeHandle, target: u8, text_ptr: ?[*]const u8, text_len: u32) bool {
1007
+ const object_ptr = acquireRenderer(renderer_handle) orelse return false;
1008
+ const targetEnum = std.meta.intToEnum(terminal.ClipboardTarget, target) catch .clipboard;
1009
+ const text_utf8 = sliceFromPtrLen(text_ptr, text_len);
1010
+ return object_ptr.copyToClipboardOSC52(targetEnum, text_utf8);
1011
+ }
1012
+
1013
+ export fn clearClipboardOSC52(renderer_handle: NativeHandle, target: u8) bool {
1014
+ const object_ptr = acquireRenderer(renderer_handle) orelse return false;
1015
+ const targetEnum = std.meta.intToEnum(terminal.ClipboardTarget, target) catch .clipboard;
1016
+ return object_ptr.clearClipboardOSC52(targetEnum);
1017
+ }
1018
+
1019
+ export fn triggerNotification(renderer_handle: NativeHandle, messagePtr: [*]const u8, messageLen: u32, titlePtr: ?[*]const u8, titleLen: u32) bool {
1020
+ const object_ptr = acquireRenderer(renderer_handle) orelse return false;
1021
+ const message = messagePtr[0..@as(usize, messageLen)];
1022
+ const title = if (titlePtr) |ptr| ptr[0..@as(usize, titleLen)] else null;
1023
+ return object_ptr.triggerNotification(message, title);
1024
+ }
1025
+
1026
+ // Buffer functions
1027
+ export fn bufferClear(buffer_handle: NativeHandle, bg: [*]const u16) void {
1028
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1029
+ object_ptr.clear(ptrToRGBA(bg), null);
1030
+ }
1031
+
1032
+ export fn bufferGetCharPtr(buffer_handle: NativeHandle) ?[*]u32 {
1033
+ const object_ptr = acquireBuffer(buffer_handle) orelse return null;
1034
+ return object_ptr.getCharPtr();
1035
+ }
1036
+
1037
+ export fn bufferGetFgPtr(buffer_handle: NativeHandle) ?[*]RGBA {
1038
+ const object_ptr = acquireBuffer(buffer_handle) orelse return null;
1039
+ return object_ptr.getFgPtr();
1040
+ }
1041
+
1042
+ export fn bufferGetBgPtr(buffer_handle: NativeHandle) ?[*]RGBA {
1043
+ const object_ptr = acquireBuffer(buffer_handle) orelse return null;
1044
+ return object_ptr.getBgPtr();
1045
+ }
1046
+
1047
+ export fn bufferGetAttributesPtr(buffer_handle: NativeHandle) ?[*]u32 {
1048
+ const object_ptr = acquireBuffer(buffer_handle) orelse return null;
1049
+ return object_ptr.getAttributesPtr();
1050
+ }
1051
+
1052
+ export fn bufferGetRespectAlpha(buffer_handle: NativeHandle) bool {
1053
+ const object_ptr = acquireBuffer(buffer_handle) orelse return false;
1054
+ return object_ptr.getRespectAlpha();
1055
+ }
1056
+
1057
+ export fn bufferSetRespectAlpha(buffer_handle: NativeHandle, respectAlpha: bool) void {
1058
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1059
+ object_ptr.setRespectAlpha(respectAlpha);
1060
+ }
1061
+
1062
+ export fn bufferGetId(buffer_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
1063
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 0;
1064
+ if (maxLen == 0) return 0;
1065
+
1066
+ const out = outPtr orelse return 0;
1067
+ const id = object_ptr.getId();
1068
+ const copyLen = @min(id.len, @as(usize, maxLen));
1069
+ @memcpy(out[0..copyLen], id[0..copyLen]);
1070
+ return @intCast(copyLen);
1071
+ }
1072
+
1073
+ export fn bufferGetRealCharSize(buffer_handle: NativeHandle) u32 {
1074
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 0;
1075
+ return object_ptr.getRealCharSize();
1076
+ }
1077
+
1078
+ export fn bufferWriteResolvedChars(buffer_handle: NativeHandle, outputPtr: ?[*]u8, outputLen: u32, addLineBreaks: bool) u32 {
1079
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 0;
1080
+ if (outputLen == 0) return 0;
1081
+
1082
+ const output = outputPtr orelse return 0;
1083
+ const output_slice = output[0..@as(usize, outputLen)];
1084
+ return object_ptr.writeResolvedChars(output_slice, addLineBreaks) catch 0;
1085
+ }
1086
+
1087
+ export fn bufferDrawText(buffer_handle: NativeHandle, text: ?[*]const u8, textLen: u32, x: u32, y: u32, fg: [*]const u16, bg: ?[*]const u16, attributes: u32) void {
1088
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1089
+ object_ptr.drawText(
1090
+ sliceFromPtrLen(text, textLen),
1091
+ x,
1092
+ y,
1093
+ ptrToRGBA(fg),
1094
+ optionalPtrToRGBA(bg),
1095
+ attributes,
1096
+ ) catch {};
1097
+ }
1098
+
1099
+ export fn bufferSetCellWithAlphaBlending(buffer_handle: NativeHandle, x: u32, y: u32, char: u32, fg: [*]const u16, bg: [*]const u16, attributes: u32) void {
1100
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1101
+ object_ptr.setCellWithAlphaBlending(x, y, char, ptrToRGBA(fg), ptrToRGBA(bg), attributes);
1102
+ }
1103
+
1104
+ export fn bufferSetCell(buffer_handle: NativeHandle, x: u32, y: u32, char: u32, fg: [*]const u16, bg: [*]const u16, attributes: u32) void {
1105
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1106
+ object_ptr.set(x, y, .{
1107
+ .char = char,
1108
+ .fg = ptrToRGBA(fg),
1109
+ .bg = ptrToRGBA(bg),
1110
+ .attributes = attributes,
1111
+ });
1112
+ }
1113
+
1114
+ export fn bufferFillRect(buffer_handle: NativeHandle, x: u32, y: u32, width: u32, height: u32, bg: [*]const u16) void {
1115
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1116
+ object_ptr.fillRect(x, y, width, height, ptrToRGBA(bg));
1117
+ }
1118
+
1119
+ export fn bufferColorMatrix(buffer_handle: NativeHandle, matrixPtr: [*]const f32, cellMaskPtr: [*]const f32, cellMaskCount: u32, strength: f32, target: u8) void {
1120
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1121
+ if (cellMaskCount == 0) return;
1122
+ const matrix = matrixPtr[0..16];
1123
+ const len = @as(usize, cellMaskCount) * 3;
1124
+ const cellMask = cellMaskPtr[0..len];
1125
+ const targetEnum: buffer_effects.ColorTarget = @enumFromInt(target);
1126
+ buffer_effects.colorMatrix(object_ptr, matrix, cellMask, strength, targetEnum);
1127
+ }
1128
+
1129
+ export fn bufferColorMatrixUniform(buffer_handle: NativeHandle, matrixPtr: [*]const f32, strength: f32, target: u8) void {
1130
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1131
+ const matrix = matrixPtr[0..16];
1132
+ const targetEnum: buffer_effects.ColorTarget = @enumFromInt(target);
1133
+ buffer_effects.colorMatrixUniform(object_ptr, matrix, strength, targetEnum);
1134
+ }
1135
+
1136
+ export fn bufferDrawPackedBuffer(buffer_handle: NativeHandle, data: [*]const u8, dataLen: u32, posX: u32, posY: u32, terminalWidthCells: u32, terminalHeightCells: u32) void {
1137
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1138
+ object_ptr.drawPackedBuffer(data, dataLen, posX, posY, terminalWidthCells, terminalHeightCells);
1139
+ }
1140
+
1141
+ export fn bufferDrawGrayscaleBuffer(buffer_handle: NativeHandle, posX: i32, posY: i32, intensities: [*]const f32, srcWidth: u32, srcHeight: u32, fg: ?[*]const u16, bg: ?[*]const u16) void {
1142
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1143
+ object_ptr.drawGrayscaleBuffer(
1144
+ posX,
1145
+ posY,
1146
+ intensities,
1147
+ srcWidth,
1148
+ srcHeight,
1149
+ optionalPtrToRGBA(fg),
1150
+ optionalPtrToRGBA(bg),
1151
+ );
1152
+ }
1153
+
1154
+ export fn bufferDrawGrayscaleBufferSupersampled(buffer_handle: NativeHandle, posX: i32, posY: i32, intensities: [*]const f32, srcWidth: u32, srcHeight: u32, fg: ?[*]const u16, bg: ?[*]const u16) void {
1155
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1156
+ object_ptr.drawGrayscaleBufferSupersampled(
1157
+ posX,
1158
+ posY,
1159
+ intensities,
1160
+ srcWidth,
1161
+ srcHeight,
1162
+ optionalPtrToRGBA(fg),
1163
+ optionalPtrToRGBA(bg),
1164
+ );
1165
+ }
1166
+
1167
+ export fn bufferPushScissorRect(buffer_handle: NativeHandle, x: i32, y: i32, width: u32, height: u32) void {
1168
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1169
+ object_ptr.pushScissorRect(x, y, width, height) catch {};
1170
+ }
1171
+
1172
+ export fn bufferPopScissorRect(buffer_handle: NativeHandle) void {
1173
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1174
+ object_ptr.popScissorRect();
1175
+ }
1176
+
1177
+ export fn bufferClearScissorRects(buffer_handle: NativeHandle) void {
1178
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1179
+ object_ptr.clearScissorRects();
1180
+ }
1181
+
1182
+ // Opacity stack functions
1183
+ export fn bufferPushOpacity(buffer_handle: NativeHandle, opacity: f32) void {
1184
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1185
+ object_ptr.pushOpacity(opacity) catch {};
1186
+ }
1187
+
1188
+ export fn bufferPopOpacity(buffer_handle: NativeHandle) void {
1189
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1190
+ object_ptr.popOpacity();
1191
+ }
1192
+
1193
+ export fn bufferGetCurrentOpacity(buffer_handle: NativeHandle) f32 {
1194
+ const object_ptr = acquireBuffer(buffer_handle) orelse return 1;
1195
+ return object_ptr.getCurrentOpacity();
1196
+ }
1197
+
1198
+ export fn bufferClearOpacity(buffer_handle: NativeHandle) void {
1199
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1200
+ object_ptr.clearOpacity();
1201
+ }
1202
+
1203
+ export fn bufferDrawSuperSampleBuffer(buffer_handle: NativeHandle, x: u32, y: u32, pixelData: [*]const u8, len: u32, format: u8, alignedBytesPerRow: u32) void {
1204
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1205
+ object_ptr.drawSuperSampleBuffer(x, y, pixelData, len, format, alignedBytesPerRow);
1206
+ }
1207
+
1208
+ export fn linkAlloc(urlPtr: ?[*]const u8, urlLen: u32) u32 {
1209
+ const url = sliceFromPtrLen(urlPtr, urlLen);
1210
+ const link_pool = link.initGlobalLinkPool(globalArena);
1211
+ return link_pool.alloc(url) catch 0;
1212
+ }
1213
+
1214
+ export fn linkGetUrl(id: u32, outPtr: ?[*]u8, maxLen: u32) u32 {
1215
+ if (maxLen == 0) return 0;
1216
+
1217
+ const out = outPtr orelse return 0;
1218
+ const link_pool = link.initGlobalLinkPool(globalArena);
1219
+ const url_bytes = link_pool.get(id) catch return 0;
1220
+ const copyLen = @min(url_bytes.len, @as(usize, maxLen));
1221
+ @memcpy(out[0..copyLen], url_bytes[0..copyLen]);
1222
+ return @intCast(copyLen);
1223
+ }
1224
+
1225
+ export fn attributesWithLink(baseAttributes: u32, linkId: u32) u32 {
1226
+ return ansi.TextAttributes.setLinkId(baseAttributes, linkId);
1227
+ }
1228
+
1229
+ export fn attributesGetLinkId(attributes: u32) u32 {
1230
+ return ansi.TextAttributes.getLinkId(attributes);
1231
+ }
1232
+
1233
+ pub const ExternalGridDrawOptions = extern struct {
1234
+ draw_inner: bool,
1235
+ draw_outer: bool,
1236
+ };
1237
+
1238
+ export fn bufferDrawGrid(
1239
+ buffer_handle: NativeHandle,
1240
+ borderChars: [*]const u32,
1241
+ borderFg: [*]const u16,
1242
+ borderBg: [*]const u16,
1243
+ columnOffsets: [*]const i32,
1244
+ columnCount: u32,
1245
+ rowOffsets: [*]const i32,
1246
+ rowCount: u32,
1247
+ options: *const ExternalGridDrawOptions,
1248
+ ) void {
1249
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1250
+ object_ptr.drawGrid(
1251
+ borderChars,
1252
+ ptrToRGBA(borderFg),
1253
+ ptrToRGBA(borderBg),
1254
+ columnOffsets,
1255
+ columnCount,
1256
+ rowOffsets,
1257
+ rowCount,
1258
+ options.draw_inner,
1259
+ options.draw_outer,
1260
+ );
1261
+ }
1262
+
1263
+ export fn bufferDrawBox(
1264
+ buffer_handle: NativeHandle,
1265
+ x: i32,
1266
+ y: i32,
1267
+ width: u32,
1268
+ height: u32,
1269
+ borderChars: [*]const u32,
1270
+ packedOptions: u32,
1271
+ borderColor: [*]const u16,
1272
+ backgroundColor: [*]const u16,
1273
+ titleColor: [*]const u16,
1274
+ title: ?[*]const u8,
1275
+ titleLen: u32,
1276
+ bottomTitle: ?[*]const u8,
1277
+ bottomTitleLen: u32,
1278
+ ) void {
1279
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1280
+ const borderSides: buffer.BorderSides = .{
1281
+ .top = (packedOptions & 0b1000) != 0,
1282
+ .right = (packedOptions & 0b0100) != 0,
1283
+ .bottom = (packedOptions & 0b0010) != 0,
1284
+ .left = (packedOptions & 0b0001) != 0,
1285
+ };
1286
+
1287
+ const shouldFill = ((packedOptions >> 4) & 1) != 0;
1288
+ const titleAlignment = @as(u8, @intCast((packedOptions >> 5) & 0b11));
1289
+ const bottomTitleAlignment = @as(u8, @intCast((packedOptions >> 7) & 0b11));
1290
+ const titleSlice = if (title) |t| t[0..titleLen] else null;
1291
+
1292
+ const bottomTitleSlice = if (bottomTitle) |bt| bt[0..bottomTitleLen] else null;
1293
+
1294
+ object_ptr.drawBox(
1295
+ x,
1296
+ y,
1297
+ width,
1298
+ height,
1299
+ borderChars,
1300
+ borderSides,
1301
+ ptrToRGBA(borderColor),
1302
+ ptrToRGBA(backgroundColor),
1303
+ ptrToRGBA(titleColor),
1304
+ shouldFill,
1305
+ titleSlice,
1306
+ titleAlignment,
1307
+ bottomTitleSlice,
1308
+ bottomTitleAlignment,
1309
+ ) catch {};
1310
+ }
1311
+
1312
+ export fn bufferResize(buffer_handle: NativeHandle, width: u32, height: u32) void {
1313
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
1314
+ object_ptr.resize(width, height) catch {};
1315
+ }
1316
+
1317
+ export fn resizeRenderer(renderer_handle: NativeHandle, width: u32, height: u32) void {
1318
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1319
+ object_ptr.resize(width, height) catch {};
1320
+ }
1321
+
1322
+ export fn addToHitGrid(renderer_handle: NativeHandle, x: i32, y: i32, width: u32, height: u32, id: u32) void {
1323
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1324
+ object_ptr.addToHitGrid(x, y, width, height, id);
1325
+ }
1326
+
1327
+ export fn clearCurrentHitGrid(renderer_handle: NativeHandle) void {
1328
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1329
+ object_ptr.clearCurrentHitGrid();
1330
+ }
1331
+
1332
+ export fn hitGridPushScissorRect(renderer_handle: NativeHandle, x: i32, y: i32, width: u32, height: u32) void {
1333
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1334
+ object_ptr.hitGridPushScissorRect(x, y, width, height);
1335
+ }
1336
+
1337
+ export fn hitGridPopScissorRect(renderer_handle: NativeHandle) void {
1338
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1339
+ object_ptr.hitGridPopScissorRect();
1340
+ }
1341
+
1342
+ export fn hitGridClearScissorRects(renderer_handle: NativeHandle) void {
1343
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1344
+ object_ptr.hitGridClearScissorRects();
1345
+ }
1346
+
1347
+ export fn addToCurrentHitGridClipped(renderer_handle: NativeHandle, x: i32, y: i32, width: u32, height: u32, id: u32) void {
1348
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1349
+ object_ptr.addToCurrentHitGridClipped(x, y, width, height, id);
1350
+ }
1351
+
1352
+ export fn checkHit(renderer_handle: NativeHandle, x: u32, y: u32) u32 {
1353
+ const object_ptr = acquireRenderer(renderer_handle) orelse return 0;
1354
+ return object_ptr.checkHit(x, y);
1355
+ }
1356
+
1357
+ export fn getHitGridDirty(renderer_handle: NativeHandle) bool {
1358
+ const object_ptr = acquireRenderer(renderer_handle) orelse return false;
1359
+ return object_ptr.getHitGridDirty();
1360
+ }
1361
+
1362
+ export fn dumpHitGrid(renderer_handle: NativeHandle) void {
1363
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1364
+ object_ptr.dumpHitGrid();
1365
+ }
1366
+
1367
+ export fn dumpBuffers(renderer_handle: NativeHandle, timestamp: i64) void {
1368
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1369
+ object_ptr.dumpBuffers(timestamp);
1370
+ }
1371
+
1372
+ export fn dumpOutputBuffer(renderer_handle: NativeHandle, timestamp: i64) void {
1373
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1374
+ object_ptr.dumpOutputBuffer(timestamp);
1375
+ }
1376
+
1377
+ export fn restoreTerminalModes(renderer_handle: NativeHandle) void {
1378
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1379
+ object_ptr.restoreTerminalModes();
1380
+ }
1381
+
1382
+ export fn enableMouse(renderer_handle: NativeHandle, enableMovement: bool) void {
1383
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1384
+ object_ptr.enableMouse(enableMovement);
1385
+ }
1386
+
1387
+ export fn disableMouse(renderer_handle: NativeHandle) void {
1388
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1389
+ object_ptr.disableMouse();
1390
+ }
1391
+
1392
+ export fn queryPixelResolution(renderer_handle: NativeHandle) void {
1393
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1394
+ object_ptr.queryPixelResolution();
1395
+ }
1396
+
1397
+ export fn queryThemeColors(renderer_handle: NativeHandle) void {
1398
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1399
+ object_ptr.queryThemeColors();
1400
+ }
1401
+
1402
+ export fn enableKittyKeyboard(renderer_handle: NativeHandle, flags: u8) void {
1403
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1404
+ object_ptr.enableKittyKeyboard(flags);
1405
+ }
1406
+
1407
+ export fn disableKittyKeyboard(renderer_handle: NativeHandle) void {
1408
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1409
+ object_ptr.disableKittyKeyboard();
1410
+ }
1411
+
1412
+ export fn setKittyKeyboardFlags(renderer_handle: NativeHandle, flags: u8) void {
1413
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1414
+ object_ptr.setKittyKeyboardFlags(flags);
1415
+ }
1416
+
1417
+ export fn getKittyKeyboardFlags(renderer_handle: NativeHandle) u8 {
1418
+ const object_ptr = acquireRenderer(renderer_handle) orelse return 0;
1419
+ return object_ptr.getKittyKeyboardFlags();
1420
+ }
1421
+
1422
+ export fn setupTerminal(renderer_handle: NativeHandle, useAlternateScreen: bool) void {
1423
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1424
+ object_ptr.setupTerminal(useAlternateScreen);
1425
+ }
1426
+
1427
+ export fn suspendRenderer(renderer_handle: NativeHandle) void {
1428
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1429
+ object_ptr.suspendRenderer();
1430
+ }
1431
+
1432
+ export fn resumeRenderer(renderer_handle: NativeHandle) void {
1433
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1434
+ object_ptr.resumeRenderer();
1435
+ }
1436
+
1437
+ export fn writeOut(renderer_handle: NativeHandle, dataPtr: ?[*]const u8, dataLen: u32) void {
1438
+ const object_ptr = acquireRenderer(renderer_handle) orelse return;
1439
+ const data = sliceFromPtrLen(dataPtr, dataLen);
1440
+ if (data.len == 0) return;
1441
+ object_ptr.writeOut(data);
1442
+ }
1443
+
1444
+ fn destroyTextBufferViewHandle(view_handle: NativeHandle) void {
1445
+ const token = handles.beginDestroy(view_handle, .text_buffer_view, text_buffer_view.UnifiedTextBufferView) orelse return;
1446
+ token.ptr.deinit();
1447
+ handles.finishDestroy(token.handle);
1448
+ }
1449
+
1450
+ fn destroyTextBufferViewChildren(owner: NativeHandle) void {
1451
+ while (handles.findChild(owner, .text_buffer_view)) |view_handle| {
1452
+ destroyTextBufferViewHandle(view_handle);
1453
+ }
1454
+ }
1455
+
1456
+ export fn createTextBuffer(widthMethod: u8) NativeHandle {
1457
+ const pool = gp.initGlobalPool(globalArena);
1458
+ const link_pool = link.initGlobalLinkPool(globalArena);
1459
+ const wMethod: utf8.WidthMethod = if (widthMethod == 0) .wcwidth else .unicode;
1460
+
1461
+ const tb = text_buffer.UnifiedTextBuffer.init(globalAllocator, pool, link_pool, wMethod) catch {
1462
+ return INVALID_HANDLE;
1463
+ };
1464
+ return handles.insert(.text_buffer, erasePtr(tb)) catch {
1465
+ tb.deinit();
1466
+ return INVALID_HANDLE;
1467
+ };
1468
+ }
1469
+
1470
+ export fn destroyTextBuffer(tb_handle: NativeHandle) void {
1471
+ const token = handles.beginDestroy(tb_handle, .text_buffer, text_buffer.UnifiedTextBuffer) orelse return;
1472
+ destroyTextBufferViewChildren(token.handle);
1473
+ token.ptr.deinit();
1474
+ handles.finishDestroy(token.handle);
1475
+ }
1476
+
1477
+ export fn textBufferGetLength(tb_handle: NativeHandle) u32 {
1478
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
1479
+ return object_ptr.getLength();
1480
+ }
1481
+
1482
+ export fn textBufferGetByteSize(tb_handle: NativeHandle) u32 {
1483
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
1484
+ return object_ptr.getByteSize();
1485
+ }
1486
+
1487
+ export fn textBufferReset(tb_handle: NativeHandle) void {
1488
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1489
+ object_ptr.reset();
1490
+ }
1491
+
1492
+ export fn textBufferClear(tb_handle: NativeHandle) void {
1493
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1494
+ object_ptr.clear();
1495
+ }
1496
+
1497
+ export fn textBufferSetDefaultFg(tb_handle: NativeHandle, fg: ?[*]const u16) void {
1498
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1499
+ object_ptr.setDefaultFg(optionalPtrToRGBA(fg));
1500
+ }
1501
+
1502
+ export fn textBufferSetDefaultBg(tb_handle: NativeHandle, bg: ?[*]const u16) void {
1503
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1504
+ object_ptr.setDefaultBg(optionalPtrToRGBA(bg));
1505
+ }
1506
+
1507
+ export fn textBufferSetDefaultAttributes(tb_handle: NativeHandle, attr: ?[*]const u32) void {
1508
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1509
+ const attributes = if (attr) |a| a[0] else null;
1510
+ object_ptr.setDefaultAttributes(attributes);
1511
+ }
1512
+
1513
+ export fn textBufferResetDefaults(tb_handle: NativeHandle) void {
1514
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1515
+ object_ptr.resetDefaults();
1516
+ }
1517
+
1518
+ export fn textBufferGetTabWidth(tb_handle: NativeHandle) u8 {
1519
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
1520
+ return object_ptr.tabWidth();
1521
+ }
1522
+
1523
+ export fn textBufferSetTabWidth(tb_handle: NativeHandle, width: u8) void {
1524
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1525
+ object_ptr.setTabWidth(width);
1526
+ }
1527
+
1528
+ export fn textBufferRegisterMemBuffer(tb_handle: NativeHandle, dataPtr: ?[*]const u8, dataLen: u32, owned: bool) u16 {
1529
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0xFFFF;
1530
+ const data = sliceFromPtrLen(dataPtr, dataLen);
1531
+ const mem_id = object_ptr.registerMemBuffer(data, owned) catch return 0xFFFF;
1532
+ return @intCast(mem_id);
1533
+ }
1534
+
1535
+ export fn textBufferReplaceMemBuffer(tb_handle: NativeHandle, id: u8, dataPtr: ?[*]const u8, dataLen: u32, owned: bool) bool {
1536
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return false;
1537
+ const data = sliceFromPtrLen(dataPtr, dataLen);
1538
+ object_ptr.replaceMemBuffer(id, data, owned) catch return false;
1539
+ return true;
1540
+ }
1541
+
1542
+ export fn textBufferClearMemRegistry(tb_handle: NativeHandle) void {
1543
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1544
+ object_ptr.clearMemRegistry();
1545
+ }
1546
+
1547
+ export fn textBufferSetTextFromMem(tb_handle: NativeHandle, id: u8) void {
1548
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1549
+ object_ptr.setTextFromMemId(id) catch {};
1550
+ }
1551
+
1552
+ export fn textBufferAppend(tb_handle: NativeHandle, dataPtr: ?[*]const u8, dataLen: u32) void {
1553
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1554
+ const data = sliceFromPtrLen(dataPtr, dataLen);
1555
+ object_ptr.append(data) catch {};
1556
+ }
1557
+
1558
+ export fn textBufferAppendFromMemId(tb_handle: NativeHandle, id: u8) void {
1559
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1560
+ object_ptr.appendFromMemId(id) catch {};
1561
+ }
1562
+
1563
+ export fn textBufferLoadFile(tb_handle: NativeHandle, pathPtr: ?[*]const u8, pathLen: u32) bool {
1564
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return false;
1565
+ const path = sliceFromPtrLen(pathPtr, pathLen);
1566
+ object_ptr.loadFile(path) catch return false;
1567
+ return true;
1568
+ }
1569
+
1570
+ export fn textBufferSetStyledText(
1571
+ tb_handle: NativeHandle,
1572
+ chunksPtr: ?[*]const text_buffer.StyledChunk,
1573
+ chunkCount: u32,
1574
+ ) void {
1575
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
1576
+ if (chunkCount == 0) return;
1577
+ const chunks = chunksPtr.?[0..@as(usize, chunkCount)];
1578
+ object_ptr.setStyledText(chunks) catch {};
1579
+ }
1580
+
1581
+ export fn textBufferGetLineCount(tb_handle: NativeHandle) u32 {
1582
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
1583
+ return object_ptr.getLineCount();
1584
+ }
1585
+
1586
+ export fn textBufferGetPlainText(tb_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
1587
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
1588
+ if (maxLen == 0) return 0;
1589
+
1590
+ const out = outPtr orelse return 0;
1591
+ const outBuffer = out[0..@as(usize, maxLen)];
1592
+ return @intCast(object_ptr.getPlainTextIntoBuffer(outBuffer));
1593
+ }
1594
+
1595
+ // TextBufferView functions (Array-based for backward compatibility)
1596
+ export fn createTextBufferView(tb_handle: NativeHandle) NativeHandle {
1597
+ if (!handles.isOwned(tb_handle, .text_buffer)) return INVALID_HANDLE;
1598
+
1599
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return INVALID_HANDLE;
1600
+ const view = text_buffer_view.UnifiedTextBufferView.init(globalAllocator, object_ptr) catch {
1601
+ return INVALID_HANDLE;
1602
+ };
1603
+ return handles.insertOwnedChild(.text_buffer_view, erasePtr(view), tb_handle) catch {
1604
+ view.deinit();
1605
+ return INVALID_HANDLE;
1606
+ };
1607
+ }
1608
+
1609
+ export fn destroyTextBufferView(view_handle: NativeHandle) void {
1610
+ destroyTextBufferViewHandle(view_handle);
1611
+ }
1612
+
1613
+ export fn textBufferViewSetSelection(view_handle: NativeHandle, start: u32, end: u32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) void {
1614
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1615
+ object_ptr.setSelectionStyle(start, end, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
1616
+ }
1617
+
1618
+ export fn textBufferViewResetSelection(view_handle: NativeHandle) void {
1619
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1620
+ object_ptr.resetSelection();
1621
+ }
1622
+
1623
+ export fn textBufferViewGetSelectionInfo(view_handle: NativeHandle) u64 {
1624
+ const object_ptr = acquireTextBufferView(view_handle) orelse return std.math.maxInt(u64);
1625
+ return object_ptr.packSelectionInfo();
1626
+ }
1627
+
1628
+ export fn textBufferViewSetLocalSelection(view_handle: NativeHandle, anchorX: i32, anchorY: i32, focusX: i32, focusY: i32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) bool {
1629
+ const object_ptr = acquireTextBufferView(view_handle) orelse return false;
1630
+ return object_ptr.setLocalSelectionStyle(anchorX, anchorY, focusX, focusY, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
1631
+ }
1632
+
1633
+ export fn textBufferViewUpdateSelection(view_handle: NativeHandle, end: u32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) void {
1634
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1635
+ object_ptr.updateSelectionStyle(end, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
1636
+ }
1637
+
1638
+ export fn textBufferViewUpdateLocalSelection(view_handle: NativeHandle, anchorX: i32, anchorY: i32, focusX: i32, focusY: i32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) bool {
1639
+ const object_ptr = acquireTextBufferView(view_handle) orelse return false;
1640
+ return object_ptr.updateLocalSelectionStyle(anchorX, anchorY, focusX, focusY, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
1641
+ }
1642
+
1643
+ export fn textBufferViewResetLocalSelection(view_handle: NativeHandle) void {
1644
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1645
+ object_ptr.resetLocalSelection();
1646
+ }
1647
+
1648
+ export fn textBufferViewSetWrapWidth(view_handle: NativeHandle, width: u32) void {
1649
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1650
+ object_ptr.setWrapWidth(if (width == 0) null else width);
1651
+ }
1652
+
1653
+ export fn textBufferViewSetWrapMode(view_handle: NativeHandle, mode: u8) void {
1654
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1655
+ const wrapMode: text_buffer.WrapMode = switch (mode) {
1656
+ 0 => .none,
1657
+ 1 => .char,
1658
+ 2 => .word,
1659
+ else => .none,
1660
+ };
1661
+ object_ptr.setWrapMode(wrapMode);
1662
+ }
1663
+
1664
+ export fn textBufferViewSetFirstLineOffset(view_handle: NativeHandle, offset: u32) void {
1665
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1666
+ object_ptr.setFirstLineOffset(offset);
1667
+ }
1668
+
1669
+ export fn textBufferViewSetViewportSize(view_handle: NativeHandle, width: u32, height: u32) void {
1670
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1671
+ object_ptr.setViewportSize(width, height);
1672
+ }
1673
+
1674
+ export fn textBufferViewSetViewport(view_handle: NativeHandle, x: u32, y: u32, width: u32, height: u32) void {
1675
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1676
+ object_ptr.setViewport(.{
1677
+ .x = x,
1678
+ .y = y,
1679
+ .width = width,
1680
+ .height = height,
1681
+ });
1682
+ }
1683
+
1684
+ export fn textBufferViewGetVirtualLineCount(view_handle: NativeHandle) u32 {
1685
+ const object_ptr = acquireTextBufferView(view_handle) orelse return 0;
1686
+ return object_ptr.getVirtualLineCount();
1687
+ }
1688
+
1689
+ export fn textBufferViewGetLineInfoDirect(view_handle: NativeHandle, outPtr: *ExternalLineInfo) void {
1690
+ const object_ptr = acquireTextBufferView(view_handle) orelse {
1691
+ emptyLineInfo(outPtr);
1692
+ return;
1693
+ };
1694
+ const line_info = object_ptr.getCachedLineInfo();
1695
+
1696
+ outPtr.* = .{
1697
+ .start_cols_ptr = line_info.line_start_cols.ptr,
1698
+ .start_cols_len = @intCast(line_info.line_start_cols.len),
1699
+ .width_cols_ptr = line_info.line_width_cols.ptr,
1700
+ .width_cols_len = @intCast(line_info.line_width_cols.len),
1701
+ .sources_ptr = line_info.line_sources.ptr,
1702
+ .sources_len = @intCast(line_info.line_sources.len),
1703
+ .wraps_ptr = line_info.line_wraps.ptr,
1704
+ .wraps_len = @intCast(line_info.line_wraps.len),
1705
+ .width_cols_max = line_info.line_width_cols_max,
1706
+ };
1707
+ }
1708
+
1709
+ export fn textBufferViewGetLogicalLineInfoDirect(view_handle: NativeHandle, outPtr: *ExternalLineInfo) void {
1710
+ const object_ptr = acquireTextBufferView(view_handle) orelse {
1711
+ emptyLineInfo(outPtr);
1712
+ return;
1713
+ };
1714
+ const line_info = object_ptr.getLogicalLineInfo();
1715
+
1716
+ outPtr.* = .{
1717
+ .start_cols_ptr = line_info.line_start_cols.ptr,
1718
+ .start_cols_len = @intCast(line_info.line_start_cols.len),
1719
+ .width_cols_ptr = line_info.line_width_cols.ptr,
1720
+ .width_cols_len = @intCast(line_info.line_width_cols.len),
1721
+ .sources_ptr = line_info.line_sources.ptr,
1722
+ .sources_len = @intCast(line_info.line_sources.len),
1723
+ .wraps_ptr = line_info.line_wraps.ptr,
1724
+ .wraps_len = @intCast(line_info.line_wraps.len),
1725
+ .width_cols_max = line_info.line_width_cols_max,
1726
+ };
1727
+ }
1728
+
1729
+ export fn textBufferViewGetSelectedText(view_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
1730
+ const object_ptr = acquireTextBufferView(view_handle) orelse return 0;
1731
+ if (maxLen == 0) return 0;
1732
+
1733
+ const out = outPtr orelse return 0;
1734
+ const outBuffer = out[0..@as(usize, maxLen)];
1735
+ return @intCast(object_ptr.getSelectedTextIntoBuffer(outBuffer));
1736
+ }
1737
+
1738
+ export fn textBufferViewGetPlainText(view_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
1739
+ const object_ptr = acquireTextBufferView(view_handle) orelse return 0;
1740
+ if (maxLen == 0) return 0;
1741
+
1742
+ const out = outPtr orelse return 0;
1743
+ const outBuffer = out[0..@as(usize, maxLen)];
1744
+ return @intCast(object_ptr.getPlainTextIntoBuffer(outBuffer));
1745
+ }
1746
+
1747
+ export fn textBufferViewSetTabIndicator(view_handle: NativeHandle, indicator: u32) void {
1748
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1749
+ object_ptr.setTabIndicator(indicator);
1750
+ }
1751
+
1752
+ export fn textBufferViewSetTabIndicatorColor(view_handle: NativeHandle, color: [*]const u16) void {
1753
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1754
+ object_ptr.setTabIndicatorColor(ptrToRGBA(color));
1755
+ }
1756
+
1757
+ export fn textBufferViewSetTruncate(view_handle: NativeHandle, truncate: bool) void {
1758
+ const object_ptr = acquireTextBufferView(view_handle) orelse return;
1759
+ object_ptr.setTruncate(truncate);
1760
+ }
1761
+
1762
+ pub const ExternalMeasureResult = extern struct {
1763
+ line_count: u32,
1764
+ width_cols_max: u32,
1765
+ };
1766
+
1767
+ export fn textBufferViewMeasureForDimensions(view_handle: NativeHandle, width: u32, height: u32, outPtr: *ExternalMeasureResult) bool {
1768
+ const object_ptr = acquireTextBufferView(view_handle) orelse {
1769
+ outPtr.* = std.mem.zeroes(ExternalMeasureResult);
1770
+ return false;
1771
+ };
1772
+ const result = object_ptr.measureForDimensions(width, height) catch return false;
1773
+ outPtr.* = .{
1774
+ .line_count = result.line_count,
1775
+ .width_cols_max = result.width_cols_max,
1776
+ };
1777
+ return true;
1778
+ }
1779
+
1780
+ // ===== EditBuffer Exports =====
1781
+
1782
+ fn destroyEditorViewHandle(view_handle: NativeHandle) void {
1783
+ const token = handles.beginDestroy(view_handle, .editor_view, editor_view.EditorView) orelse return;
1784
+ handles.invalidateChildren(token.handle);
1785
+ token.ptr.deinit();
1786
+ handles.finishDestroy(token.handle);
1787
+ }
1788
+
1789
+ fn destroyEditorViewChildren(owner: NativeHandle) void {
1790
+ while (handles.findChild(owner, .editor_view)) |view_handle| {
1791
+ destroyEditorViewHandle(view_handle);
1792
+ }
1793
+ }
1794
+
1795
+ export fn createEditBuffer(widthMethod: u8, event_sink_handle: NativeHandle) NativeHandle {
1796
+ const pool = gp.initGlobalPool(globalArena);
1797
+ const link_pool = link.initGlobalLinkPool(globalArena);
1798
+ const wMethod: utf8.WidthMethod = if (widthMethod == 0) .wcwidth else .unicode;
1799
+ const event_sink_ptr = if (event_sink_handle == INVALID_HANDLE) null else acquireEventSink(event_sink_handle);
1800
+ const event_sink = if (event_sink_ptr) |object_ptr| object_ptr else null;
1801
+
1802
+ const edit_buffer = edit_buffer_mod.EditBuffer.init(
1803
+ globalAllocator,
1804
+ pool,
1805
+ link_pool,
1806
+ wMethod,
1807
+ event_sink,
1808
+ ) catch return INVALID_HANDLE;
1809
+
1810
+ const edit_handle = handles.insert(.edit_buffer, erasePtr(edit_buffer)) catch {
1811
+ edit_buffer.deinit();
1812
+ return INVALID_HANDLE;
1813
+ };
1814
+ _ = handles.getOrInsertBorrowed(.text_buffer, erasePtr(edit_buffer.getTextBuffer()), edit_handle) catch {
1815
+ if (handles.beginDestroy(edit_handle, .edit_buffer, edit_buffer_mod.EditBuffer)) |token| {
1816
+ token.ptr.deinit();
1817
+ handles.finishDestroy(token.handle);
1818
+ }
1819
+ return INVALID_HANDLE;
1820
+ };
1821
+ return edit_handle;
1822
+ }
1823
+
1824
+ export fn destroyEditBuffer(edit_handle: NativeHandle) void {
1825
+ const token = handles.beginDestroy(edit_handle, .edit_buffer, edit_buffer_mod.EditBuffer) orelse return;
1826
+ destroyEditorViewChildren(token.handle);
1827
+ handles.invalidateChildren(token.handle);
1828
+ token.ptr.deinit();
1829
+ handles.finishDestroy(token.handle);
1830
+ }
1831
+
1832
+ export fn editBufferGetTextBuffer(edit_handle: NativeHandle) NativeHandle {
1833
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return INVALID_HANDLE;
1834
+ return handles.getOrInsertBorrowed(.text_buffer, erasePtr(object_ptr.getTextBuffer()), edit_handle) catch INVALID_HANDLE;
1835
+ }
1836
+
1837
+ export fn editBufferInsertText(edit_handle: NativeHandle, textPtr: ?[*]const u8, textLen: u32) void {
1838
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1839
+ const text = sliceFromPtrLen(textPtr, textLen);
1840
+ object_ptr.insertText(text) catch {};
1841
+ }
1842
+
1843
+ export fn editBufferDeleteRange(edit_handle: NativeHandle, start_row: u32, start_col: u32, end_row: u32, end_col: u32) void {
1844
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1845
+ const start: edit_buffer_mod.Cursor = .{ .row = start_row, .col = start_col };
1846
+ const end: edit_buffer_mod.Cursor = .{ .row = end_row, .col = end_col };
1847
+ object_ptr.deleteRange(start, end) catch {};
1848
+ }
1849
+
1850
+ export fn editBufferDeleteCharBackward(edit_handle: NativeHandle) void {
1851
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1852
+ object_ptr.backspace() catch {};
1853
+ }
1854
+
1855
+ export fn editBufferDeleteChar(edit_handle: NativeHandle) void {
1856
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1857
+ object_ptr.deleteForward() catch {};
1858
+ }
1859
+
1860
+ export fn editBufferMoveCursorLeft(edit_handle: NativeHandle) void {
1861
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1862
+ object_ptr.moveLeft();
1863
+ }
1864
+
1865
+ export fn editBufferMoveCursorRight(edit_handle: NativeHandle) void {
1866
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1867
+ object_ptr.moveRight();
1868
+ }
1869
+
1870
+ export fn editBufferMoveCursorUp(edit_handle: NativeHandle) void {
1871
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1872
+ object_ptr.moveUp();
1873
+ }
1874
+
1875
+ export fn editBufferMoveCursorDown(edit_handle: NativeHandle) void {
1876
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1877
+ object_ptr.moveDown();
1878
+ }
1879
+
1880
+ export fn editBufferGetCursor(edit_handle: NativeHandle, outRow: *u32, outCol: *u32) void {
1881
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
1882
+ outRow.* = 0;
1883
+ outCol.* = 0;
1884
+ return;
1885
+ };
1886
+ const cursor = object_ptr.getPrimaryCursor();
1887
+ outRow.* = cursor.row;
1888
+ outCol.* = cursor.col;
1889
+ }
1890
+
1891
+ export fn editBufferSetCursor(edit_handle: NativeHandle, row: u32, col: u32) void {
1892
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1893
+ object_ptr.setCursor(row, col) catch {};
1894
+ }
1895
+
1896
+ export fn editBufferSetCursorToLineCol(edit_handle: NativeHandle, row: u32, col: u32) void {
1897
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1898
+ object_ptr.setCursor(row, col) catch {};
1899
+ }
1900
+
1901
+ export fn editBufferSetCursorByOffset(edit_handle: NativeHandle, offset: u32) void {
1902
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1903
+ object_ptr.setCursorByOffset(offset) catch {};
1904
+ }
1905
+
1906
+ export fn editBufferGetNextWordBoundary(edit_handle: NativeHandle, outPtr: *ExternalLogicalCursor) void {
1907
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
1908
+ outPtr.* = std.mem.zeroes(ExternalLogicalCursor);
1909
+ return;
1910
+ };
1911
+ const cursor = object_ptr.getNextWordBoundary();
1912
+ outPtr.* = .{
1913
+ .row = cursor.row,
1914
+ .col = cursor.col,
1915
+ .offset = cursor.offset,
1916
+ };
1917
+ }
1918
+
1919
+ export fn editBufferGetPrevWordBoundary(edit_handle: NativeHandle, outPtr: *ExternalLogicalCursor) void {
1920
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
1921
+ outPtr.* = std.mem.zeroes(ExternalLogicalCursor);
1922
+ return;
1923
+ };
1924
+ const cursor = object_ptr.getPrevWordBoundary();
1925
+ outPtr.* = .{
1926
+ .row = cursor.row,
1927
+ .col = cursor.col,
1928
+ .offset = cursor.offset,
1929
+ };
1930
+ }
1931
+
1932
+ export fn editBufferGetEOL(edit_handle: NativeHandle, outPtr: *ExternalLogicalCursor) void {
1933
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
1934
+ outPtr.* = std.mem.zeroes(ExternalLogicalCursor);
1935
+ return;
1936
+ };
1937
+ const cursor = object_ptr.getEOL();
1938
+ outPtr.* = .{
1939
+ .row = cursor.row,
1940
+ .col = cursor.col,
1941
+ .offset = cursor.offset,
1942
+ };
1943
+ }
1944
+
1945
+ export fn editBufferOffsetToPosition(edit_handle: NativeHandle, offset: u32, outPtr: *ExternalLogicalCursor) bool {
1946
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
1947
+ outPtr.* = std.mem.zeroes(ExternalLogicalCursor);
1948
+ return false;
1949
+ };
1950
+ const coords = text_buffer_iterators.offsetToCoords(object_ptr.tb.rope(), offset) orelse return false;
1951
+ outPtr.* = .{
1952
+ .row = coords.row,
1953
+ .col = coords.col,
1954
+ .offset = offset,
1955
+ };
1956
+ return true;
1957
+ }
1958
+
1959
+ export fn editBufferPositionToOffset(edit_handle: NativeHandle, row: u32, col: u32) u32 {
1960
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
1961
+ return text_buffer_iterators.coordsToOffset(object_ptr.tb.rope(), row, col) orelse 0;
1962
+ }
1963
+
1964
+ export fn editBufferGetLineStartOffset(edit_handle: NativeHandle, row: u32) u32 {
1965
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
1966
+ return text_buffer_iterators.coordsToOffset(object_ptr.tb.rope(), row, 0) orelse 0;
1967
+ }
1968
+
1969
+ export fn editBufferGetTextRange(edit_handle: NativeHandle, start_offset: u32, end_offset: u32, outPtr: ?[*]u8, maxLen: u32) u32 {
1970
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
1971
+ if (maxLen == 0) return 0;
1972
+
1973
+ const out = outPtr orelse return 0;
1974
+ const outBuffer = out[0..@as(usize, maxLen)];
1975
+ return @intCast(object_ptr.getTextRange(start_offset, end_offset, outBuffer) catch 0);
1976
+ }
1977
+
1978
+ export fn editBufferGetTextRangeByCoords(edit_handle: NativeHandle, start_row: u32, start_col: u32, end_row: u32, end_col: u32, outPtr: ?[*]u8, maxLen: u32) u32 {
1979
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
1980
+ if (maxLen == 0) return 0;
1981
+
1982
+ const out = outPtr orelse return 0;
1983
+ const outBuffer = out[0..@as(usize, maxLen)];
1984
+ return @intCast(object_ptr.getTextRangeByCoords(start_row, start_col, end_row, end_col, outBuffer));
1985
+ }
1986
+
1987
+ export fn editBufferSetText(edit_handle: NativeHandle, textPtr: ?[*]const u8, textLen: u32) void {
1988
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1989
+ const text = sliceFromPtrLen(textPtr, textLen);
1990
+ object_ptr.setText(text) catch {};
1991
+ }
1992
+
1993
+ export fn editBufferSetTextFromMem(edit_handle: NativeHandle, mem_id: u8) void {
1994
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
1995
+ object_ptr.setTextFromMemId(mem_id) catch {};
1996
+ }
1997
+
1998
+ export fn editBufferReplaceText(edit_handle: NativeHandle, textPtr: ?[*]const u8, textLen: u32) void {
1999
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2000
+ const text = sliceFromPtrLen(textPtr, textLen);
2001
+ object_ptr.replaceText(text) catch {};
2002
+ }
2003
+
2004
+ export fn editBufferReplaceTextFromMem(edit_handle: NativeHandle, mem_id: u8) void {
2005
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2006
+ object_ptr.replaceTextFromMemId(mem_id) catch {};
2007
+ }
2008
+
2009
+ export fn editBufferGetText(edit_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
2010
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
2011
+ if (maxLen == 0) return 0;
2012
+
2013
+ const out = outPtr orelse return 0;
2014
+ const outBuffer = out[0..@as(usize, maxLen)];
2015
+ return @intCast(object_ptr.getText(outBuffer));
2016
+ }
2017
+
2018
+ export fn editBufferInsertChar(edit_handle: NativeHandle, charPtr: ?[*]const u8, charLen: u32) void {
2019
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2020
+ const text = sliceFromPtrLen(charPtr, charLen);
2021
+ object_ptr.insertText(text) catch {};
2022
+ }
2023
+
2024
+ export fn editBufferNewLine(edit_handle: NativeHandle) void {
2025
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2026
+ object_ptr.insertText("\n") catch {};
2027
+ }
2028
+
2029
+ export fn editBufferDeleteLine(edit_handle: NativeHandle) void {
2030
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2031
+ object_ptr.deleteLine() catch {};
2032
+ }
2033
+
2034
+ export fn editBufferGotoLine(edit_handle: NativeHandle, line: u32) void {
2035
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2036
+ object_ptr.gotoLine(line) catch {};
2037
+ }
2038
+
2039
+ export fn editBufferGetCursorPosition(edit_handle: NativeHandle, outPtr: *ExternalLogicalCursor) void {
2040
+ const object_ptr = acquireEditBuffer(edit_handle) orelse {
2041
+ outPtr.* = std.mem.zeroes(ExternalLogicalCursor);
2042
+ return;
2043
+ };
2044
+ const pos = object_ptr.getCursorPosition();
2045
+ outPtr.* = .{
2046
+ .row = pos.line,
2047
+ .col = pos.visual_col,
2048
+ .offset = pos.offset,
2049
+ };
2050
+ }
2051
+
2052
+ export fn editBufferGetId(edit_handle: NativeHandle) u16 {
2053
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
2054
+ return object_ptr.getId();
2055
+ }
2056
+
2057
+ export fn editBufferDebugLogRope(edit_handle: NativeHandle) void {
2058
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2059
+ object_ptr.debugLogRope();
2060
+ }
2061
+
2062
+ export fn editBufferUndo(edit_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
2063
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
2064
+ if (maxLen == 0) return 0;
2065
+
2066
+ const out = outPtr orelse return 0;
2067
+ const prev_meta = object_ptr.undo() catch return 0;
2068
+ const copyLen = @min(prev_meta.len, @as(usize, maxLen));
2069
+ @memcpy(out[0..copyLen], prev_meta[0..copyLen]);
2070
+ return @intCast(copyLen);
2071
+ }
2072
+
2073
+ export fn editBufferRedo(edit_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
2074
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return 0;
2075
+ if (maxLen == 0) return 0;
2076
+
2077
+ const out = outPtr orelse return 0;
2078
+ const next_meta = object_ptr.redo() catch return 0;
2079
+ const copyLen = @min(next_meta.len, @as(usize, maxLen));
2080
+ @memcpy(out[0..copyLen], next_meta[0..copyLen]);
2081
+ return @intCast(copyLen);
2082
+ }
2083
+
2084
+ export fn editBufferCanUndo(edit_handle: NativeHandle) bool {
2085
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return false;
2086
+ return object_ptr.canUndo();
2087
+ }
2088
+
2089
+ export fn editBufferCanRedo(edit_handle: NativeHandle) bool {
2090
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return false;
2091
+ return object_ptr.canRedo();
2092
+ }
2093
+
2094
+ export fn editBufferClearHistory(edit_handle: NativeHandle) void {
2095
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2096
+ object_ptr.clearHistory();
2097
+ }
2098
+
2099
+ export fn editBufferClear(edit_handle: NativeHandle) void {
2100
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return;
2101
+ object_ptr.clear() catch {};
2102
+ }
2103
+
2104
+ // ===== EditorView Exports =====
2105
+
2106
+ export fn createEditorView(edit_handle: NativeHandle, viewport_width: u32, viewport_height: u32) NativeHandle {
2107
+ const object_ptr = acquireEditBuffer(edit_handle) orelse return INVALID_HANDLE;
2108
+ const view = editor_view.EditorView.init(globalArena, object_ptr, viewport_width, viewport_height) catch return INVALID_HANDLE;
2109
+ const view_handle = handles.insertOwnedChild(.editor_view, erasePtr(view), edit_handle) catch {
2110
+ view.deinit();
2111
+ return INVALID_HANDLE;
2112
+ };
2113
+ _ = handles.getOrInsertBorrowed(.text_buffer_view, erasePtr(view.getTextBufferView()), view_handle) catch {
2114
+ if (handles.beginDestroy(view_handle, .editor_view, editor_view.EditorView)) |token| {
2115
+ token.ptr.deinit();
2116
+ handles.finishDestroy(token.handle);
2117
+ }
2118
+ return INVALID_HANDLE;
2119
+ };
2120
+ return view_handle;
2121
+ }
2122
+
2123
+ export fn destroyEditorView(view_handle: NativeHandle) void {
2124
+ destroyEditorViewHandle(view_handle);
2125
+ }
2126
+
2127
+ export fn editorViewSetViewport(view_handle: NativeHandle, x: u32, y: u32, width: u32, height: u32, moveCursor: bool) void {
2128
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2129
+ object_ptr.setViewport(.{ .x = x, .y = y, .width = width, .height = height }, moveCursor);
2130
+ }
2131
+
2132
+ export fn editorViewClearViewport(view_handle: NativeHandle) void {
2133
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2134
+ object_ptr.setViewport(null, false);
2135
+ }
2136
+
2137
+ export fn editorViewGetViewport(view_handle: NativeHandle, outX: *u32, outY: *u32, outWidth: *u32, outHeight: *u32) bool {
2138
+ const object_ptr = acquireEditorView(view_handle) orelse {
2139
+ outX.* = 0;
2140
+ outY.* = 0;
2141
+ outWidth.* = 0;
2142
+ outHeight.* = 0;
2143
+ return false;
2144
+ };
2145
+ if (object_ptr.getViewport()) |vp| {
2146
+ outX.* = vp.x;
2147
+ outY.* = vp.y;
2148
+ outWidth.* = vp.width;
2149
+ outHeight.* = vp.height;
2150
+ return true;
2151
+ }
2152
+ return false;
2153
+ }
2154
+
2155
+ export fn editorViewSetScrollMargin(view_handle: NativeHandle, margin: f32) void {
2156
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2157
+ object_ptr.setScrollMargin(margin);
2158
+ }
2159
+
2160
+ export fn editorViewGetVirtualLineCount(view_handle: NativeHandle) u32 {
2161
+ const object_ptr = acquireEditorView(view_handle) orelse return 0;
2162
+ // TODO: There is a getter for that directly, no?
2163
+ return @intCast(object_ptr.getVirtualLines().len);
2164
+ }
2165
+
2166
+ export fn editorViewGetTotalVirtualLineCount(view_handle: NativeHandle) u32 {
2167
+ const object_ptr = acquireEditorView(view_handle) orelse return 0;
2168
+ return object_ptr.getTotalVirtualLineCount();
2169
+ }
2170
+
2171
+ export fn editorViewGetLineInfoDirect(view_handle: NativeHandle, outPtr: *ExternalLineInfo) void {
2172
+ const object_ptr = acquireEditorView(view_handle) orelse {
2173
+ emptyLineInfo(outPtr);
2174
+ return;
2175
+ };
2176
+ const line_info = object_ptr.getCachedLineInfo();
2177
+ outPtr.* = .{
2178
+ .start_cols_ptr = line_info.line_start_cols.ptr,
2179
+ .start_cols_len = @intCast(line_info.line_start_cols.len),
2180
+ .width_cols_ptr = line_info.line_width_cols.ptr,
2181
+ .width_cols_len = @intCast(line_info.line_width_cols.len),
2182
+ .sources_ptr = line_info.line_sources.ptr,
2183
+ .sources_len = @intCast(line_info.line_sources.len),
2184
+ .wraps_ptr = line_info.line_wraps.ptr,
2185
+ .wraps_len = @intCast(line_info.line_wraps.len),
2186
+ .width_cols_max = line_info.line_width_cols_max,
2187
+ };
2188
+ }
2189
+
2190
+ export fn editorViewGetTextBufferView(view_handle: NativeHandle) NativeHandle {
2191
+ const object_ptr = acquireEditorView(view_handle) orelse return INVALID_HANDLE;
2192
+ return handles.getOrInsertBorrowed(.text_buffer_view, erasePtr(object_ptr.getTextBufferView()), view_handle) catch INVALID_HANDLE;
2193
+ }
2194
+
2195
+ export fn editorViewGetLogicalLineInfoDirect(view_handle: NativeHandle, outPtr: *ExternalLineInfo) void {
2196
+ const object_ptr = acquireEditorView(view_handle) orelse {
2197
+ emptyLineInfo(outPtr);
2198
+ return;
2199
+ };
2200
+ const line_info = object_ptr.getLogicalLineInfo();
2201
+ outPtr.* = .{
2202
+ .start_cols_ptr = line_info.line_start_cols.ptr,
2203
+ .start_cols_len = @intCast(line_info.line_start_cols.len),
2204
+ .width_cols_ptr = line_info.line_width_cols.ptr,
2205
+ .width_cols_len = @intCast(line_info.line_width_cols.len),
2206
+ .sources_ptr = line_info.line_sources.ptr,
2207
+ .sources_len = @intCast(line_info.line_sources.len),
2208
+ .wraps_ptr = line_info.line_wraps.ptr,
2209
+ .wraps_len = @intCast(line_info.line_wraps.len),
2210
+ .width_cols_max = line_info.line_width_cols_max,
2211
+ };
2212
+ }
2213
+
2214
+ export fn editorViewSetViewportSize(view_handle: NativeHandle, width: u32, height: u32) void {
2215
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2216
+ object_ptr.setViewportSize(width, height);
2217
+ }
2218
+
2219
+ export fn editorViewSetWrapMode(view_handle: NativeHandle, mode: u8) void {
2220
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2221
+ const wrapMode: text_buffer.WrapMode = switch (mode) {
2222
+ 0 => .none,
2223
+ 1 => .char,
2224
+ 2 => .word,
2225
+ else => .none,
2226
+ };
2227
+ object_ptr.setWrapMode(wrapMode);
2228
+ }
2229
+
2230
+ // EditorView selection methods - delegate to TextBufferView
2231
+ export fn editorViewSetSelection(view_handle: NativeHandle, start: u32, end: u32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) void {
2232
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2233
+ object_ptr.text_buffer_view.setSelectionStyle(start, end, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
2234
+ }
2235
+
2236
+ export fn editorViewResetSelection(view_handle: NativeHandle) void {
2237
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2238
+ object_ptr.text_buffer_view.resetSelection();
2239
+ }
2240
+
2241
+ export fn editorViewGetSelection(view_handle: NativeHandle) u64 {
2242
+ const object_ptr = acquireEditorView(view_handle) orelse return std.math.maxInt(u64);
2243
+ return object_ptr.text_buffer_view.packSelectionInfo();
2244
+ }
2245
+
2246
+ export fn editorViewSetLocalSelection(view_handle: NativeHandle, anchorX: i32, anchorY: i32, focusX: i32, focusY: i32, bgColor: ?[*]const u16, fgColor: ?[*]const u16, updateCursor: bool, followCursor: bool) bool {
2247
+ const object_ptr = acquireEditorView(view_handle) orelse return false;
2248
+ object_ptr.setSelectionFollowCursor(followCursor);
2249
+ const changed = object_ptr.text_buffer_view.setLocalSelectionStyle(anchorX, anchorY, focusX, focusY, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
2250
+ if (changed and updateCursor) {
2251
+ object_ptr.syncCursorToSelectionFocus();
2252
+ }
2253
+ return changed;
2254
+ }
2255
+
2256
+ export fn editorViewUpdateSelection(view_handle: NativeHandle, end: u32, bgColor: ?[*]const u16, fgColor: ?[*]const u16) void {
2257
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2258
+ object_ptr.text_buffer_view.updateSelectionStyle(end, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
2259
+ }
2260
+
2261
+ export fn editorViewUpdateLocalSelection(view_handle: NativeHandle, anchorX: i32, anchorY: i32, focusX: i32, focusY: i32, bgColor: ?[*]const u16, fgColor: ?[*]const u16, updateCursor: bool, followCursor: bool) bool {
2262
+ const object_ptr = acquireEditorView(view_handle) orelse return false;
2263
+ object_ptr.setSelectionFollowCursor(followCursor);
2264
+ const changed = object_ptr.text_buffer_view.updateLocalSelectionStyle(anchorX, anchorY, focusX, focusY, selectionStyle(optionalPtrToRGBA(bgColor), optionalPtrToRGBA(fgColor)));
2265
+ if (changed and updateCursor) {
2266
+ object_ptr.syncCursorToSelectionFocus();
2267
+ }
2268
+ return changed;
2269
+ }
2270
+
2271
+ export fn editorViewResetLocalSelection(view_handle: NativeHandle) void {
2272
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2273
+ object_ptr.setSelectionFollowCursor(false);
2274
+ object_ptr.text_buffer_view.resetLocalSelection();
2275
+ }
2276
+
2277
+ export fn editorViewGetSelectedTextBytes(view_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
2278
+ const object_ptr = acquireEditorView(view_handle) orelse return 0;
2279
+ if (maxLen == 0) return 0;
2280
+
2281
+ const out = outPtr orelse return 0;
2282
+ const outBuffer = out[0..@as(usize, maxLen)];
2283
+ return @intCast(object_ptr.text_buffer_view.getSelectedTextIntoBuffer(outBuffer));
2284
+ }
2285
+
2286
+ // EditorView cursor and text methods
2287
+ export fn editorViewGetCursor(view_handle: NativeHandle, outRow: *u32, outCol: *u32) void {
2288
+ const object_ptr = acquireEditorView(view_handle) orelse {
2289
+ outRow.* = 0;
2290
+ outCol.* = 0;
2291
+ return;
2292
+ };
2293
+ const cursor = object_ptr.getPrimaryCursor();
2294
+ outRow.* = cursor.row;
2295
+ outCol.* = cursor.col;
2296
+ }
2297
+
2298
+ export fn editorViewGetText(view_handle: NativeHandle, outPtr: ?[*]u8, maxLen: u32) u32 {
2299
+ const object_ptr = acquireEditorView(view_handle) orelse return 0;
2300
+ if (maxLen == 0) return 0;
2301
+
2302
+ const out = outPtr orelse return 0;
2303
+ const outBuffer = out[0..@as(usize, maxLen)];
2304
+ return @intCast(object_ptr.getText(outBuffer));
2305
+ }
2306
+
2307
+ // ===== EditorView VisualCursor Exports =====
2308
+
2309
+ export fn editorViewGetVisualCursor(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2310
+ const object_ptr = acquireEditorView(view_handle) orelse {
2311
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2312
+ return;
2313
+ };
2314
+ const vcursor = object_ptr.getVisualCursor();
2315
+ outPtr.* = .{
2316
+ .visual_row = vcursor.visual_row,
2317
+ .visual_col = vcursor.visual_col,
2318
+ .logical_row = vcursor.logical_row,
2319
+ .logical_col = vcursor.logical_col,
2320
+ .offset = vcursor.offset,
2321
+ };
2322
+ }
2323
+
2324
+ export fn editorViewMoveUpVisual(view_handle: NativeHandle) void {
2325
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2326
+ object_ptr.moveUpVisual();
2327
+ }
2328
+
2329
+ export fn editorViewMoveDownVisual(view_handle: NativeHandle) void {
2330
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2331
+ object_ptr.moveDownVisual();
2332
+ }
2333
+
2334
+ export fn editorViewDeleteSelectedText(view_handle: NativeHandle) void {
2335
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2336
+ object_ptr.deleteSelectedText() catch {};
2337
+ }
2338
+
2339
+ export fn editorViewSetCursorByOffset(view_handle: NativeHandle, offset: u32) void {
2340
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2341
+ object_ptr.setCursorByOffset(offset) catch {};
2342
+ }
2343
+
2344
+ export fn editorViewGetNextWordBoundary(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2345
+ const object_ptr = acquireEditorView(view_handle) orelse {
2346
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2347
+ return;
2348
+ };
2349
+ const vcursor = object_ptr.getNextWordBoundary();
2350
+ outPtr.* = .{
2351
+ .visual_row = vcursor.visual_row,
2352
+ .visual_col = vcursor.visual_col,
2353
+ .logical_row = vcursor.logical_row,
2354
+ .logical_col = vcursor.logical_col,
2355
+ .offset = vcursor.offset,
2356
+ };
2357
+ }
2358
+
2359
+ export fn editorViewGetPrevWordBoundary(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2360
+ const object_ptr = acquireEditorView(view_handle) orelse {
2361
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2362
+ return;
2363
+ };
2364
+ const vcursor = object_ptr.getPrevWordBoundary();
2365
+ outPtr.* = .{
2366
+ .visual_row = vcursor.visual_row,
2367
+ .visual_col = vcursor.visual_col,
2368
+ .logical_row = vcursor.logical_row,
2369
+ .logical_col = vcursor.logical_col,
2370
+ .offset = vcursor.offset,
2371
+ };
2372
+ }
2373
+
2374
+ export fn editorViewGetEOL(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2375
+ const object_ptr = acquireEditorView(view_handle) orelse {
2376
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2377
+ return;
2378
+ };
2379
+ const vcursor = object_ptr.getEOL();
2380
+ outPtr.* = .{
2381
+ .visual_row = vcursor.visual_row,
2382
+ .visual_col = vcursor.visual_col,
2383
+ .logical_row = vcursor.logical_row,
2384
+ .logical_col = vcursor.logical_col,
2385
+ .offset = vcursor.offset,
2386
+ };
2387
+ }
2388
+
2389
+ export fn editorViewGetVisualSOL(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2390
+ const object_ptr = acquireEditorView(view_handle) orelse {
2391
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2392
+ return;
2393
+ };
2394
+ const vcursor = object_ptr.getVisualSOL();
2395
+ outPtr.* = .{
2396
+ .visual_row = vcursor.visual_row,
2397
+ .visual_col = vcursor.visual_col,
2398
+ .logical_row = vcursor.logical_row,
2399
+ .logical_col = vcursor.logical_col,
2400
+ .offset = vcursor.offset,
2401
+ };
2402
+ }
2403
+
2404
+ export fn editorViewGetVisualEOL(view_handle: NativeHandle, outPtr: *ExternalVisualCursor) void {
2405
+ const object_ptr = acquireEditorView(view_handle) orelse {
2406
+ outPtr.* = std.mem.zeroes(ExternalVisualCursor);
2407
+ return;
2408
+ };
2409
+ const vcursor = object_ptr.getVisualEOL();
2410
+ outPtr.* = .{
2411
+ .visual_row = vcursor.visual_row,
2412
+ .visual_col = vcursor.visual_col,
2413
+ .logical_row = vcursor.logical_row,
2414
+ .logical_col = vcursor.logical_col,
2415
+ .offset = vcursor.offset,
2416
+ };
2417
+ }
2418
+
2419
+ export fn editorViewSetPlaceholderStyledText(
2420
+ view_handle: NativeHandle,
2421
+ chunksPtr: ?[*]const text_buffer.StyledChunk,
2422
+ chunkCount: u32,
2423
+ ) void {
2424
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2425
+ if (chunkCount == 0) {
2426
+ object_ptr.setPlaceholderStyledText(&[_]text_buffer.StyledChunk{}) catch {};
2427
+ return;
2428
+ }
2429
+ const chunks = chunksPtr.?[0..@as(usize, chunkCount)];
2430
+ object_ptr.setPlaceholderStyledText(chunks) catch {};
2431
+ }
2432
+
2433
+ export fn editorViewSetTabIndicator(view_handle: NativeHandle, indicator: u32) void {
2434
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2435
+ object_ptr.setTabIndicator(indicator);
2436
+ }
2437
+
2438
+ export fn editorViewSetTabIndicatorColor(view_handle: NativeHandle, color: [*]const u16) void {
2439
+ const object_ptr = acquireEditorView(view_handle) orelse return;
2440
+ object_ptr.setTabIndicatorColor(ptrToRGBA(color));
2441
+ }
2442
+
2443
+ export fn bufferDrawEditorView(
2444
+ buffer_handle: NativeHandle,
2445
+ view_handle: NativeHandle,
2446
+ x: i32,
2447
+ y: i32,
2448
+ ) void {
2449
+ const buffer_ptr = acquireBuffer(buffer_handle) orelse return;
2450
+ const view_ptr = acquireEditorView(view_handle) orelse return;
2451
+ buffer_ptr.drawEditorView(view_ptr, x, y);
2452
+ }
2453
+
2454
+ export fn bufferDrawTextBufferView(
2455
+ buffer_handle: NativeHandle,
2456
+ view_handle: NativeHandle,
2457
+ x: i32,
2458
+ y: i32,
2459
+ ) void {
2460
+ const buffer_ptr = acquireBuffer(buffer_handle) orelse return;
2461
+ const view_ptr = acquireTextBufferView(view_handle) orelse return;
2462
+ buffer_ptr.drawTextBuffer(view_ptr, x, y);
2463
+ }
2464
+
2465
+ pub const ExternalHighlight = extern struct {
2466
+ start: u32,
2467
+ end: u32,
2468
+ style_id: u32,
2469
+ priority: u8,
2470
+ hl_ref: u16,
2471
+ };
2472
+
2473
+ pub const ExternalLogicalCursor = extern struct {
2474
+ row: u32,
2475
+ col: u32,
2476
+ offset: u32,
2477
+ };
2478
+
2479
+ pub const ExternalVisualCursor = extern struct {
2480
+ visual_row: u32,
2481
+ visual_col: u32,
2482
+ logical_row: u32,
2483
+ logical_col: u32,
2484
+ offset: u32,
2485
+ };
2486
+
2487
+ pub const ExternalLineInfo = extern struct {
2488
+ start_cols_ptr: [*]const u32,
2489
+ start_cols_len: u32,
2490
+ width_cols_ptr: [*]const u32,
2491
+ width_cols_len: u32,
2492
+ sources_ptr: [*]const u32,
2493
+ sources_len: u32,
2494
+ wraps_ptr: [*]const u32,
2495
+ wraps_len: u32,
2496
+ width_cols_max: u32,
2497
+ };
2498
+
2499
+ export fn textBufferAddHighlightByCharRange(
2500
+ tb_handle: NativeHandle,
2501
+ hl_ptr: [*]const ExternalHighlight,
2502
+ ) void {
2503
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
2504
+ const hl = hl_ptr[0];
2505
+ // For char-range highlights, start/end in the struct are unused (passed as char_start/char_end)
2506
+ object_ptr.addHighlightByCharRange(hl.start, hl.end, hl.style_id, hl.priority, hl.hl_ref) catch {};
2507
+ }
2508
+
2509
+ export fn textBufferAddHighlight(
2510
+ tb_handle: NativeHandle,
2511
+ line_idx: u32,
2512
+ hl_ptr: [*]const ExternalHighlight,
2513
+ ) void {
2514
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
2515
+ const hl = hl_ptr[0];
2516
+ // For line-based highlights, start/end are column offsets
2517
+ object_ptr.addHighlight(line_idx, hl.start, hl.end, hl.style_id, hl.priority, hl.hl_ref) catch {};
2518
+ }
2519
+
2520
+ export fn textBufferRemoveHighlightsByRef(tb_handle: NativeHandle, hl_ref: u16) void {
2521
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
2522
+ object_ptr.removeHighlightsByRef(hl_ref);
2523
+ }
2524
+
2525
+ export fn textBufferClearLineHighlights(tb_handle: NativeHandle, line_idx: u32) void {
2526
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
2527
+ object_ptr.clearLineHighlights(line_idx);
2528
+ }
2529
+
2530
+ export fn textBufferClearAllHighlights(tb_handle: NativeHandle) void {
2531
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return;
2532
+ object_ptr.clearAllHighlights();
2533
+ }
2534
+
2535
+ export fn textBufferSetSyntaxStyle(tb_handle: NativeHandle, style_handle: NativeHandle) bool {
2536
+ const tb_ptr = acquireTextBuffer(tb_handle) orelse return false;
2537
+ if (style_handle == INVALID_HANDLE) {
2538
+ tb_ptr.setSyntaxStyle(null);
2539
+ return true;
2540
+ }
2541
+ const style_ptr = acquireSyntaxStyle(style_handle) orelse return false;
2542
+ tb_ptr.setSyntaxStyle(style_ptr);
2543
+ return tb_ptr.getSyntaxStyle() == style_ptr;
2544
+ }
2545
+
2546
+ export fn textBufferGetLineHighlightsPtr(
2547
+ tb_handle: NativeHandle,
2548
+ line_idx: u32,
2549
+ out_count: *u32,
2550
+ ) ?[*]const ExternalHighlight {
2551
+ const object_ptr = acquireTextBuffer(tb_handle) orelse {
2552
+ out_count.* = 0;
2553
+ return null;
2554
+ };
2555
+ const highs = object_ptr.getLineHighlightsSlice(@intCast(line_idx));
2556
+
2557
+ if (highs.len == 0) {
2558
+ out_count.* = 0;
2559
+ return null;
2560
+ }
2561
+
2562
+ var slice = globalAllocator.alloc(ExternalHighlight, highs.len) catch return null;
2563
+
2564
+ for (highs, 0..) |hl, i| {
2565
+ slice[i] = .{
2566
+ .start = hl.col_start,
2567
+ .end = hl.col_end,
2568
+ .style_id = hl.style_id,
2569
+ .priority = hl.priority,
2570
+ .hl_ref = hl.hl_ref,
2571
+ };
2572
+ }
2573
+
2574
+ out_count.* = @intCast(highs.len);
2575
+ return slice.ptr;
2576
+ }
2577
+
2578
+ export fn textBufferFreeLineHighlights(ptr: [*]const ExternalHighlight, count: u32) void {
2579
+ globalAllocator.free(@constCast(ptr)[0..@as(usize, count)]);
2580
+ }
2581
+
2582
+ export fn textBufferGetHighlightCount(tb_handle: NativeHandle) u32 {
2583
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
2584
+ return object_ptr.getHighlightCount();
2585
+ }
2586
+
2587
+ export fn textBufferGetTextRange(tb_handle: NativeHandle, start_offset: u32, end_offset: u32, outPtr: ?[*]u8, maxLen: u32) u32 {
2588
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
2589
+ if (maxLen == 0) return 0;
2590
+
2591
+ const out = outPtr orelse return 0;
2592
+ const outBuffer = out[0..@as(usize, maxLen)];
2593
+ return @intCast(object_ptr.getTextRange(start_offset, end_offset, outBuffer));
2594
+ }
2595
+
2596
+ export fn textBufferGetTextRangeByCoords(tb_handle: NativeHandle, start_row: u32, start_col: u32, end_row: u32, end_col: u32, outPtr: ?[*]u8, maxLen: u32) u32 {
2597
+ const object_ptr = acquireTextBuffer(tb_handle) orelse return 0;
2598
+ if (maxLen == 0) return 0;
2599
+
2600
+ const out = outPtr orelse return 0;
2601
+ const outBuffer = out[0..@as(usize, maxLen)];
2602
+ return @intCast(object_ptr.getTextRangeByCoords(start_row, start_col, end_row, end_col, outBuffer));
2603
+ }
2604
+
2605
+ // SyntaxStyle functions
2606
+ export fn createSyntaxStyle() NativeHandle {
2607
+ const style = syntax_style.SyntaxStyle.init(globalAllocator) catch |err| {
2608
+ logger.err("Failed to create SyntaxStyle: {}", .{err});
2609
+ return INVALID_HANDLE;
2610
+ };
2611
+ return handles.insert(.syntax_style, erasePtr(style)) catch {
2612
+ style.deinit();
2613
+ return INVALID_HANDLE;
2614
+ };
2615
+ }
2616
+
2617
+ export fn destroySyntaxStyle(style_handle: NativeHandle) void {
2618
+ const token = handles.beginDestroy(style_handle, .syntax_style, syntax_style.SyntaxStyle) orelse return;
2619
+ token.ptr.deinit();
2620
+ handles.finishDestroy(token.handle);
2621
+ }
2622
+
2623
+ export fn syntaxStyleRegister(style_handle: NativeHandle, namePtr: ?[*]const u8, nameLen: u32, fg: ?[*]const u16, bg: ?[*]const u16, attributes: u32) u32 {
2624
+ const object_ptr = acquireSyntaxStyle(style_handle) orelse return 0;
2625
+ const name = sliceFromPtrLen(namePtr, nameLen);
2626
+ return object_ptr.registerStyleDefinition(name, .{
2627
+ .fg = optionalPtrToRGBA(fg),
2628
+ .bg = optionalPtrToRGBA(bg),
2629
+ .attributes = attributes,
2630
+ }) catch 0;
2631
+ }
2632
+
2633
+ export fn syntaxStyleResolveByName(style_handle: NativeHandle, namePtr: ?[*]const u8, nameLen: u32) u32 {
2634
+ const object_ptr = acquireSyntaxStyle(style_handle) orelse return 0;
2635
+ const name = sliceFromPtrLen(namePtr, nameLen);
2636
+ return object_ptr.resolveByName(name) orelse 0;
2637
+ }
2638
+
2639
+ export fn syntaxStyleGetStyleCount(style_handle: NativeHandle) u32 {
2640
+ const object_ptr = acquireSyntaxStyle(style_handle) orelse return 0;
2641
+ return @intCast(object_ptr.getStyleCount());
2642
+ }
2643
+
2644
+ // Unicode encoding API
2645
+
2646
+ pub const EncodedChar = extern struct {
2647
+ width: u8,
2648
+ char: u32,
2649
+ };
2650
+
2651
+ export fn encodeUnicode(
2652
+ textPtr: ?[*]const u8,
2653
+ textLen: u32,
2654
+ outPtr: *?[*]EncodedChar,
2655
+ outLenPtr: *usize,
2656
+ widthMethod: u8,
2657
+ ) bool {
2658
+ const text = sliceFromPtrLen(textPtr, textLen);
2659
+
2660
+ if (text.len == 0) {
2661
+ outPtr.* = @ptrFromInt(0);
2662
+ outLenPtr.* = 0;
2663
+ return true;
2664
+ }
2665
+
2666
+ const pool = gp.initGlobalPool(globalArena);
2667
+ const wMethod: utf8.WidthMethod = if (widthMethod == 0) .wcwidth else .unicode;
2668
+
2669
+ // Check if ASCII only for optimization
2670
+ const is_ascii_only = utf8.isAsciiOnly(text);
2671
+
2672
+ // Find grapheme info
2673
+ var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{};
2674
+ defer grapheme_list.deinit(globalAllocator);
2675
+
2676
+ const tab_width: u8 = 2;
2677
+ utf8.findGraphemeInfo(globalAllocator, text, tab_width, is_ascii_only, wMethod, &grapheme_list) catch return false;
2678
+ const specials = grapheme_list.items;
2679
+
2680
+ // Allocate output array
2681
+ const estimated_count = if (is_ascii_only) text.len else text.len * 2;
2682
+ var result = globalAllocator.alloc(EncodedChar, estimated_count) catch return false;
2683
+ var result_idx: usize = 0;
2684
+ var success = false;
2685
+ var pending_gid: ?u32 = null; // Track grapheme allocated but not yet stored in result
2686
+
2687
+ // Clean up result array and any allocated grapheme IDs on failure
2688
+ defer {
2689
+ if (!success) {
2690
+ // Clean up pending grapheme that wasn't stored yet
2691
+ if (pending_gid) |gid| {
2692
+ // Try decref first (works if incref was called, refcount >= 1)
2693
+ // If that fails (refcount was 0), use freeUnreferenced
2694
+ pool.decref(gid) catch {
2695
+ pool.freeUnreferenced(gid) catch {};
2696
+ };
2697
+ }
2698
+ // Decref any grapheme IDs we allocated before the failure
2699
+ for (result[0..result_idx]) |encoded_char| {
2700
+ if (gp.isGraphemeChar(encoded_char.char)) {
2701
+ const gid = gp.graphemeIdFromChar(encoded_char.char);
2702
+ pool.decref(gid) catch {};
2703
+ }
2704
+ }
2705
+ globalAllocator.free(result);
2706
+ }
2707
+ }
2708
+
2709
+ var byte_offset: u32 = 0;
2710
+ var col: u32 = 0;
2711
+ var special_idx: usize = 0;
2712
+
2713
+ while (byte_offset < text.len) {
2714
+ const at_special = special_idx < specials.len and specials[special_idx].col_offset == col;
2715
+
2716
+ var grapheme_bytes: []const u8 = undefined;
2717
+ var g_width: u8 = undefined;
2718
+
2719
+ if (at_special) {
2720
+ const g = specials[special_idx];
2721
+ grapheme_bytes = text[g.byte_offset .. g.byte_offset + g.byte_len];
2722
+ g_width = g.width;
2723
+ byte_offset = g.byte_offset + g.byte_len;
2724
+ special_idx += 1;
2725
+ } else {
2726
+ if (byte_offset >= text.len) break;
2727
+ grapheme_bytes = text[byte_offset .. byte_offset + 1];
2728
+ g_width = 1;
2729
+ byte_offset += 1;
2730
+ }
2731
+
2732
+ const cell_width = utf8.getWidthAt(text, if (at_special) specials[special_idx - 1].byte_offset else byte_offset - 1, tab_width, wMethod);
2733
+ if (cell_width == 0) {
2734
+ col += g_width;
2735
+ continue;
2736
+ }
2737
+
2738
+ // Encode the character
2739
+ var encoded_char: u32 = 0;
2740
+ if (grapheme_bytes.len == 1 and cell_width == 1 and grapheme_bytes[0] >= 32) {
2741
+ // Simple ASCII character
2742
+ encoded_char = @as(u32, grapheme_bytes[0]);
2743
+ } else {
2744
+ // Multi-byte or special character - allocate in pool
2745
+ const gid = pool.alloc(grapheme_bytes) catch return false;
2746
+ pending_gid = gid; // Track until stored in result
2747
+ encoded_char = gp.packGraphemeStart(gid & gp.GRAPHEME_ID_MASK, cell_width);
2748
+
2749
+ // Incref since we're handing this off to the caller
2750
+ // Note: incref can only fail if gid is invalid, which shouldn't happen
2751
+ // for a freshly allocated gid. If it does fail, the slot leaks but
2752
+ // this is an edge case that indicates a bug elsewhere.
2753
+ pool.incref(gid) catch return false;
2754
+ }
2755
+
2756
+ // Ensure we have space
2757
+ if (result_idx >= result.len) {
2758
+ const new_len = result.len * 2;
2759
+ result = globalAllocator.realloc(result, new_len) catch return false;
2760
+ }
2761
+
2762
+ result[result_idx] = EncodedChar{
2763
+ .width = @intCast(cell_width),
2764
+ .char = encoded_char,
2765
+ };
2766
+ pending_gid = null; // Successfully stored, no longer pending
2767
+ result_idx += 1;
2768
+ col += g_width;
2769
+ }
2770
+
2771
+ // Trim to actual size
2772
+ result = globalAllocator.realloc(result, result_idx) catch result;
2773
+
2774
+ outPtr.* = result.ptr;
2775
+ outLenPtr.* = result_idx;
2776
+ success = true;
2777
+ return true;
2778
+ }
2779
+
2780
+ export fn freeUnicode(charsPtr: ?[*]const EncodedChar, charsLen: u32) void {
2781
+ if (charsLen == 0 or charsPtr == null) {
2782
+ return;
2783
+ }
2784
+
2785
+ const chars = charsPtr.?[0..@as(usize, charsLen)];
2786
+ const pool = gp.initGlobalPool(globalArena);
2787
+
2788
+ for (chars) |encoded_char| {
2789
+ const char = encoded_char.char;
2790
+
2791
+ // Check if this is a packed grapheme
2792
+ if (gp.isGraphemeChar(char)) {
2793
+ const gid = gp.graphemeIdFromChar(char);
2794
+ pool.decref(gid) catch {};
2795
+ }
2796
+ }
2797
+
2798
+ // Free the array itself
2799
+ globalAllocator.free(chars);
2800
+ }
2801
+
2802
+ export fn bufferDrawChar(
2803
+ buffer_handle: NativeHandle,
2804
+ char: u32,
2805
+ x: u32,
2806
+ y: u32,
2807
+ fg: [*]const u16,
2808
+ bg: [*]const u16,
2809
+ attributes: u32,
2810
+ ) void {
2811
+ const object_ptr = acquireBuffer(buffer_handle) orelse return;
2812
+ object_ptr.drawChar(char, x, y, ptrToRGBA(fg), ptrToRGBA(bg), attributes);
2813
+ }