@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,330 @@
1
+ import { OptimizedBuffer } from "../buffer.js"
2
+ import { parseColor, RGBA, type ColorInput } from "./RGBA.js"
3
+ import block from "./fonts/block.json" with { type: "json" }
4
+ import shade from "./fonts/shade.json" with { type: "json" }
5
+ import slick from "./fonts/slick.json" with { type: "json" }
6
+ import tiny from "./fonts/tiny.json" with { type: "json" }
7
+ import huge from "./fonts/huge.json" with { type: "json" }
8
+ import grid from "./fonts/grid.json" with { type: "json" }
9
+ import pallet from "./fonts/pallet.json" with { type: "json" }
10
+
11
+ /*
12
+ * Renders ASCII fonts to a buffer.
13
+ * Font definitions plugged from cfonts - https://github.com/dominikwilkowski/cfonts
14
+ */
15
+
16
+ export type ASCIIFontName = "tiny" | "block" | "shade" | "slick" | "huge" | "grid" | "pallet"
17
+
18
+ export const fonts = {
19
+ tiny,
20
+ block,
21
+ shade,
22
+ slick,
23
+ huge,
24
+ grid,
25
+ pallet,
26
+ }
27
+
28
+ type FontSegment = {
29
+ text: string
30
+ colorIndex: number
31
+ }
32
+
33
+ type FontDefinition = {
34
+ name: string
35
+ lines: number
36
+ letterspace_size: number
37
+ letterspace: string[]
38
+ colors?: number
39
+ chars: Record<string, string[]>
40
+ }
41
+
42
+ type ParsedFontDefinition = {
43
+ name: string
44
+ lines: number
45
+ letterspace_size: number
46
+ letterspace: string[]
47
+ colors: number
48
+ chars: Record<string, FontSegment[][]>
49
+ }
50
+
51
+ const parsedFonts: Record<string, ParsedFontDefinition> = {}
52
+
53
+ function parseColorTags(text: string): FontSegment[] {
54
+ const segments: FontSegment[] = []
55
+ let currentIndex = 0
56
+
57
+ const colorTagRegex = /<c(\d+)>(.*?)<\/c\d+>/g
58
+ let lastIndex = 0
59
+ let match
60
+
61
+ while ((match = colorTagRegex.exec(text)) !== null) {
62
+ if (match.index > lastIndex) {
63
+ const plainText = text.slice(lastIndex, match.index)
64
+ if (plainText) {
65
+ segments.push({ text: plainText, colorIndex: 0 })
66
+ }
67
+ }
68
+
69
+ const colorIndex = parseInt(match[1]) - 1
70
+ const taggedText = match[2]
71
+ segments.push({ text: taggedText, colorIndex: Math.max(0, colorIndex) })
72
+
73
+ lastIndex = match.index + match[0].length
74
+ }
75
+
76
+ if (lastIndex < text.length) {
77
+ const remainingText = text.slice(lastIndex)
78
+ if (remainingText) {
79
+ segments.push({ text: remainingText, colorIndex: 0 })
80
+ }
81
+ }
82
+
83
+ return segments
84
+ }
85
+
86
+ function getParsedFont(fontKey: keyof typeof fonts): ParsedFontDefinition {
87
+ if (!parsedFonts[fontKey]) {
88
+ const fontDef = fonts[fontKey] as FontDefinition
89
+ const parsedChars: Record<string, FontSegment[][]> = {}
90
+
91
+ for (const [char, lines] of Object.entries(fontDef.chars)) {
92
+ parsedChars[char] = lines.map((line) => parseColorTags(line))
93
+ }
94
+
95
+ parsedFonts[fontKey] = {
96
+ ...fontDef,
97
+ colors: fontDef.colors || 1,
98
+ chars: parsedChars,
99
+ }
100
+ }
101
+
102
+ return parsedFonts[fontKey]
103
+ }
104
+
105
+ export function measureText({ text, font = "tiny" }: { text: string; font?: keyof typeof fonts }): {
106
+ width: number
107
+ height: number
108
+ } {
109
+ const fontDef = getParsedFont(font)
110
+ if (!fontDef) {
111
+ console.warn(`Font '${font}' not found`)
112
+ return { width: 0, height: 0 }
113
+ }
114
+
115
+ let currentX = 0
116
+
117
+ for (let i = 0; i < text.length; i++) {
118
+ const char = text[i].toUpperCase()
119
+ const charDef = fontDef.chars[char]
120
+
121
+ if (!charDef) {
122
+ const spaceChar = fontDef.chars[" "]
123
+ if (spaceChar && spaceChar[0]) {
124
+ let spaceWidth = 0
125
+ for (const segment of spaceChar[0]) {
126
+ spaceWidth += segment.text.length
127
+ }
128
+ currentX += spaceWidth
129
+ } else {
130
+ currentX += 1
131
+ }
132
+ continue
133
+ }
134
+
135
+ let charWidth = 0
136
+ if (charDef[0]) {
137
+ for (const segment of charDef[0]) {
138
+ charWidth += segment.text.length
139
+ }
140
+ }
141
+
142
+ currentX += charWidth
143
+
144
+ if (i < text.length - 1) {
145
+ currentX += fontDef.letterspace_size
146
+ }
147
+ }
148
+
149
+ return {
150
+ width: currentX,
151
+ height: fontDef.lines,
152
+ }
153
+ }
154
+
155
+ export function getCharacterPositions(text: string, font: keyof typeof fonts = "tiny"): number[] {
156
+ const fontDef = getParsedFont(font)
157
+ if (!fontDef) {
158
+ return [0]
159
+ }
160
+
161
+ const positions: number[] = [0]
162
+ let currentX = 0
163
+
164
+ for (let i = 0; i < text.length; i++) {
165
+ const char = text[i].toUpperCase()
166
+ const charDef = fontDef.chars[char]
167
+
168
+ let charWidth = 0
169
+ if (!charDef) {
170
+ const spaceChar = fontDef.chars[" "]
171
+ if (spaceChar && spaceChar[0]) {
172
+ for (const segment of spaceChar[0]) {
173
+ charWidth += segment.text.length
174
+ }
175
+ } else {
176
+ charWidth = 1
177
+ }
178
+ } else if (charDef[0]) {
179
+ for (const segment of charDef[0]) {
180
+ charWidth += segment.text.length
181
+ }
182
+ }
183
+
184
+ currentX += charWidth
185
+
186
+ if (i < text.length - 1) {
187
+ currentX += fontDef.letterspace_size
188
+ }
189
+
190
+ positions.push(currentX)
191
+ }
192
+
193
+ return positions
194
+ }
195
+
196
+ export function coordinateToCharacterIndex(x: number, text: string, font: keyof typeof fonts = "tiny"): number {
197
+ const positions = getCharacterPositions(text, font)
198
+
199
+ if (x < 0) {
200
+ return 0
201
+ }
202
+
203
+ for (let i = 0; i < positions.length - 1; i++) {
204
+ const currentPos = positions[i]
205
+ const nextPos = positions[i + 1]
206
+
207
+ if (x >= currentPos && x < nextPos) {
208
+ const charMidpoint = currentPos + (nextPos - currentPos) / 2
209
+ return x < charMidpoint ? i : i + 1
210
+ }
211
+ }
212
+
213
+ if (positions.length > 0 && x >= positions[positions.length - 1]) {
214
+ return text.length
215
+ }
216
+
217
+ return 0
218
+ }
219
+
220
+ export function renderFontToFrameBuffer(
221
+ buffer: OptimizedBuffer,
222
+ {
223
+ text,
224
+ x = 0,
225
+ y = 0,
226
+ color = [RGBA.fromInts(255, 255, 255, 255)],
227
+ backgroundColor = RGBA.fromInts(0, 0, 0, 255),
228
+ font = "tiny",
229
+ }: {
230
+ text: string
231
+ x?: number
232
+ y?: number
233
+ color?: ColorInput | ColorInput[]
234
+ backgroundColor?: ColorInput
235
+ font?: keyof typeof fonts
236
+ },
237
+ ): { width: number; height: number } {
238
+ const width = buffer.width
239
+ const height = buffer.height
240
+
241
+ const fontDef = getParsedFont(font)
242
+ if (!fontDef) {
243
+ console.warn(`Font '${font}' not found`)
244
+ return { width: 0, height: 0 }
245
+ }
246
+
247
+ const colors = Array.isArray(color) ? color : [color]
248
+
249
+ if (y < 0 || y + fontDef.lines > height) {
250
+ return { width: 0, height: fontDef.lines }
251
+ }
252
+
253
+ let currentX = x
254
+ const startX = x
255
+
256
+ for (let i = 0; i < text.length; i++) {
257
+ const char = text[i].toUpperCase()
258
+ const charDef = fontDef.chars[char]
259
+
260
+ if (!charDef) {
261
+ const spaceChar = fontDef.chars[" "]
262
+ if (spaceChar && spaceChar[0]) {
263
+ let spaceWidth = 0
264
+ for (const segment of spaceChar[0]) {
265
+ spaceWidth += segment.text.length
266
+ }
267
+ currentX += spaceWidth
268
+ } else {
269
+ currentX += 1
270
+ }
271
+ continue
272
+ }
273
+
274
+ let charWidth = 0
275
+ if (charDef[0]) {
276
+ for (const segment of charDef[0]) {
277
+ charWidth += segment.text.length
278
+ }
279
+ }
280
+
281
+ if (currentX >= width) break
282
+ if (currentX + charWidth < 0) {
283
+ currentX += charWidth + fontDef.letterspace_size
284
+ continue
285
+ }
286
+
287
+ for (let lineIdx = 0; lineIdx < fontDef.lines && lineIdx < charDef.length; lineIdx++) {
288
+ const segments = charDef[lineIdx]
289
+ const renderY = y + lineIdx
290
+
291
+ if (renderY >= 0 && renderY < height) {
292
+ let segmentX = currentX
293
+
294
+ for (const segment of segments) {
295
+ const segmentColor = colors[segment.colorIndex] || colors[0]
296
+
297
+ for (let charIdx = 0; charIdx < segment.text.length; charIdx++) {
298
+ const renderX = segmentX + charIdx
299
+
300
+ if (renderX >= 0 && renderX < width) {
301
+ const fontChar = segment.text[charIdx]
302
+ if (fontChar !== " ") {
303
+ buffer.setCellWithAlphaBlending(
304
+ renderX,
305
+ renderY,
306
+ fontChar,
307
+ parseColor(segmentColor),
308
+ parseColor(backgroundColor),
309
+ )
310
+ }
311
+ }
312
+ }
313
+
314
+ segmentX += segment.text.length
315
+ }
316
+ }
317
+ }
318
+
319
+ currentX += charWidth
320
+
321
+ if (i < text.length - 1) {
322
+ currentX += fontDef.letterspace_size
323
+ }
324
+ }
325
+
326
+ return {
327
+ width: currentX - startX,
328
+ height: fontDef.lines,
329
+ }
330
+ }
@@ -0,0 +1,83 @@
1
+ import { test, expect, describe, spyOn, afterEach } from "bun:test"
2
+ import { isValidBorderStyle, parseBorderStyle, type BorderStyle } from "./border.js"
3
+
4
+ describe("isValidBorderStyle", () => {
5
+ test("returns true for valid border styles", () => {
6
+ expect(isValidBorderStyle("single")).toBe(true)
7
+ expect(isValidBorderStyle("double")).toBe(true)
8
+ expect(isValidBorderStyle("rounded")).toBe(true)
9
+ expect(isValidBorderStyle("heavy")).toBe(true)
10
+ })
11
+
12
+ test("returns false for invalid border styles", () => {
13
+ expect(isValidBorderStyle("invalid")).toBe(false)
14
+ expect(isValidBorderStyle("")).toBe(false)
15
+ expect(isValidBorderStyle(null)).toBe(false)
16
+ expect(isValidBorderStyle(undefined)).toBe(false)
17
+ expect(isValidBorderStyle(123)).toBe(false)
18
+ expect(isValidBorderStyle({})).toBe(false)
19
+ expect(isValidBorderStyle([])).toBe(false)
20
+ })
21
+ })
22
+
23
+ describe("parseBorderStyle", () => {
24
+ let warnSpy: ReturnType<typeof spyOn>
25
+
26
+ afterEach(() => {
27
+ warnSpy?.mockRestore()
28
+ })
29
+
30
+ test("returns valid border styles unchanged", () => {
31
+ expect(parseBorderStyle("single")).toBe("single")
32
+ expect(parseBorderStyle("double")).toBe("double")
33
+ expect(parseBorderStyle("rounded")).toBe("rounded")
34
+ expect(parseBorderStyle("heavy")).toBe("heavy")
35
+ })
36
+
37
+ test("falls back to 'single' for invalid string values", () => {
38
+ warnSpy = spyOn(console, "warn").mockImplementation(() => {})
39
+
40
+ expect(parseBorderStyle("invalid")).toBe("single")
41
+ expect(parseBorderStyle("")).toBe("single")
42
+ expect(parseBorderStyle("SINGLE")).toBe("single") // case sensitive
43
+ expect(parseBorderStyle("Single")).toBe("single")
44
+ })
45
+
46
+ test("falls back to custom fallback for invalid values", () => {
47
+ warnSpy = spyOn(console, "warn").mockImplementation(() => {})
48
+
49
+ expect(parseBorderStyle("invalid", "double")).toBe("double")
50
+ expect(parseBorderStyle("invalid", "rounded")).toBe("rounded")
51
+ expect(parseBorderStyle("invalid", "heavy")).toBe("heavy")
52
+ })
53
+
54
+ test("falls back silently for undefined/null without warning", () => {
55
+ warnSpy = spyOn(console, "warn").mockImplementation(() => {})
56
+
57
+ expect(parseBorderStyle(undefined)).toBe("single")
58
+ expect(parseBorderStyle(null)).toBe("single")
59
+ expect(warnSpy).not.toHaveBeenCalled()
60
+ })
61
+
62
+ test("logs warning for invalid non-null/undefined values", () => {
63
+ warnSpy = spyOn(console, "warn").mockImplementation(() => {})
64
+
65
+ parseBorderStyle("invalid-style")
66
+
67
+ expect(warnSpy).toHaveBeenCalledTimes(1)
68
+ expect(warnSpy).toHaveBeenCalledWith(
69
+ 'Invalid borderStyle "invalid-style", falling back to "single". Valid values are: single, double, rounded, heavy',
70
+ )
71
+ })
72
+
73
+ describe("regression: does not crash with unexpected value types", () => {
74
+ test("handles invalid values", () => {
75
+ warnSpy = spyOn(console, "warn").mockImplementation(() => {})
76
+
77
+ expect(parseBorderStyle(123 as unknown as BorderStyle)).toBe("single")
78
+ expect(parseBorderStyle({} as unknown as BorderStyle)).toBe("single")
79
+ expect(parseBorderStyle(true as unknown as BorderStyle)).toBe("single")
80
+ expect(parseBorderStyle((() => "single") as unknown as BorderStyle)).toBe("single")
81
+ })
82
+ })
83
+ })
@@ -0,0 +1,170 @@
1
+ import type { ColorInput } from "./RGBA.js"
2
+
3
+ export interface BorderCharacters {
4
+ topLeft: string
5
+ topRight: string
6
+ bottomLeft: string
7
+ bottomRight: string
8
+ horizontal: string
9
+ vertical: string
10
+ topT: string
11
+ bottomT: string
12
+ leftT: string
13
+ rightT: string
14
+ cross: string
15
+ }
16
+
17
+ export type BorderStyle = "single" | "double" | "rounded" | "heavy"
18
+ export type BorderSides = "top" | "right" | "bottom" | "left"
19
+
20
+ const VALID_BORDER_STYLES: readonly BorderStyle[] = ["single", "double", "rounded", "heavy"] as const
21
+
22
+ export function isValidBorderStyle(value: unknown): value is BorderStyle {
23
+ return typeof value === "string" && VALID_BORDER_STYLES.includes(value as BorderStyle)
24
+ }
25
+
26
+ export function parseBorderStyle(value: unknown, fallback: BorderStyle = "single"): BorderStyle {
27
+ if (isValidBorderStyle(value)) {
28
+ return value
29
+ }
30
+
31
+ if (value !== undefined && value !== null) {
32
+ console.warn(
33
+ `Invalid borderStyle "${value}", falling back to "${fallback}". Valid values are: ${VALID_BORDER_STYLES.join(", ")}`,
34
+ )
35
+ }
36
+ return fallback
37
+ }
38
+
39
+ export const BorderChars: Record<BorderStyle, BorderCharacters> = {
40
+ single: {
41
+ topLeft: "┌",
42
+ topRight: "┐",
43
+ bottomLeft: "└",
44
+ bottomRight: "┘",
45
+ horizontal: "─",
46
+ vertical: "│",
47
+ topT: "┬",
48
+ bottomT: "┴",
49
+ leftT: "├",
50
+ rightT: "┤",
51
+ cross: "┼",
52
+ },
53
+ double: {
54
+ topLeft: "╔",
55
+ topRight: "╗",
56
+ bottomLeft: "╚",
57
+ bottomRight: "╝",
58
+ horizontal: "═",
59
+ vertical: "║",
60
+ topT: "╦",
61
+ bottomT: "╩",
62
+ leftT: "╠",
63
+ rightT: "╣",
64
+ cross: "╬",
65
+ },
66
+ rounded: {
67
+ topLeft: "╭",
68
+ topRight: "╮",
69
+ bottomLeft: "╰",
70
+ bottomRight: "╯",
71
+ horizontal: "─",
72
+ vertical: "│",
73
+ topT: "┬",
74
+ bottomT: "┴",
75
+ leftT: "├",
76
+ rightT: "┤",
77
+ cross: "┼",
78
+ },
79
+ heavy: {
80
+ topLeft: "┏",
81
+ topRight: "┓",
82
+ bottomLeft: "┗",
83
+ bottomRight: "┛",
84
+ horizontal: "━",
85
+ vertical: "┃",
86
+ topT: "┳",
87
+ bottomT: "┻",
88
+ leftT: "┣",
89
+ rightT: "┫",
90
+ cross: "╋",
91
+ },
92
+ }
93
+
94
+ export interface BorderConfig {
95
+ borderStyle: BorderStyle
96
+ border: boolean | BorderSides[]
97
+ borderColor?: ColorInput
98
+ customBorderChars?: BorderCharacters
99
+ }
100
+
101
+ export interface BoxDrawOptions {
102
+ x: number
103
+ y: number
104
+ width: number
105
+ height: number
106
+ borderStyle: BorderStyle
107
+ border: boolean | BorderSides[]
108
+ borderColor: ColorInput
109
+ customBorderChars?: BorderCharacters
110
+ backgroundColor: ColorInput
111
+ shouldFill?: boolean
112
+ title?: string
113
+ titleAlignment?: "left" | "center" | "right"
114
+ bottomTitle?: string
115
+ bottomTitleAlignment?: "left" | "center" | "right"
116
+ }
117
+
118
+ export interface BorderSidesConfig {
119
+ top: boolean
120
+ right: boolean
121
+ bottom: boolean
122
+ left: boolean
123
+ }
124
+
125
+ export function getBorderFromSides(sides: BorderSidesConfig): boolean | BorderSides[] {
126
+ const result: BorderSides[] = []
127
+ if (sides.top) result.push("top")
128
+ if (sides.right) result.push("right")
129
+ if (sides.bottom) result.push("bottom")
130
+ if (sides.left) result.push("left")
131
+ return result.length > 0 ? result : false
132
+ }
133
+
134
+ export function getBorderSides(border: boolean | BorderSides[]): BorderSidesConfig {
135
+ return border === true
136
+ ? { top: true, right: true, bottom: true, left: true }
137
+ : Array.isArray(border)
138
+ ? {
139
+ top: border.includes("top"),
140
+ right: border.includes("right"),
141
+ bottom: border.includes("bottom"),
142
+ left: border.includes("left"),
143
+ }
144
+ : { top: false, right: false, bottom: false, left: false }
145
+ }
146
+
147
+ // Convert BorderCharacters to Uint32Array for passing to Zig
148
+ export function borderCharsToArray(chars: BorderCharacters): Uint32Array {
149
+ const array = new Uint32Array(11)
150
+ array[0] = chars.topLeft.codePointAt(0)!
151
+ array[1] = chars.topRight.codePointAt(0)!
152
+ array[2] = chars.bottomLeft.codePointAt(0)!
153
+ array[3] = chars.bottomRight.codePointAt(0)!
154
+ array[4] = chars.horizontal.codePointAt(0)!
155
+ array[5] = chars.vertical.codePointAt(0)!
156
+ array[6] = chars.topT.codePointAt(0)!
157
+ array[7] = chars.bottomT.codePointAt(0)!
158
+ array[8] = chars.leftT.codePointAt(0)!
159
+ array[9] = chars.rightT.codePointAt(0)!
160
+ array[10] = chars.cross.codePointAt(0)!
161
+ return array
162
+ }
163
+
164
+ // Pre-converted border character arrays for performance
165
+ export const BorderCharArrays: Record<BorderStyle, Uint32Array> = {
166
+ single: borderCharsToArray(BorderChars.single),
167
+ double: borderCharsToArray(BorderChars.double),
168
+ rounded: borderCharsToArray(BorderChars.rounded),
169
+ heavy: borderCharsToArray(BorderChars.heavy),
170
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { isBunfsPath, getBunfsRootPath } from "./bunfs.js"
3
+
4
+ describe("bunfs", () => {
5
+ test("isBunfsPath detects $bunfs paths", () => {
6
+ expect(isBunfsPath("/$bunfs/root/file.wasm")).toBe(true)
7
+ })
8
+
9
+ test("isBunfsPath detects Windows B: paths", () => {
10
+ expect(isBunfsPath("B:\\~BUN\\root\\file.wasm")).toBe(true)
11
+ expect(isBunfsPath("B:/~BUN/root/file.wasm")).toBe(true)
12
+ })
13
+
14
+ test("isBunfsPath ignores regular paths", () => {
15
+ expect(isBunfsPath("/usr/local/bin/file")).toBe(false)
16
+ expect(isBunfsPath("C:/Users/file.wasm")).toBe(false)
17
+ })
18
+
19
+ test("getBunfsRootPath", () => {
20
+ const root = getBunfsRootPath()
21
+ if (process.platform === "win32") {
22
+ expect(root).toBe("B:\\~BUN\\root")
23
+ } else {
24
+ expect(root).toBe("/$bunfs/root")
25
+ }
26
+ })
27
+ })
@@ -0,0 +1,18 @@
1
+ import { basename, join } from "node:path"
2
+
3
+ export function isBunfsPath(path: string): boolean {
4
+ // Removed ambiguous '//' check
5
+ return path.includes("$bunfs") || /^B:[\\/]~BUN/i.test(path)
6
+ }
7
+
8
+ export function getBunfsRootPath(): string {
9
+ return process.platform === "win32" ? "B:\\~BUN\\root" : "/$bunfs/root"
10
+ }
11
+
12
+ /**
13
+ * Normalizes a path to the embedded root.
14
+ * Flattens directory structure to ensure file exists at root.
15
+ */
16
+ export function normalizeBunfsPath(fileName: string): string {
17
+ return join(getBunfsRootPath(), basename(fileName))
18
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it, afterEach } from "bun:test"
2
+ import { createTestRenderer, type TestRenderer } from "../testing/test-renderer.js"
3
+ import { Clipboard, ClipboardTarget } from "./clipboard.js"
4
+ import type { RendererHandle, RenderLib } from "../zig.js"
5
+
6
+ describe("clipboard", () => {
7
+ let renderer: TestRenderer | null = null
8
+
9
+ const respondToOsc52Query = (testRenderer: TestRenderer, response: string) => {
10
+ const lib = (testRenderer as unknown as { lib: RenderLib }).lib
11
+ lib.processCapabilityResponse(testRenderer.rendererPtr, response)
12
+ }
13
+
14
+ afterEach(() => {
15
+ renderer?.destroy()
16
+ renderer = null
17
+ })
18
+
19
+ it("preserves the native selection target ABI", () => {
20
+ expect(ClipboardTarget.Clipboard).toBe(0)
21
+ expect(ClipboardTarget.Primary).toBe(1)
22
+ expect(ClipboardTarget.Select).toBe(2)
23
+ expect(ClipboardTarget.Secondary).toBe(3)
24
+ })
25
+
26
+ it("passes raw UTF-8 bytes to the native encoder", () => {
27
+ let received: Uint8Array | undefined
28
+ const lib = {
29
+ encoder: new TextEncoder(),
30
+ getTerminalCapabilities: () => ({ osc52_support: "unknown" }),
31
+ copyToClipboardOSC52: (_renderer: RendererHandle, _target: number, textUtf8: Uint8Array) => {
32
+ received = textUtf8
33
+ return true
34
+ },
35
+ } as unknown as RenderLib
36
+ const clipboard = new Clipboard(lib, 0 as unknown as RendererHandle)
37
+
38
+ expect(clipboard.copyToClipboardOSC52("世界")).toBe(true)
39
+ expect(received).toEqual(new TextEncoder().encode("世界"))
40
+ })
41
+
42
+ it("treats negative XTGETTCAP Ms replies as inconclusive", async () => {
43
+ ;({ renderer } = await createTestRenderer({ remote: true }))
44
+
45
+ expect(renderer.isOsc52Supported()).toBe(true)
46
+ expect(renderer.copyToClipboardOSC52("test")).toBe(true)
47
+
48
+ respondToOsc52Query(renderer, "\x1bP0+r\x1b\\")
49
+ expect(renderer.copyToClipboardOSC52("test")).toBe(true)
50
+ expect(renderer.clearClipboardOSC52()).toBe(true)
51
+
52
+ respondToOsc52Query(renderer, "\x1bP0+r4d73\x1b\\")
53
+ expect(renderer.copyToClipboardOSC52("test")).toBe(true)
54
+
55
+ respondToOsc52Query(renderer, "\x1bP1+r4d73=2570312573\x1b\\")
56
+
57
+ expect(renderer.copyToClipboardOSC52("test")).toBe(true)
58
+ expect(renderer.copyToClipboardOSC52("test", ClipboardTarget.Primary)).toBe(true)
59
+ expect(renderer.copyToClipboardOSC52("test", ClipboardTarget.Select)).toBe(true)
60
+ expect(renderer.copyToClipboardOSC52("test", ClipboardTarget.Secondary)).toBe(true)
61
+ expect(renderer.clearClipboardOSC52()).toBe(true)
62
+ })
63
+ })