@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,216 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test"
2
+ import { createTestRenderer, type TestRenderer } from "./test-renderer.js"
3
+ import { TextRenderable } from "../renderables/Text.js"
4
+ import { BoxRenderable } from "../renderables/Box.js"
5
+ import { TextAttributes, type CapturedFrame } from "../types.js"
6
+ import { RGBA } from "../lib/index.js"
7
+ import { StyledText } from "../lib/styled-text.js"
8
+
9
+ describe("captureSpans", () => {
10
+ let renderer: TestRenderer
11
+ let renderOnce: () => Promise<void>
12
+ let captureSpans: () => CapturedFrame
13
+
14
+ beforeEach(async () => {
15
+ const setup = await createTestRenderer({ width: 40, height: 10 })
16
+ renderer = setup.renderer
17
+ renderOnce = setup.renderOnce
18
+ captureSpans = setup.captureSpans
19
+ })
20
+
21
+ afterEach(() => {
22
+ renderer.destroy()
23
+ })
24
+
25
+ test("returns correct dimensions and line count", async () => {
26
+ await renderOnce()
27
+ const data = captureSpans()
28
+
29
+ expect(data.cols).toBe(40)
30
+ expect(data.rows).toBe(10)
31
+ expect(data.lines.length).toBe(10)
32
+ })
33
+
34
+ test("captures text content in spans", async () => {
35
+ const text = new TextRenderable(renderer, { content: "Hello World" })
36
+ renderer.root.add(text)
37
+ await renderOnce()
38
+
39
+ const data = captureSpans()
40
+ const firstLine = data.lines[0]
41
+ const textContent = firstLine.spans.map((s) => s.text).join("")
42
+
43
+ expect(textContent).toContain("Hello World")
44
+ })
45
+
46
+ test("groups consecutive cells with same styling into single span", async () => {
47
+ const text = new TextRenderable(renderer, { content: "AAAA" })
48
+ renderer.root.add(text)
49
+ await renderOnce()
50
+
51
+ const data = captureSpans()
52
+ const firstLine = data.lines[0]
53
+ const aaaSpan = firstLine.spans.find((s) => s.text.includes("AAAA"))
54
+
55
+ expect(aaaSpan).toBeDefined()
56
+ expect(aaaSpan!.width).toBeGreaterThanOrEqual(4)
57
+ })
58
+
59
+ test("captures foreground color", async () => {
60
+ const text = new TextRenderable(renderer, {
61
+ content: "Red Text",
62
+ fg: RGBA.fromHex("#ff0000"),
63
+ })
64
+ renderer.root.add(text)
65
+ await renderOnce()
66
+
67
+ const data = captureSpans()
68
+ const firstLine = data.lines[0]
69
+ const redSpan = firstLine.spans.find((s) => s.text.includes("Red"))
70
+
71
+ expect(redSpan).toBeDefined()
72
+ expect(redSpan!.fg.r).toBe(1)
73
+ expect(redSpan!.fg.g).toBe(0)
74
+ expect(redSpan!.fg.b).toBe(0)
75
+ })
76
+
77
+ test("captures background color", async () => {
78
+ const box = new BoxRenderable(renderer, {
79
+ width: 10,
80
+ height: 3,
81
+ backgroundColor: RGBA.fromHex("#00ff00"),
82
+ })
83
+ renderer.root.add(box)
84
+ await renderOnce()
85
+
86
+ const data = captureSpans()
87
+ const secondLine = data.lines[1]
88
+ const greenSpan = secondLine.spans.find((s) => s.bg.g === 1 && s.bg.r === 0 && s.bg.b === 0)
89
+
90
+ expect(greenSpan).toBeDefined()
91
+ })
92
+
93
+ test("returns alpha 0 for transparent colors", async () => {
94
+ await renderOnce()
95
+
96
+ const data = captureSpans()
97
+ const firstLine = data.lines[0]
98
+ const transparentSpan = firstLine.spans.find((s) => s.bg.a === 0)
99
+
100
+ expect(transparentSpan).toBeDefined()
101
+ })
102
+
103
+ test("captures text attributes", async () => {
104
+ const text = new TextRenderable(renderer, {
105
+ content: "Styled",
106
+ attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE | TextAttributes.DIM,
107
+ })
108
+ renderer.root.add(text)
109
+ await renderOnce()
110
+
111
+ const data = captureSpans()
112
+ const firstLine = data.lines[0]
113
+ const styledSpan = firstLine.spans.find((s) => s.text.includes("Styled"))
114
+
115
+ expect(styledSpan).toBeDefined()
116
+ expect(styledSpan!.attributes & TextAttributes.BOLD).toBeTruthy()
117
+ expect(styledSpan!.attributes & TextAttributes.ITALIC).toBeTruthy()
118
+ expect(styledSpan!.attributes & TextAttributes.UNDERLINE).toBeTruthy()
119
+ expect(styledSpan!.attributes & TextAttributes.DIM).toBeTruthy()
120
+ })
121
+
122
+ test("includes cursor position", async () => {
123
+ await renderOnce()
124
+ const data = captureSpans()
125
+
126
+ expect(data.cursor).toEqual([expect.any(Number), expect.any(Number)])
127
+ })
128
+
129
+ test("splits spans when styling changes", async () => {
130
+ const text1 = new TextRenderable(renderer, {
131
+ content: "AAA",
132
+ fg: RGBA.fromHex("#ff0000"),
133
+ })
134
+ const text2 = new TextRenderable(renderer, {
135
+ content: "BBB",
136
+ fg: RGBA.fromHex("#00ff00"),
137
+ })
138
+ renderer.root.add(text1)
139
+ renderer.root.add(text2)
140
+ await renderOnce()
141
+
142
+ const data = captureSpans()
143
+ const allSpans = data.lines.flatMap((l) => l.spans)
144
+
145
+ expect(allSpans.some((s) => s.fg.r === 1 && s.fg.g === 0)).toBe(true)
146
+ expect(allSpans.some((s) => s.fg.g === 1 && s.fg.r === 0)).toBe(true)
147
+ })
148
+
149
+ test("preserves tag-only color intent differences in captured spans", async () => {
150
+ const text = new TextRenderable(renderer, {
151
+ content: new StyledText([
152
+ { __isChunk: true, text: "A", fg: RGBA.fromHex("#aabbcc") },
153
+ { __isChunk: true, text: "B", fg: RGBA.defaultForeground("#aabbcc") },
154
+ ]),
155
+ })
156
+
157
+ renderer.root.add(text)
158
+ await renderOnce()
159
+
160
+ const firstLine = captureSpans().lines[0]
161
+ const contentSpans = firstLine.spans.filter((span) => span.text.trim().length > 0)
162
+
163
+ expect(contentSpans).toHaveLength(2)
164
+ expect(contentSpans[0].text).toBe("A")
165
+ expect(contentSpans[1].text).toBe("B")
166
+ expect(contentSpans[0].fg.intent).toBe("rgb")
167
+ expect(contentSpans[1].fg.intent).toBe("default")
168
+ })
169
+
170
+ test("handles box-drawing characters without crashing", async () => {
171
+ const text = new TextRenderable(renderer, {
172
+ content: "├── folder",
173
+ })
174
+ renderer.root.add(text)
175
+ await renderOnce()
176
+
177
+ const data = captureSpans()
178
+ const firstLine = data.lines[0]
179
+ const textContent = firstLine.spans.map((s) => s.text).join("")
180
+
181
+ expect(textContent).toContain("├── folder")
182
+ })
183
+
184
+ test("handles box borders without crashing", async () => {
185
+ const box = new BoxRenderable(renderer, {
186
+ width: 10,
187
+ height: 4,
188
+ border: true,
189
+ borderStyle: "single",
190
+ borderColor: RGBA.fromHex("#ffffff"),
191
+ })
192
+ renderer.root.add(box)
193
+ await renderOnce()
194
+
195
+ const data = captureSpans()
196
+ expect(data.lines.length).toBe(10)
197
+
198
+ const firstLine = data.lines[0]
199
+ const textContent = firstLine.spans.map((s) => s.text).join("")
200
+ expect(textContent.includes("┌") || textContent.includes("─")).toBe(true)
201
+ })
202
+
203
+ test("handles multi-width characters correctly", async () => {
204
+ const text = new TextRenderable(renderer, {
205
+ content: "A🌟B",
206
+ })
207
+ renderer.root.add(text)
208
+ await renderOnce()
209
+
210
+ const data = captureSpans()
211
+ const firstLine = data.lines[0]
212
+ const textContent = firstLine.spans.map((s) => s.text).join("")
213
+
214
+ expect(textContent).toContain("A🌟B")
215
+ })
216
+ })
@@ -0,0 +1,276 @@
1
+ import { describe, test, expect } from "bun:test"
2
+ import { createMockMouse, MouseButtons } from "./mock-mouse.js"
3
+ import { MouseParser } from "../lib/parse.mouse.js"
4
+
5
+ class MockRenderer {
6
+ public stdin: { emit: (event: string, data: Buffer) => void }
7
+ public emittedData: Buffer[] = []
8
+
9
+ constructor() {
10
+ this.stdin = {
11
+ emit: (event: string, chunk: Buffer) => {
12
+ this.emittedData.push(chunk)
13
+ },
14
+ }
15
+ }
16
+
17
+ getEmittedData(): Buffer {
18
+ return Buffer.concat(this.emittedData)
19
+ }
20
+ }
21
+
22
+ // Helper function to parse all events from buffer
23
+ function parseAllEvents(emittedData: Buffer, parser: MouseParser) {
24
+ return parser.parseAllMouseEvents(emittedData)
25
+ }
26
+
27
+ describe("mock-mouse + parser integration", () => {
28
+ test("simple click is correctly parsed", async () => {
29
+ const mockRenderer = new MockRenderer()
30
+ const mockMouse = createMockMouse(mockRenderer as any)
31
+ const parser = new MouseParser()
32
+
33
+ await mockMouse.click(10, 5)
34
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
35
+
36
+ expect(parsedEvents).toHaveLength(2)
37
+ expect(parsedEvents[0]).toEqual({
38
+ type: "down",
39
+ button: 0,
40
+ x: 10,
41
+ y: 5,
42
+ modifiers: { shift: false, alt: false, ctrl: false },
43
+ scroll: undefined,
44
+ })
45
+ expect(parsedEvents[1]).toEqual({
46
+ type: "up",
47
+ button: 0,
48
+ x: 10,
49
+ y: 5,
50
+ modifiers: { shift: false, alt: false, ctrl: false },
51
+ scroll: undefined,
52
+ })
53
+ })
54
+
55
+ test("double click is correctly parsed", async () => {
56
+ const mockRenderer = new MockRenderer()
57
+ const mockMouse = createMockMouse(mockRenderer as any)
58
+ const parser = new MouseParser()
59
+
60
+ await mockMouse.doubleClick(10, 5)
61
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
62
+
63
+ expect(parsedEvents).toHaveLength(4)
64
+ // All events should be at the same position with LEFT button
65
+ parsedEvents.forEach((event) => {
66
+ expect(event.x).toBe(10)
67
+ expect(event.y).toBe(5)
68
+ expect(event.button).toBe(0)
69
+ })
70
+ // Should alternate down/up
71
+ expect(parsedEvents.map((e) => e.type)).toEqual(["down", "up", "down", "up"])
72
+ })
73
+
74
+ test("pressDown and release separately are correctly parsed", async () => {
75
+ const mockRenderer = new MockRenderer()
76
+ const mockMouse = createMockMouse(mockRenderer as any)
77
+ const parser = new MouseParser()
78
+
79
+ await mockMouse.pressDown(10, 5, MouseButtons.MIDDLE)
80
+ await mockMouse.release(10, 5, MouseButtons.MIDDLE)
81
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
82
+
83
+ expect(parsedEvents).toHaveLength(2)
84
+ expect(parsedEvents[0]).toEqual({
85
+ type: "down",
86
+ button: 1, // MIDDLE button
87
+ x: 10,
88
+ y: 5,
89
+ modifiers: { shift: false, alt: false, ctrl: false },
90
+ scroll: undefined,
91
+ })
92
+ expect(parsedEvents[1]).toEqual({
93
+ type: "up",
94
+ button: 1,
95
+ x: 10,
96
+ y: 5,
97
+ modifiers: { shift: false, alt: false, ctrl: false },
98
+ scroll: undefined,
99
+ })
100
+ })
101
+
102
+ test("different buttons work correctly", async () => {
103
+ const mockRenderer = new MockRenderer()
104
+ const mockMouse = createMockMouse(mockRenderer as any)
105
+ const parser = new MouseParser()
106
+
107
+ // Test RIGHT button
108
+ await mockMouse.click(10, 5, MouseButtons.RIGHT)
109
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
110
+
111
+ expect(parsedEvents).toHaveLength(2)
112
+ parsedEvents.forEach((event) => {
113
+ expect(event.button).toBe(2) // RIGHT button
114
+ expect(event.x).toBe(10)
115
+ expect(event.y).toBe(5)
116
+ })
117
+ })
118
+
119
+ test("all scroll directions are correctly parsed", async () => {
120
+ const mockRenderer = new MockRenderer()
121
+ const mockMouse = createMockMouse(mockRenderer as any)
122
+ const parser = new MouseParser()
123
+
124
+ await mockMouse.scroll(15, 8, "up")
125
+ await mockMouse.scroll(15, 8, "down")
126
+ await mockMouse.scroll(15, 8, "left")
127
+ await mockMouse.scroll(15, 8, "right")
128
+
129
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
130
+
131
+ expect(parsedEvents).toHaveLength(4)
132
+ const expectedDirections: ("up" | "down" | "left" | "right")[] = ["up", "down", "left", "right"]
133
+ const expectedButtons = [0, 1, 2, 0] // Based on parser logic: button & 3, with button=3 becoming 0
134
+ parsedEvents.forEach((event, index) => {
135
+ expect(event.type).toBe("scroll")
136
+ expect(event.button).toBe(expectedButtons[index])
137
+ expect(event.x).toBe(15)
138
+ expect(event.y).toBe(8)
139
+ expect(event.scroll).toEqual({
140
+ direction: expectedDirections[index],
141
+ delta: 1,
142
+ })
143
+ })
144
+ })
145
+
146
+ test("scroll with modifiers is correctly parsed", async () => {
147
+ const mockRenderer = new MockRenderer()
148
+ const mockMouse = createMockMouse(mockRenderer as any)
149
+ const parser = new MouseParser()
150
+
151
+ await mockMouse.scroll(15, 8, "left", { modifiers: { shift: true } })
152
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
153
+
154
+ expect(parsedEvents).toHaveLength(1)
155
+ expect(parsedEvents[0]).toEqual({
156
+ type: "scroll",
157
+ button: 2, // WHEEL_LEFT (66) & 3 = 2
158
+ x: 15,
159
+ y: 8,
160
+ modifiers: { shift: true, alt: false, ctrl: false },
161
+ scroll: { direction: "left", delta: 1 },
162
+ })
163
+ })
164
+
165
+ test("drag events are correctly parsed", async () => {
166
+ const mockRenderer = new MockRenderer()
167
+ const mockMouse = createMockMouse(mockRenderer as any)
168
+ const parser = new MouseParser()
169
+
170
+ await mockMouse.drag(5, 5, 15, 10)
171
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
172
+
173
+ // Should have down, several drag events, and up
174
+ expect(parsedEvents.length).toBeGreaterThan(3)
175
+ expect(parsedEvents[0].type).toBe("down")
176
+ expect(parsedEvents[0].button).toBe(0)
177
+ expect(parsedEvents[0].x).toBe(5)
178
+ expect(parsedEvents[0].y).toBe(5)
179
+
180
+ // Check that intermediate events are drag events
181
+ for (let i = 1; i < parsedEvents.length - 1; i++) {
182
+ expect(parsedEvents[i].type).toBe("drag")
183
+ expect(parsedEvents[i].button).toBe(0)
184
+ }
185
+
186
+ const lastEvent = parsedEvents[parsedEvents.length - 1]
187
+ expect(lastEvent.type).toBe("up")
188
+ expect(lastEvent.x).toBe(15)
189
+ expect(lastEvent.y).toBe(10)
190
+ })
191
+
192
+ test("moveTo without button press generates move events", async () => {
193
+ const mockRenderer = new MockRenderer()
194
+ const mockMouse = createMockMouse(mockRenderer as any)
195
+ const parser = new MouseParser()
196
+
197
+ await mockMouse.moveTo(15, 8)
198
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
199
+
200
+ expect(parsedEvents).toHaveLength(1)
201
+ expect(parsedEvents[0]).toEqual({
202
+ type: "move",
203
+ button: 0,
204
+ x: 15,
205
+ y: 8,
206
+ modifiers: { shift: false, alt: false, ctrl: false },
207
+ scroll: undefined,
208
+ })
209
+ })
210
+
211
+ test("moveTo with button press generates drag events", async () => {
212
+ const mockRenderer = new MockRenderer()
213
+ const mockMouse = createMockMouse(mockRenderer as any)
214
+ const parser = new MouseParser()
215
+
216
+ await mockMouse.pressDown(5, 5)
217
+ await mockMouse.moveTo(15, 8)
218
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
219
+
220
+ expect(parsedEvents).toHaveLength(2)
221
+ expect(parsedEvents[0].type).toBe("down")
222
+ expect(parsedEvents[1].type).toBe("drag")
223
+ expect(parsedEvents[1].x).toBe(15)
224
+ expect(parsedEvents[1].y).toBe(8)
225
+ })
226
+
227
+ test("all modifier combinations work", async () => {
228
+ const mockRenderer = new MockRenderer()
229
+ const mockMouse = createMockMouse(mockRenderer as any)
230
+ const parser = new MouseParser()
231
+
232
+ const modifierCombos = [
233
+ { shift: true },
234
+ { alt: true },
235
+ { ctrl: true },
236
+ { shift: true, alt: true },
237
+ { shift: true, ctrl: true },
238
+ { alt: true, ctrl: true },
239
+ { shift: true, alt: true, ctrl: true },
240
+ ]
241
+
242
+ for (const modifiers of modifierCombos) {
243
+ const testRenderer = new MockRenderer()
244
+ const testMouse = createMockMouse(testRenderer as any)
245
+ const testParser = new MouseParser()
246
+
247
+ await testMouse.click(10, 5, MouseButtons.LEFT, { modifiers })
248
+ const parsedEvents = parseAllEvents(testRenderer.getEmittedData(), testParser)
249
+
250
+ expect(parsedEvents).toHaveLength(2)
251
+ parsedEvents.forEach((event) => {
252
+ expect(event.modifiers).toEqual({
253
+ ...modifiers,
254
+ shift: modifiers.shift || false,
255
+ alt: modifiers.alt || false,
256
+ ctrl: modifiers.ctrl || false,
257
+ })
258
+ })
259
+ }
260
+ })
261
+
262
+ test("drag with different button and modifiers", async () => {
263
+ const mockRenderer = new MockRenderer()
264
+ const mockMouse = createMockMouse(mockRenderer as any)
265
+ const parser = new MouseParser()
266
+
267
+ await mockMouse.drag(5, 5, 15, 10, MouseButtons.RIGHT, { modifiers: { alt: true } })
268
+ const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
269
+
270
+ expect(parsedEvents.length).toBeGreaterThan(3)
271
+ parsedEvents.forEach((event) => {
272
+ expect(event.button).toBe(2) // RIGHT button
273
+ expect(event.modifiers.alt).toBe(true)
274
+ })
275
+ })
276
+ })
@@ -0,0 +1,117 @@
1
+ import type { Clock, TimerHandle } from "../lib/clock.js"
2
+
3
+ interface ScheduledTimer {
4
+ id: number
5
+ fireAt: number
6
+ order: number
7
+ delayMs: number
8
+ repeat: boolean
9
+ fn: () => void
10
+ }
11
+
12
+ function compareTimers(left: ScheduledTimer, right: ScheduledTimer): number {
13
+ if (left.fireAt !== right.fireAt) {
14
+ return left.fireAt - right.fireAt
15
+ }
16
+
17
+ return left.order - right.order
18
+ }
19
+
20
+ export class ManualClock implements Clock {
21
+ private time = 0
22
+ private nextId = 1
23
+ private nextOrder = 0
24
+ private readonly timers = new Map<number, ScheduledTimer>()
25
+
26
+ public now(): number {
27
+ return this.time
28
+ }
29
+
30
+ public setTime(time: number): void {
31
+ const targetTime = Math.floor(time)
32
+
33
+ if (targetTime >= this.time) {
34
+ this.advance(targetTime - this.time)
35
+ return
36
+ }
37
+
38
+ this.time = targetTime
39
+ }
40
+
41
+ public setTimeout(fn: () => void, delayMs: number): TimerHandle {
42
+ return this.schedule(fn, delayMs, false)
43
+ }
44
+
45
+ public clearTimeout(handle: TimerHandle): void {
46
+ this.timers.delete(Number(handle))
47
+ }
48
+
49
+ public setInterval(fn: () => void, delayMs: number): TimerHandle {
50
+ return this.schedule(fn, delayMs, true)
51
+ }
52
+
53
+ public clearInterval(handle: TimerHandle): void {
54
+ this.clearTimeout(handle)
55
+ }
56
+
57
+ public advance(delayMs: number): void {
58
+ const targetTime = this.time + Math.max(0, Math.floor(delayMs))
59
+
60
+ while (true) {
61
+ const nextTimer = this.peekNextTimer()
62
+ if (!nextTimer || nextTimer.fireAt > targetTime) {
63
+ break
64
+ }
65
+
66
+ this.timers.delete(nextTimer.id)
67
+ this.time = nextTimer.fireAt
68
+ nextTimer.fn()
69
+
70
+ if (nextTimer.repeat && !this.timers.has(nextTimer.id)) {
71
+ this.timers.set(nextTimer.id, {
72
+ ...nextTimer,
73
+ fireAt: this.time + nextTimer.delayMs,
74
+ order: this.nextOrder++,
75
+ })
76
+ }
77
+ }
78
+
79
+ this.time = targetTime
80
+ }
81
+
82
+ public runAll(): void {
83
+ while (true) {
84
+ const nextTimer = this.peekNextTimer()
85
+ if (!nextTimer) {
86
+ return
87
+ }
88
+
89
+ this.advance(nextTimer.fireAt - this.time)
90
+ }
91
+ }
92
+
93
+ private schedule(fn: () => void, delayMs: number, repeat: boolean): number {
94
+ const id = this.nextId++
95
+ const normalizedDelay = Math.max(0, Math.floor(delayMs))
96
+ this.timers.set(id, {
97
+ id,
98
+ fireAt: this.time + normalizedDelay,
99
+ order: this.nextOrder++,
100
+ delayMs: normalizedDelay,
101
+ repeat,
102
+ fn,
103
+ })
104
+ return id
105
+ }
106
+
107
+ private peekNextTimer(): ScheduledTimer | null {
108
+ let nextTimer: ScheduledTimer | null = null
109
+ for (const timer of this.timers.values()) {
110
+ if (!nextTimer || compareTimers(timer, nextTimer) < 0) {
111
+ nextTimer = timer
112
+ }
113
+ }
114
+
115
+ return nextTimer
116
+ }
117
+ }