@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,948 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import {
4
+ TextTableRenderable,
5
+ type TextTableCellContent,
6
+ type TextTableColumnFitter,
7
+ type TextTableColumnWidthMode,
8
+ type TextTableContent,
9
+ type CliRenderer,
10
+ } from "../index.js"
11
+ import { createTestRenderer } from "../testing.js"
12
+ import { Command } from "commander"
13
+ import { existsSync } from "node:fs"
14
+ import { mkdir } from "node:fs/promises"
15
+ import path from "node:path"
16
+
17
+ const realStdoutWrite = process.stdout.write.bind(process.stdout)
18
+
19
+ const WORDS = [
20
+ "alpha",
21
+ "bravo",
22
+ "charlie",
23
+ "delta",
24
+ "echo",
25
+ "foxtrot",
26
+ "golf",
27
+ "hotel",
28
+ "india",
29
+ "juliet",
30
+ "kilo",
31
+ "lima",
32
+ "mango",
33
+ "nectar",
34
+ "oscar",
35
+ "papa",
36
+ "quartz",
37
+ "romeo",
38
+ "sierra",
39
+ "tango",
40
+ "uniform",
41
+ "vector",
42
+ "whiskey",
43
+ "xray",
44
+ "yankee",
45
+ "zulu",
46
+ "matrix",
47
+ "signal",
48
+ "tensor",
49
+ "render",
50
+ "schema",
51
+ "buffer",
52
+ "layout",
53
+ "stream",
54
+ "parser",
55
+ "syntax",
56
+ "viewport",
57
+ "cursor",
58
+ ]
59
+
60
+ type MemorySample = {
61
+ rss: number
62
+ heapTotal: number
63
+ heapUsed: number
64
+ external: number
65
+ arrayBuffers: number
66
+ }
67
+
68
+ type MemoryStats = {
69
+ samples: number
70
+ start: MemorySample
71
+ end: MemorySample
72
+ delta: MemorySample
73
+ peak: MemorySample
74
+ }
75
+
76
+ type TimingStats = {
77
+ count: number
78
+ averageMs: number
79
+ medianMs: number
80
+ p95Ms: number
81
+ minMs: number
82
+ maxMs: number
83
+ stdDevMs: number
84
+ }
85
+
86
+ type ScenarioResult = {
87
+ name: string
88
+ description: string
89
+ category: "replace" | "incremental" | "selection"
90
+ timingMode: "content-set-and-render" | "selection-update-and-render"
91
+ iterations: number
92
+ warmupIterations: number
93
+ elapsedMs: number
94
+ updateStats: TimingStats
95
+ memoryStats?: MemoryStats
96
+ tableStats: {
97
+ initialRows: number
98
+ finalRows: number
99
+ maxRows: number
100
+ columns: number
101
+ updates: number
102
+ datasetVariants: number
103
+ }
104
+ settings: Record<string, unknown>
105
+ }
106
+
107
+ type ReplaceScenarioPlan = {
108
+ kind: "replace"
109
+ name: string
110
+ description: string
111
+ iterations: number
112
+ warmupIterations: number
113
+ rows: number
114
+ cols: number
115
+ variants: TextTableContent[]
116
+ tableConfig: BenchmarkTableConfig
117
+ }
118
+
119
+ type IncrementalScenarioPlan = {
120
+ kind: "incremental"
121
+ name: string
122
+ description: string
123
+ iterations: number
124
+ warmupIterations: number
125
+ cols: number
126
+ header: TextTableCellContent[]
127
+ baseRows: TextTableCellContent[][]
128
+ rowPool: TextTableCellContent[][]
129
+ maxRows: number
130
+ tableConfig: BenchmarkTableConfig
131
+ }
132
+
133
+ type SelectionScenarioPlan = {
134
+ kind: "selection"
135
+ name: string
136
+ description: string
137
+ iterations: number
138
+ warmupIterations: number
139
+ rows: number
140
+ cols: number
141
+ content: TextTableContent
142
+ dragSteps: number
143
+ tableConfig: BenchmarkTableConfig
144
+ }
145
+
146
+ type ScenarioPlan = ReplaceScenarioPlan | IncrementalScenarioPlan | SelectionScenarioPlan
147
+
148
+ type BenchmarkTableConfig = {
149
+ wrapMode: "none" | "char" | "word"
150
+ columnWidthMode: TextTableColumnWidthMode
151
+ columnFitter: TextTableColumnFitter
152
+ }
153
+
154
+ type RunContext = {
155
+ renderer: CliRenderer
156
+ table: TextTableRenderable
157
+ renderOnce: () => Promise<void>
158
+ memSampleEvery: number
159
+ }
160
+
161
+ type SuiteConfig = {
162
+ iterations: number
163
+ warmupIterations: number
164
+ longIterations: number
165
+ scale: number
166
+ }
167
+
168
+ type OutputMeta = {
169
+ suiteName: string
170
+ width: number
171
+ height: number
172
+ iterations: number
173
+ warmupIterations: number
174
+ longIterations: number
175
+ scale: number
176
+ seed: number
177
+ memSampleEvery: number
178
+ }
179
+
180
+ type IncrementalState = {
181
+ rows: TextTableCellContent[][]
182
+ cursor: number
183
+ maxRowsSeen: number
184
+ }
185
+
186
+ const program = new Command()
187
+ program
188
+ .name("text-table-benchmark")
189
+ .description("TextTableRenderable benchmark scenarios")
190
+ .option("-s, --suite <name>", "benchmark suite: quick, default, long", "default")
191
+ .option("-i, --iterations <count>", "iterations per scenario", "800")
192
+ .option("--warmup-iterations <count>", "warmup iterations per scenario", "80")
193
+ .option("--long-iterations <count>", "iterations for long suite", "3000")
194
+ .option("--scale <n>", "scale rows and dataset size", "1")
195
+ .option("--seed <n>", "seed for deterministic content", "1337")
196
+ .option("--width <n>", "test renderer width", "140")
197
+ .option("--height <n>", "test renderer height", "48")
198
+ .option("--mem-sample-every <count>", "sample memory every N iterations (0 disables)", "10")
199
+ .option("--scenario <name>", "run a single scenario")
200
+ .option("--json [path]", "write JSON results to file")
201
+ .option("--no-output", "suppress stdout output")
202
+ .parse(process.argv)
203
+
204
+ const options = program.opts()
205
+
206
+ const suiteName = String(options.suite)
207
+ const iterations = Math.max(1, Math.floor(toNumber(options.iterations, 800)))
208
+ const warmupIterations = Math.max(0, Math.floor(toNumber(options.warmupIterations, 80)))
209
+ const longIterations = Math.max(iterations, Math.floor(toNumber(options.longIterations, 3000)))
210
+ const scale = Math.max(0.25, toNumber(options.scale, 1))
211
+ const seed = Math.max(1, Math.floor(toNumber(options.seed, 1337)))
212
+ const width = Math.max(40, Math.floor(toNumber(options.width, 140)))
213
+ const height = Math.max(12, Math.floor(toNumber(options.height, 48)))
214
+ const memSampleEvery = Math.max(0, Math.floor(toNumber(options.memSampleEvery, 10)))
215
+ const scenarioFilter = options.scenario ? String(options.scenario) : null
216
+ const outputEnabled = options.output !== false
217
+
218
+ const PROPORTIONAL_TABLE_CONFIG: BenchmarkTableConfig = {
219
+ wrapMode: "word",
220
+ columnWidthMode: "full",
221
+ columnFitter: "proportional",
222
+ }
223
+
224
+ const BALANCED_TABLE_CONFIG: BenchmarkTableConfig = {
225
+ wrapMode: "word",
226
+ columnWidthMode: "full",
227
+ columnFitter: "balanced",
228
+ }
229
+
230
+ const jsonArg = options.json
231
+ const jsonPath =
232
+ typeof jsonArg === "string"
233
+ ? path.resolve(process.cwd(), jsonArg)
234
+ : jsonArg
235
+ ? path.resolve(process.cwd(), "latest-text-table-bench-run.json")
236
+ : null
237
+
238
+ if (jsonPath) {
239
+ const dir = path.dirname(jsonPath)
240
+ if (!existsSync(dir)) {
241
+ await mkdir(dir, { recursive: true })
242
+ }
243
+ if (existsSync(jsonPath)) {
244
+ console.error(`Error: output file already exists: ${jsonPath}`)
245
+ process.exit(1)
246
+ }
247
+ }
248
+
249
+ const scenarios = createScenarios(
250
+ suiteName,
251
+ {
252
+ iterations,
253
+ warmupIterations,
254
+ longIterations,
255
+ scale,
256
+ },
257
+ seed,
258
+ )
259
+
260
+ if (scenarios.length === 0) {
261
+ console.error(`Unknown suite: ${suiteName}`)
262
+ process.exit(1)
263
+ }
264
+
265
+ const filteredScenarios = scenarioFilter ? scenarios.filter((scenario) => scenario.name === scenarioFilter) : scenarios
266
+
267
+ if (scenarioFilter && filteredScenarios.length === 0) {
268
+ writeLine(`Unknown scenario: ${scenarioFilter}`)
269
+ process.exit(1)
270
+ }
271
+
272
+ const { renderer, renderOnce } = await createTestRenderer({
273
+ width,
274
+ height,
275
+ screenMode: "main-screen",
276
+ externalOutputMode: "passthrough",
277
+ consoleMode: "disabled",
278
+ })
279
+
280
+ renderer.requestRender = () => {}
281
+
282
+ const table = new TextTableRenderable(renderer, {
283
+ id: "text-table-bench",
284
+ width: "100%",
285
+ wrapMode: PROPORTIONAL_TABLE_CONFIG.wrapMode,
286
+ columnWidthMode: PROPORTIONAL_TABLE_CONFIG.columnWidthMode,
287
+ columnFitter: PROPORTIONAL_TABLE_CONFIG.columnFitter,
288
+ content: [],
289
+ })
290
+
291
+ renderer.root.add(table)
292
+ await renderOnce()
293
+
294
+ const ctx: RunContext = {
295
+ renderer,
296
+ table,
297
+ renderOnce,
298
+ memSampleEvery,
299
+ }
300
+
301
+ const results: ScenarioResult[] = []
302
+ const scenarioLines: string[] = []
303
+
304
+ try {
305
+ for (const plan of filteredScenarios) {
306
+ const result = await runScenario(plan, ctx)
307
+ results.push(result)
308
+ scenarioLines.push(formatScenarioResult(result))
309
+ }
310
+ } finally {
311
+ renderer.destroy()
312
+ }
313
+
314
+ await outputResults(
315
+ {
316
+ suiteName,
317
+ width,
318
+ height,
319
+ iterations,
320
+ warmupIterations,
321
+ longIterations,
322
+ scale,
323
+ seed,
324
+ memSampleEvery,
325
+ },
326
+ results,
327
+ scenarioLines,
328
+ outputEnabled,
329
+ jsonPath,
330
+ )
331
+
332
+ function createScenarios(suite: string, config: SuiteConfig, runSeed: number): ScenarioPlan[] {
333
+ const quick = {
334
+ replaceRows: scaled(24, config.scale),
335
+ replaceCols: 4,
336
+ replaceVariants: scaled(6, config.scale),
337
+ incrementalCols: 4,
338
+ incrementalBaseRows: scaled(8, config.scale),
339
+ incrementalPoolRows: scaled(220, config.scale),
340
+ incrementalMaxRows: scaled(120, config.scale),
341
+ }
342
+
343
+ const defaultSuite = {
344
+ replaceRows: scaled(72, config.scale),
345
+ replaceCols: 6,
346
+ replaceVariants: scaled(10, config.scale),
347
+ incrementalCols: 6,
348
+ incrementalBaseRows: scaled(16, config.scale),
349
+ incrementalPoolRows: scaled(480, config.scale),
350
+ incrementalMaxRows: scaled(320, config.scale),
351
+ }
352
+
353
+ const long = {
354
+ replaceRows: scaled(140, config.scale),
355
+ replaceCols: 8,
356
+ replaceVariants: scaled(14, config.scale),
357
+ incrementalCols: 8,
358
+ incrementalBaseRows: scaled(24, config.scale),
359
+ incrementalPoolRows: scaled(960, config.scale),
360
+ incrementalMaxRows: scaled(720, config.scale),
361
+ }
362
+
363
+ let shape: typeof quick
364
+ let runIterations = config.iterations
365
+
366
+ if (suite === "quick") {
367
+ shape = quick
368
+ } else if (suite === "default") {
369
+ shape = defaultSuite
370
+ } else if (suite === "long") {
371
+ shape = long
372
+ runIterations = config.longIterations
373
+ } else {
374
+ return []
375
+ }
376
+
377
+ const replaceRng = createRng((runSeed ^ 0x9e3779b9) >>> 0)
378
+ const variants: TextTableContent[] = []
379
+ for (let i = 0; i < shape.replaceVariants; i += 1) {
380
+ variants.push(buildTableContent(replaceRng, shape.replaceRows, shape.replaceCols))
381
+ }
382
+
383
+ const incrementalRng = createRng((runSeed ^ 0x85ebca6b) >>> 0)
384
+ const header = makeHeader(shape.incrementalCols)
385
+ const baseRows = buildRows(incrementalRng, shape.incrementalBaseRows, shape.incrementalCols, 0)
386
+ const rowPool = buildRows(
387
+ incrementalRng,
388
+ Math.max(shape.incrementalPoolRows, shape.incrementalBaseRows + 1),
389
+ shape.incrementalCols,
390
+ shape.incrementalBaseRows,
391
+ )
392
+
393
+ const replaceScenario: ReplaceScenarioPlan = {
394
+ kind: "replace",
395
+ name: "replace_tables",
396
+ description: "Replace full table content with prebuilt variants",
397
+ iterations: runIterations,
398
+ warmupIterations: config.warmupIterations,
399
+ rows: shape.replaceRows,
400
+ cols: shape.replaceCols,
401
+ variants,
402
+ tableConfig: PROPORTIONAL_TABLE_CONFIG,
403
+ }
404
+
405
+ const balancedFitterReplaceScenario: ReplaceScenarioPlan = {
406
+ kind: "replace",
407
+ name: "replace_tables_balanced_fitter",
408
+ description: "Replace full table content with prebuilt variants (balanced fitter)",
409
+ iterations: runIterations,
410
+ warmupIterations: config.warmupIterations,
411
+ rows: shape.replaceRows,
412
+ cols: shape.replaceCols,
413
+ variants,
414
+ tableConfig: BALANCED_TABLE_CONFIG,
415
+ }
416
+
417
+ const incrementalScenario: IncrementalScenarioPlan = {
418
+ kind: "incremental",
419
+ name: "incremental_table_rows",
420
+ description: "Append table rows and periodically reset to base size",
421
+ iterations: runIterations,
422
+ warmupIterations: config.warmupIterations,
423
+ cols: shape.incrementalCols,
424
+ header,
425
+ baseRows,
426
+ rowPool,
427
+ maxRows: Math.max(shape.incrementalMaxRows, shape.incrementalBaseRows + 1),
428
+ tableConfig: PROPORTIONAL_TABLE_CONFIG,
429
+ }
430
+
431
+ const balancedFitterIncrementalScenario: IncrementalScenarioPlan = {
432
+ kind: "incremental",
433
+ name: "incremental_table_rows_balanced_fitter",
434
+ description: "Append table rows and periodically reset to base size (balanced fitter)",
435
+ iterations: runIterations,
436
+ warmupIterations: config.warmupIterations,
437
+ cols: shape.incrementalCols,
438
+ header,
439
+ baseRows,
440
+ rowPool,
441
+ maxRows: Math.max(shape.incrementalMaxRows, shape.incrementalBaseRows + 1),
442
+ tableConfig: BALANCED_TABLE_CONFIG,
443
+ }
444
+
445
+ const selectionRng = createRng((runSeed ^ 0xa2f9c6d1) >>> 0)
446
+ const selectionContent = buildTableContent(selectionRng, shape.replaceRows, shape.replaceCols)
447
+
448
+ const selectionScenario: SelectionScenarioPlan = {
449
+ kind: "selection",
450
+ name: "selection_update",
451
+ description: "Update selection focus across rows and render",
452
+ iterations: runIterations,
453
+ warmupIterations: config.warmupIterations,
454
+ rows: shape.replaceRows,
455
+ cols: shape.replaceCols,
456
+ content: selectionContent,
457
+ dragSteps: 5,
458
+ tableConfig: PROPORTIONAL_TABLE_CONFIG,
459
+ }
460
+
461
+ const balancedFitterSelectionScenario: SelectionScenarioPlan = {
462
+ kind: "selection",
463
+ name: "selection_update_balanced_fitter",
464
+ description: "Update selection focus across rows and render (balanced fitter)",
465
+ iterations: runIterations,
466
+ warmupIterations: config.warmupIterations,
467
+ rows: shape.replaceRows,
468
+ cols: shape.replaceCols,
469
+ content: selectionContent,
470
+ dragSteps: 5,
471
+ tableConfig: BALANCED_TABLE_CONFIG,
472
+ }
473
+
474
+ return [
475
+ replaceScenario,
476
+ balancedFitterReplaceScenario,
477
+ incrementalScenario,
478
+ balancedFitterIncrementalScenario,
479
+ selectionScenario,
480
+ balancedFitterSelectionScenario,
481
+ ]
482
+ }
483
+
484
+ async function runScenario(plan: ScenarioPlan, ctx: RunContext): Promise<ScenarioResult> {
485
+ if (plan.kind === "replace") {
486
+ return runReplaceScenario(plan, ctx)
487
+ }
488
+ if (plan.kind === "incremental") {
489
+ return runIncrementalScenario(plan, ctx)
490
+ }
491
+ return runSelectionScenario(plan, ctx)
492
+ }
493
+
494
+ function applyTableConfig(table: TextTableRenderable, config: BenchmarkTableConfig): void {
495
+ table.wrapMode = config.wrapMode
496
+ table.columnWidthMode = config.columnWidthMode
497
+ table.columnFitter = config.columnFitter
498
+ }
499
+
500
+ async function runReplaceScenario(plan: ReplaceScenarioPlan, ctx: RunContext): Promise<ScenarioResult> {
501
+ applyTableConfig(ctx.table, plan.tableConfig)
502
+
503
+ for (let i = 0; i < plan.warmupIterations; i += 1) {
504
+ const variant = plan.variants[i % plan.variants.length]
505
+ ctx.table.content = variant
506
+ await ctx.renderOnce()
507
+ }
508
+
509
+ const durations: number[] = []
510
+ const measurementStart = Date.now()
511
+ const memStart = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
512
+ const memSamples: MemorySample[] = []
513
+
514
+ for (let i = 0; i < plan.iterations; i += 1) {
515
+ const variant = plan.variants[i % plan.variants.length]
516
+ const start = performance.now()
517
+ ctx.table.content = variant
518
+ await ctx.renderOnce()
519
+ durations.push(performance.now() - start)
520
+
521
+ if (ctx.memSampleEvery > 0 && (i + 1) % ctx.memSampleEvery === 0) {
522
+ memSamples.push(readMemorySample())
523
+ }
524
+ }
525
+
526
+ const elapsedMs = Date.now() - measurementStart
527
+ const memEnd = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
528
+
529
+ return {
530
+ name: plan.name,
531
+ description: plan.description,
532
+ category: "replace",
533
+ timingMode: "content-set-and-render",
534
+ iterations: plan.iterations,
535
+ warmupIterations: plan.warmupIterations,
536
+ elapsedMs,
537
+ updateStats: computeTimingStats(durations),
538
+ memoryStats: memStart && memEnd ? computeMemoryStats(memSamples, memStart, memEnd) : undefined,
539
+ tableStats: {
540
+ initialRows: plan.rows,
541
+ finalRows: plan.rows,
542
+ maxRows: plan.rows,
543
+ columns: plan.cols,
544
+ updates: plan.iterations,
545
+ datasetVariants: plan.variants.length,
546
+ },
547
+ settings: {
548
+ rows: plan.rows,
549
+ cols: plan.cols,
550
+ variants: plan.variants.length,
551
+ mode: "replace",
552
+ wrapMode: plan.tableConfig.wrapMode,
553
+ columnWidthMode: plan.tableConfig.columnWidthMode,
554
+ columnFitter: plan.tableConfig.columnFitter,
555
+ },
556
+ }
557
+ }
558
+
559
+ async function runIncrementalScenario(plan: IncrementalScenarioPlan, ctx: RunContext): Promise<ScenarioResult> {
560
+ applyTableConfig(ctx.table, plan.tableConfig)
561
+
562
+ const state: IncrementalState = {
563
+ rows: [...plan.baseRows],
564
+ cursor: 0,
565
+ maxRowsSeen: plan.baseRows.length,
566
+ }
567
+
568
+ ctx.table.content = [plan.header, ...state.rows]
569
+ await ctx.renderOnce()
570
+
571
+ for (let i = 0; i < plan.warmupIterations; i += 1) {
572
+ const next = nextIncrementalContent(plan, state)
573
+ ctx.table.content = next
574
+ await ctx.renderOnce()
575
+ }
576
+
577
+ const durations: number[] = []
578
+ const measurementStart = Date.now()
579
+ const memStart = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
580
+ const memSamples: MemorySample[] = []
581
+
582
+ for (let i = 0; i < plan.iterations; i += 1) {
583
+ const next = nextIncrementalContent(plan, state)
584
+
585
+ const start = performance.now()
586
+ ctx.table.content = next
587
+ await ctx.renderOnce()
588
+ durations.push(performance.now() - start)
589
+
590
+ if (ctx.memSampleEvery > 0 && (i + 1) % ctx.memSampleEvery === 0) {
591
+ memSamples.push(readMemorySample())
592
+ }
593
+ }
594
+
595
+ const elapsedMs = Date.now() - measurementStart
596
+ const memEnd = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
597
+
598
+ return {
599
+ name: plan.name,
600
+ description: plan.description,
601
+ category: "incremental",
602
+ timingMode: "content-set-and-render",
603
+ iterations: plan.iterations,
604
+ warmupIterations: plan.warmupIterations,
605
+ elapsedMs,
606
+ updateStats: computeTimingStats(durations),
607
+ memoryStats: memStart && memEnd ? computeMemoryStats(memSamples, memStart, memEnd) : undefined,
608
+ tableStats: {
609
+ initialRows: plan.baseRows.length,
610
+ finalRows: state.rows.length,
611
+ maxRows: state.maxRowsSeen,
612
+ columns: plan.cols,
613
+ updates: plan.iterations,
614
+ datasetVariants: plan.rowPool.length,
615
+ },
616
+ settings: {
617
+ cols: plan.cols,
618
+ baseRows: plan.baseRows.length,
619
+ rowPool: plan.rowPool.length,
620
+ maxRows: plan.maxRows,
621
+ mode: "incremental",
622
+ wrapMode: plan.tableConfig.wrapMode,
623
+ columnWidthMode: plan.tableConfig.columnWidthMode,
624
+ columnFitter: plan.tableConfig.columnFitter,
625
+ },
626
+ }
627
+ }
628
+
629
+ async function runSelectionScenario(plan: SelectionScenarioPlan, ctx: RunContext): Promise<ScenarioResult> {
630
+ applyTableConfig(ctx.table, plan.tableConfig)
631
+ ctx.table.content = plan.content
632
+ await ctx.renderOnce()
633
+
634
+ const tableX = ctx.table.x
635
+ const tableY = ctx.table.y
636
+ const tableH = ctx.table.height
637
+
638
+ const anchorX = tableX + 2
639
+ const anchorY = tableY + 2
640
+
641
+ const maxFocusY = tableY + tableH - 2
642
+ const focusRange = Math.max(1, maxFocusY - anchorY)
643
+
644
+ for (let i = 0; i < plan.warmupIterations; i += 1) {
645
+ const focusY = anchorY + (i % focusRange)
646
+ ctx.renderer.startSelection(ctx.table, anchorX, anchorY)
647
+ for (let step = 1; step <= plan.dragSteps; step += 1) {
648
+ const stepY = anchorY + Math.round(((focusY - anchorY) * step) / plan.dragSteps)
649
+ ctx.renderer.updateSelection(ctx.table, anchorX + 4, stepY)
650
+ }
651
+ await ctx.renderOnce()
652
+ ctx.renderer.clearSelection()
653
+ await ctx.renderOnce()
654
+ }
655
+
656
+ const durations: number[] = []
657
+ const measurementStart = Date.now()
658
+ const memStart = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
659
+ const memSamples: MemorySample[] = []
660
+
661
+ for (let i = 0; i < plan.iterations; i += 1) {
662
+ const focusY = anchorY + (i % focusRange)
663
+
664
+ const start = performance.now()
665
+
666
+ ctx.renderer.startSelection(ctx.table, anchorX, anchorY)
667
+ for (let step = 1; step <= plan.dragSteps; step += 1) {
668
+ const stepY = anchorY + Math.round(((focusY - anchorY) * step) / plan.dragSteps)
669
+ ctx.renderer.updateSelection(ctx.table, anchorX + 4, stepY)
670
+ }
671
+ await ctx.renderOnce()
672
+ ctx.renderer.clearSelection()
673
+ await ctx.renderOnce()
674
+
675
+ durations.push(performance.now() - start)
676
+
677
+ if (ctx.memSampleEvery > 0 && (i + 1) % ctx.memSampleEvery === 0) {
678
+ memSamples.push(readMemorySample())
679
+ }
680
+ }
681
+
682
+ const elapsedMs = Date.now() - measurementStart
683
+ const memEnd = shouldSampleMemory(ctx.memSampleEvery) ? readMemorySample() : null
684
+
685
+ return {
686
+ name: plan.name,
687
+ description: plan.description,
688
+ category: "selection",
689
+ timingMode: "selection-update-and-render",
690
+ iterations: plan.iterations,
691
+ warmupIterations: plan.warmupIterations,
692
+ elapsedMs,
693
+ updateStats: computeTimingStats(durations),
694
+ memoryStats: memStart && memEnd ? computeMemoryStats(memSamples, memStart, memEnd) : undefined,
695
+ tableStats: {
696
+ initialRows: plan.rows,
697
+ finalRows: plan.rows,
698
+ maxRows: plan.rows,
699
+ columns: plan.cols,
700
+ updates: plan.iterations * (plan.dragSteps + 1),
701
+ datasetVariants: 1,
702
+ },
703
+ settings: {
704
+ rows: plan.rows,
705
+ cols: plan.cols,
706
+ dragSteps: plan.dragSteps,
707
+ mode: "selection",
708
+ wrapMode: plan.tableConfig.wrapMode,
709
+ columnWidthMode: plan.tableConfig.columnWidthMode,
710
+ columnFitter: plan.tableConfig.columnFitter,
711
+ },
712
+ }
713
+ }
714
+
715
+ function nextIncrementalContent(plan: IncrementalScenarioPlan, state: IncrementalState): TextTableContent {
716
+ if (state.rows.length >= plan.maxRows) {
717
+ state.rows = [...plan.baseRows]
718
+ }
719
+
720
+ const fallbackRow = plan.rowPool[0] ?? makeDataRow(createRng(1), 0, plan.cols)
721
+ const nextRow = plan.rowPool[state.cursor] ?? fallbackRow
722
+
723
+ state.cursor += 1
724
+ if (state.cursor >= plan.rowPool.length) {
725
+ state.cursor = 0
726
+ }
727
+
728
+ state.rows = [...state.rows, nextRow]
729
+ state.maxRowsSeen = Math.max(state.maxRowsSeen, state.rows.length)
730
+
731
+ return [plan.header, ...state.rows]
732
+ }
733
+
734
+ function makeHeader(cols: number): TextTableCellContent[] {
735
+ const header: TextTableCellContent[] = []
736
+ for (let c = 0; c < cols; c += 1) {
737
+ header.push(chunkCell(`Column ${c + 1}`))
738
+ }
739
+ return header
740
+ }
741
+
742
+ function buildTableContent(rng: () => number, rows: number, cols: number): TextTableContent {
743
+ return [makeHeader(cols), ...buildRows(rng, rows, cols, 0)]
744
+ }
745
+
746
+ function buildRows(rng: () => number, rows: number, cols: number, rowOffset: number): TextTableCellContent[][] {
747
+ const out: TextTableCellContent[][] = []
748
+ for (let r = 0; r < rows; r += 1) {
749
+ out.push(makeDataRow(rng, rowOffset + r, cols))
750
+ }
751
+ return out
752
+ }
753
+
754
+ function makeDataRow(rng: () => number, rowIndex: number, cols: number): TextTableCellContent[] {
755
+ const row: TextTableCellContent[] = []
756
+ for (let c = 0; c < cols; c += 1) {
757
+ row.push(chunkCell(makeCellText(rng, rowIndex, c)))
758
+ }
759
+ return row
760
+ }
761
+
762
+ function chunkCell(text: string): TextTableCellContent {
763
+ return [
764
+ {
765
+ __isChunk: true,
766
+ text,
767
+ },
768
+ ]
769
+ }
770
+
771
+ function makeCellText(rng: () => number, row: number, col: number): string {
772
+ const a = pick(rng, WORDS)
773
+ const b = pick(rng, WORDS)
774
+ const roll = rng()
775
+
776
+ if (roll < 0.2) {
777
+ return `${a}-${b}-${row + col}`
778
+ }
779
+ if (roll < 0.4) {
780
+ return `${a} ${Math.floor(rng() * 1000)}`
781
+ }
782
+ if (roll < 0.6) {
783
+ return `${a} ${b} ${pick(rng, WORDS)}`
784
+ }
785
+ if (roll < 0.8) {
786
+ return `${a}_${b}_${Math.floor(rng() * 100)}`
787
+ }
788
+ return `${a} ${b} r${row}c${col}`
789
+ }
790
+
791
+ function pick<T>(rng: () => number, list: T[]): T {
792
+ return list[Math.floor(rng() * list.length)]
793
+ }
794
+
795
+ function createRng(initialSeed: number): () => number {
796
+ let state = initialSeed >>> 0
797
+ return () => {
798
+ state = (state * 1664525 + 1013904223) >>> 0
799
+ return state / 0x100000000
800
+ }
801
+ }
802
+
803
+ function scaled(value: number, scaleValue: number): number {
804
+ return Math.max(1, Math.round(value * scaleValue))
805
+ }
806
+
807
+ function toNumber(value: unknown, fallback: number): number {
808
+ if (typeof value === "number" && Number.isFinite(value)) return value
809
+ if (typeof value === "string") {
810
+ const parsed = Number(value)
811
+ if (Number.isFinite(parsed)) return parsed
812
+ }
813
+ return fallback
814
+ }
815
+
816
+ function shouldSampleMemory(memSampleEvery: number): boolean {
817
+ return memSampleEvery > 0
818
+ }
819
+
820
+ function readMemorySample(): MemorySample {
821
+ const usage = process.memoryUsage()
822
+ return {
823
+ rss: usage.rss ?? 0,
824
+ heapTotal: usage.heapTotal ?? 0,
825
+ heapUsed: usage.heapUsed ?? 0,
826
+ external: usage.external ?? 0,
827
+ arrayBuffers: usage.arrayBuffers ?? 0,
828
+ }
829
+ }
830
+
831
+ function computeMemoryStats(samples: MemorySample[], start: MemorySample, end: MemorySample): MemoryStats {
832
+ const all = [start, ...samples, end]
833
+ const peak = { ...start }
834
+
835
+ for (const sample of all) {
836
+ peak.rss = Math.max(peak.rss, sample.rss)
837
+ peak.heapTotal = Math.max(peak.heapTotal, sample.heapTotal)
838
+ peak.heapUsed = Math.max(peak.heapUsed, sample.heapUsed)
839
+ peak.external = Math.max(peak.external, sample.external)
840
+ peak.arrayBuffers = Math.max(peak.arrayBuffers, sample.arrayBuffers)
841
+ }
842
+
843
+ return {
844
+ samples: all.length,
845
+ start,
846
+ end,
847
+ delta: diffMemory(start, end),
848
+ peak,
849
+ }
850
+ }
851
+
852
+ function diffMemory(start: MemorySample, end: MemorySample): MemorySample {
853
+ return {
854
+ rss: end.rss - start.rss,
855
+ heapTotal: end.heapTotal - start.heapTotal,
856
+ heapUsed: end.heapUsed - start.heapUsed,
857
+ external: end.external - start.external,
858
+ arrayBuffers: end.arrayBuffers - start.arrayBuffers,
859
+ }
860
+ }
861
+
862
+ function computeTimingStats(durations: number[]): TimingStats {
863
+ const sorted = [...durations].sort((a, b) => a - b)
864
+ const count = sorted.length
865
+ const sum = sorted.reduce((acc, value) => acc + value, 0)
866
+ const average = count > 0 ? sum / count : 0
867
+ const min = sorted[0] ?? 0
868
+ const max = sorted[count - 1] ?? 0
869
+ const median = count > 0 ? (sorted[Math.floor(count / 2)] ?? 0) : 0
870
+ const p95 = count > 0 ? (sorted[Math.floor(count * 0.95)] ?? 0) : 0
871
+ const stdDev = count > 0 ? Math.sqrt(sorted.reduce((acc, v) => acc + Math.pow(v - average, 2), 0) / count) : 0
872
+
873
+ return {
874
+ count,
875
+ averageMs: average,
876
+ medianMs: median,
877
+ p95Ms: p95,
878
+ minMs: min,
879
+ maxMs: max,
880
+ stdDevMs: stdDev,
881
+ }
882
+ }
883
+
884
+ async function outputResults(
885
+ meta: OutputMeta,
886
+ results: ScenarioResult[],
887
+ scenarioLines: string[],
888
+ outputEnabled: boolean,
889
+ outputPath: string | null,
890
+ ): Promise<void> {
891
+ const runId = new Date().toISOString()
892
+ const payload = {
893
+ runId,
894
+ suite: meta.suiteName,
895
+ config: {
896
+ width: meta.width,
897
+ height: meta.height,
898
+ iterations: meta.iterations,
899
+ warmupIterations: meta.warmupIterations,
900
+ longIterations: meta.longIterations,
901
+ scale: meta.scale,
902
+ seed: meta.seed,
903
+ memSampleEvery: meta.memSampleEvery,
904
+ },
905
+ results,
906
+ }
907
+
908
+ if (outputEnabled) {
909
+ writeLine(
910
+ `text-table-benchmark suite=${meta.suiteName} mode=content-set-and-render iters=${meta.iterations} warmup=${meta.warmupIterations}`,
911
+ )
912
+ for (const line of scenarioLines) {
913
+ writeLine(line)
914
+ }
915
+ }
916
+
917
+ if (outputPath) {
918
+ try {
919
+ const json = JSON.stringify(payload, null, 2)
920
+ await Bun.write(outputPath, json)
921
+ } catch (error: any) {
922
+ writeLine(`Error writing results to ${outputPath}: ${error.message}`)
923
+ }
924
+ }
925
+ }
926
+
927
+ function formatBytes(value: number): string {
928
+ return `${(value / (1024 * 1024)).toFixed(2)}MB`
929
+ }
930
+
931
+ function formatScenarioResult(result: ScenarioResult): string {
932
+ const mem = result.memoryStats
933
+ const memSummary = mem
934
+ ? ` memDeltaRss=${formatBytes(mem.delta.rss)}` +
935
+ ` memDeltaHeap=${formatBytes(mem.delta.heapUsed)}` +
936
+ ` memDeltaExt=${formatBytes(mem.delta.external)}` +
937
+ ` memDeltaAB=${formatBytes(mem.delta.arrayBuffers)}` +
938
+ ` memPeakRss=${formatBytes(mem.peak.rss)}`
939
+ : ""
940
+
941
+ const fitter = typeof result.settings.columnFitter === "string" ? result.settings.columnFitter : "unknown"
942
+
943
+ return `scenario=${result.name} category=${result.category} mode=${result.timingMode} fitter=${fitter} iters=${result.updateStats.count} elapsedMs=${result.elapsedMs} avgMs=${result.updateStats.averageMs.toFixed(3)} medianMs=${result.updateStats.medianMs.toFixed(3)} p95Ms=${result.updateStats.p95Ms.toFixed(3)} minMs=${result.updateStats.minMs.toFixed(3)} maxMs=${result.updateStats.maxMs.toFixed(3)} rows=${result.tableStats.finalRows} maxRows=${result.tableStats.maxRows} cols=${result.tableStats.columns}${memSummary}`
944
+ }
945
+
946
+ function writeLine(line: string): void {
947
+ realStdoutWrite(`${line}\n`)
948
+ }