@fairyhunter13/opentui-core 0.1.112 → 0.1.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/dev/keypress-debug-renderer.ts +148 -0
  2. package/dev/keypress-debug.ts +43 -0
  3. package/dev/print-env-vars.ts +32 -0
  4. package/dev/test-tmux-graphics-334.sh +68 -0
  5. package/dev/thai-debug-test.ts +68 -0
  6. package/docs/development.md +144 -0
  7. package/package.json +63 -51
  8. package/scripts/build.ts +400 -0
  9. package/scripts/publish.ts +60 -0
  10. package/src/3d/SpriteResourceManager.ts +286 -0
  11. package/src/3d/SpriteUtils.ts +70 -0
  12. package/src/3d/TextureUtils.ts +196 -0
  13. package/src/3d/ThreeRenderable.ts +197 -0
  14. package/src/3d/WGPURenderer.ts +294 -0
  15. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  16. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  17. package/src/3d/animation/SpriteAnimator.ts +633 -0
  18. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  19. package/src/3d/canvas.ts +464 -0
  20. package/src/3d/index.ts +12 -0
  21. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  22. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  23. package/src/3d/physics/physics-interface.ts +31 -0
  24. package/src/3d/shaders/supersampling.wgsl +201 -0
  25. package/src/3d.ts +3 -0
  26. package/src/NativeSpanFeed.ts +300 -0
  27. package/src/Renderable.ts +1704 -0
  28. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  29. package/src/animation/Timeline.test.ts +2709 -0
  30. package/src/animation/Timeline.ts +598 -0
  31. package/src/ansi.ts +18 -0
  32. package/src/benchmark/attenuation-benchmark.ts +81 -0
  33. package/src/benchmark/colormatrix-benchmark.ts +128 -0
  34. package/src/benchmark/gain-benchmark.ts +80 -0
  35. package/src/benchmark/latest-all-bench-run.json +707 -0
  36. package/src/benchmark/latest-async-bench-run.json +336 -0
  37. package/src/benchmark/latest-default-bench-run.json +657 -0
  38. package/src/benchmark/latest-large-bench-run.json +707 -0
  39. package/src/benchmark/latest-quick-bench-run.json +207 -0
  40. package/src/benchmark/markdown-benchmark.ts +1796 -0
  41. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  42. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  43. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  44. package/src/benchmark/native-span-feed-compare.ts +280 -0
  45. package/src/benchmark/renderer-benchmark.ts +754 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +291 -0
  48. package/src/buffer.ts +554 -0
  49. package/src/console.test.ts +612 -0
  50. package/src/console.ts +1254 -0
  51. package/src/edit-buffer.test.ts +1769 -0
  52. package/src/edit-buffer.ts +411 -0
  53. package/src/editor-view.test.ts +1032 -0
  54. package/src/editor-view.ts +284 -0
  55. package/src/examples/ascii-font-selection-demo.ts +245 -0
  56. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  57. package/src/examples/assets/concrete.png +0 -0
  58. package/src/examples/assets/crate.png +0 -0
  59. package/src/examples/assets/crate_emissive.png +0 -0
  60. package/src/examples/assets/forrest_background.png +0 -0
  61. package/src/examples/assets/hast-example.json +1018 -0
  62. package/src/examples/assets/heart.png +0 -0
  63. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  64. package/src/examples/assets/main_char_idle.png +0 -0
  65. package/src/examples/assets/main_char_jump_end.png +0 -0
  66. package/src/examples/assets/main_char_jump_landing.png +0 -0
  67. package/src/examples/assets/main_char_jump_start.png +0 -0
  68. package/src/examples/assets/main_char_run_loop.png +0 -0
  69. package/src/examples/assets/roughness_map.jpg +0 -0
  70. package/src/examples/build.ts +115 -0
  71. package/src/examples/code-demo.ts +924 -0
  72. package/src/examples/console-demo.ts +358 -0
  73. package/src/examples/core-plugin-slots-demo.ts +759 -0
  74. package/src/examples/diff-demo.ts +701 -0
  75. package/src/examples/draggable-three-demo.ts +259 -0
  76. package/src/examples/editor-demo.ts +322 -0
  77. package/src/examples/extmarks-demo.ts +196 -0
  78. package/src/examples/focus-restore-demo.ts +310 -0
  79. package/src/examples/fonts.ts +245 -0
  80. package/src/examples/fractal-shader-demo.ts +268 -0
  81. package/src/examples/framebuffer-demo.ts +674 -0
  82. package/src/examples/full-unicode-demo.ts +241 -0
  83. package/src/examples/golden-star-demo.ts +933 -0
  84. package/src/examples/grayscale-buffer-demo.ts +249 -0
  85. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  86. package/src/examples/index.ts +926 -0
  87. package/src/examples/input-demo.ts +377 -0
  88. package/src/examples/input-select-layout-demo.ts +425 -0
  89. package/src/examples/install.sh +143 -0
  90. package/src/examples/keypress-debug-demo.ts +452 -0
  91. package/src/examples/lib/HexList.ts +122 -0
  92. package/src/examples/lib/PaletteGrid.ts +125 -0
  93. package/src/examples/lib/standalone-keys.ts +25 -0
  94. package/src/examples/lib/tab-controller.ts +243 -0
  95. package/src/examples/lights-phong-demo.ts +290 -0
  96. package/src/examples/link-demo.ts +220 -0
  97. package/src/examples/live-state-demo.ts +480 -0
  98. package/src/examples/markdown-demo.ts +725 -0
  99. package/src/examples/mouse-interaction-demo.ts +428 -0
  100. package/src/examples/nested-zindex-demo.ts +357 -0
  101. package/src/examples/opacity-example.ts +235 -0
  102. package/src/examples/opentui-demo.ts +1057 -0
  103. package/src/examples/physx-planck-2d-demo.ts +623 -0
  104. package/src/examples/physx-rapier-2d-demo.ts +655 -0
  105. package/src/examples/relative-positioning-demo.ts +323 -0
  106. package/src/examples/scroll-example.ts +214 -0
  107. package/src/examples/scrollbox-mouse-test.ts +112 -0
  108. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  109. package/src/examples/select-demo.ts +237 -0
  110. package/src/examples/shader-cube-demo.ts +1015 -0
  111. package/src/examples/simple-layout-example.ts +591 -0
  112. package/src/examples/slider-demo.ts +617 -0
  113. package/src/examples/split-mode-demo.ts +453 -0
  114. package/src/examples/sprite-animation-demo.ts +443 -0
  115. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  116. package/src/examples/static-sprite-demo.ts +193 -0
  117. package/src/examples/sticky-scroll-example.ts +308 -0
  118. package/src/examples/styled-text-demo.ts +282 -0
  119. package/src/examples/tab-select-demo.ts +219 -0
  120. package/src/examples/terminal-title.ts +29 -0
  121. package/src/examples/terminal.ts +305 -0
  122. package/src/examples/text-node-demo.ts +416 -0
  123. package/src/examples/text-selection-demo.ts +377 -0
  124. package/src/examples/text-table-demo.ts +503 -0
  125. package/src/examples/text-truncation-demo.ts +481 -0
  126. package/src/examples/text-wrap.ts +757 -0
  127. package/src/examples/texture-loading-demo.ts +259 -0
  128. package/src/examples/timeline-example.ts +670 -0
  129. package/src/examples/transparency-demo.ts +400 -0
  130. package/src/examples/vnode-composition-demo.ts +404 -0
  131. package/src/examples/wide-grapheme-overlay-demo.ts +280 -0
  132. package/src/index.ts +24 -0
  133. package/src/lib/KeyHandler.integration.test.ts +292 -0
  134. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  135. package/src/lib/KeyHandler.test.ts +662 -0
  136. package/src/lib/KeyHandler.ts +222 -0
  137. package/src/lib/RGBA.test.ts +984 -0
  138. package/src/lib/RGBA.ts +204 -0
  139. package/src/lib/ascii.font.ts +330 -0
  140. package/src/lib/border.test.ts +83 -0
  141. package/src/lib/border.ts +170 -0
  142. package/src/lib/bunfs.test.ts +27 -0
  143. package/src/lib/bunfs.ts +18 -0
  144. package/src/lib/clipboard.test.ts +41 -0
  145. package/src/lib/clipboard.ts +47 -0
  146. package/src/lib/clock.ts +35 -0
  147. package/src/lib/data-paths.test.ts +133 -0
  148. package/src/lib/data-paths.ts +109 -0
  149. package/src/lib/debounce.ts +106 -0
  150. package/src/lib/detect-links.test.ts +98 -0
  151. package/src/lib/detect-links.ts +56 -0
  152. package/src/lib/env.test.ts +228 -0
  153. package/src/lib/env.ts +209 -0
  154. package/src/lib/extmarks-history.ts +51 -0
  155. package/src/lib/extmarks-multiwidth.test.ts +322 -0
  156. package/src/lib/extmarks.test.ts +3457 -0
  157. package/src/lib/extmarks.ts +843 -0
  158. package/src/lib/fonts/block.json +405 -0
  159. package/src/lib/fonts/grid.json +265 -0
  160. package/src/lib/fonts/huge.json +741 -0
  161. package/src/lib/fonts/pallet.json +314 -0
  162. package/src/lib/fonts/shade.json +591 -0
  163. package/src/lib/fonts/slick.json +321 -0
  164. package/src/lib/fonts/tiny.json +69 -0
  165. package/src/lib/hast-styled-text.ts +59 -0
  166. package/src/lib/index.ts +21 -0
  167. package/src/lib/keymapping.test.ts +317 -0
  168. package/src/lib/keymapping.ts +115 -0
  169. package/src/lib/objects-in-viewport.test.ts +787 -0
  170. package/src/lib/objects-in-viewport.ts +153 -0
  171. package/src/lib/output.capture.ts +58 -0
  172. package/src/lib/parse.keypress-kitty.protocol.test.ts +340 -0
  173. package/src/lib/parse.keypress-kitty.test.ts +663 -0
  174. package/src/lib/parse.keypress-kitty.ts +439 -0
  175. package/src/lib/parse.keypress.test.ts +1849 -0
  176. package/src/lib/parse.keypress.ts +397 -0
  177. package/src/lib/parse.mouse.test.ts +552 -0
  178. package/src/lib/parse.mouse.ts +232 -0
  179. package/src/lib/paste.ts +16 -0
  180. package/src/lib/queue.ts +65 -0
  181. package/src/lib/renderable.validations.test.ts +87 -0
  182. package/src/lib/renderable.validations.ts +83 -0
  183. package/src/lib/scroll-acceleration.ts +98 -0
  184. package/src/lib/selection.ts +240 -0
  185. package/src/lib/singleton.ts +28 -0
  186. package/src/lib/stdin-parser.test.ts +2290 -0
  187. package/src/lib/stdin-parser.ts +1810 -0
  188. package/src/lib/styled-text.ts +178 -0
  189. package/src/lib/terminal-capability-detection.test.ts +202 -0
  190. package/src/lib/terminal-capability-detection.ts +79 -0
  191. package/src/lib/terminal-palette.test.ts +878 -0
  192. package/src/lib/terminal-palette.ts +383 -0
  193. package/src/lib/tree-sitter/assets/README.md +118 -0
  194. package/src/lib/tree-sitter/assets/update.ts +334 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +273 -0
  197. package/src/lib/tree-sitter/client.test.ts +1165 -0
  198. package/src/lib/tree-sitter/client.ts +607 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +86 -0
  200. package/src/lib/tree-sitter/download-utils.ts +148 -0
  201. package/src/lib/tree-sitter/index.ts +28 -0
  202. package/src/lib/tree-sitter/parser.worker.ts +1042 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  204. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  205. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  206. package/src/lib/tree-sitter/types.ts +82 -0
  207. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  208. package/src/lib/tree-sitter-styled-text.ts +306 -0
  209. package/src/lib/validate-dir-name.ts +55 -0
  210. package/src/lib/yoga.options.test.ts +628 -0
  211. package/src/lib/yoga.options.ts +346 -0
  212. package/src/plugins/core-slot.ts +579 -0
  213. package/src/plugins/registry.ts +402 -0
  214. package/src/plugins/types.ts +46 -0
  215. package/src/post/effects.ts +930 -0
  216. package/src/post/filters.ts +489 -0
  217. package/src/post/matrices.ts +288 -0
  218. package/src/renderables/ASCIIFont.ts +219 -0
  219. package/src/renderables/Box.test.ts +205 -0
  220. package/src/renderables/Box.ts +326 -0
  221. package/src/renderables/Code.test.ts +2062 -0
  222. package/src/renderables/Code.ts +357 -0
  223. package/src/renderables/Diff.regression.test.ts +226 -0
  224. package/src/renderables/Diff.test.ts +3101 -0
  225. package/src/renderables/Diff.ts +1211 -0
  226. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  227. package/src/renderables/EditBufferRenderable.ts +1166 -0
  228. package/src/renderables/FrameBuffer.ts +47 -0
  229. package/src/renderables/Input.test.ts +1228 -0
  230. package/src/renderables/Input.ts +247 -0
  231. package/src/renderables/LineNumberRenderable.ts +724 -0
  232. package/src/renderables/Markdown.ts +1393 -0
  233. package/src/renderables/ScrollBar.ts +422 -0
  234. package/src/renderables/ScrollBox.ts +883 -0
  235. package/src/renderables/Select.test.ts +1033 -0
  236. package/src/renderables/Select.ts +524 -0
  237. package/src/renderables/Slider.test.ts +456 -0
  238. package/src/renderables/Slider.ts +342 -0
  239. package/src/renderables/TabSelect.test.ts +197 -0
  240. package/src/renderables/TabSelect.ts +455 -0
  241. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  242. package/src/renderables/Text.test.ts +2660 -0
  243. package/src/renderables/Text.ts +147 -0
  244. package/src/renderables/TextBufferRenderable.ts +518 -0
  245. package/src/renderables/TextNode.test.ts +1058 -0
  246. package/src/renderables/TextNode.ts +325 -0
  247. package/src/renderables/TextTable.test.ts +1421 -0
  248. package/src/renderables/TextTable.ts +1344 -0
  249. package/src/renderables/Textarea.ts +430 -0
  250. package/src/renderables/TimeToFirstDraw.ts +89 -0
  251. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  252. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  253. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  254. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  255. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  256. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  257. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1865 -0
  258. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  259. package/src/renderables/__tests__/Markdown.code-colors.test.ts +242 -0
  260. package/src/renderables/__tests__/Markdown.test.ts +2518 -0
  261. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  262. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  263. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  264. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  265. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  266. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  267. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  268. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  269. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  270. package/src/renderables/__tests__/Textarea.rendering.test.ts +1866 -0
  271. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  272. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  273. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  274. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  275. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  276. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  277. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  278. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  279. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  280. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  281. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  282. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  283. package/src/renderables/composition/README.md +8 -0
  284. package/src/renderables/composition/VRenderable.ts +32 -0
  285. package/src/renderables/composition/constructs.ts +127 -0
  286. package/src/renderables/composition/vnode.ts +289 -0
  287. package/src/renderables/index.ts +23 -0
  288. package/src/renderables/markdown-parser.ts +66 -0
  289. package/src/renderer.ts +2681 -0
  290. package/src/runtime-plugin-support.ts +39 -0
  291. package/src/runtime-plugin.ts +615 -0
  292. package/src/syntax-style.test.ts +841 -0
  293. package/src/syntax-style.ts +257 -0
  294. package/src/testing/README.md +210 -0
  295. package/src/testing/capture-spans.test.ts +194 -0
  296. package/src/testing/integration.test.ts +276 -0
  297. package/src/testing/manual-clock.ts +117 -0
  298. package/src/testing/mock-keys.test.ts +1378 -0
  299. package/src/testing/mock-keys.ts +457 -0
  300. package/src/testing/mock-mouse.test.ts +218 -0
  301. package/src/testing/mock-mouse.ts +247 -0
  302. package/src/testing/mock-tree-sitter-client.ts +73 -0
  303. package/src/testing/spy.ts +13 -0
  304. package/src/testing/test-recorder.test.ts +415 -0
  305. package/src/testing/test-recorder.ts +145 -0
  306. package/src/testing/test-renderer.ts +132 -0
  307. package/src/testing.ts +7 -0
  308. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  309. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  310. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  311. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  312. package/src/tests/allocator-stats.test.ts +38 -0
  313. package/src/tests/destroy-during-render.test.ts +200 -0
  314. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  315. package/src/tests/destroy-on-exit.test.ts +41 -0
  316. package/src/tests/hover-cursor.test.ts +98 -0
  317. package/src/tests/native-span-feed-async.test.ts +173 -0
  318. package/src/tests/native-span-feed-close.test.ts +120 -0
  319. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  320. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  321. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  322. package/src/tests/opacity.test.ts +123 -0
  323. package/src/tests/renderable.snapshot.test.ts +524 -0
  324. package/src/tests/renderable.test.ts +1281 -0
  325. package/src/tests/renderer.clock.test.ts +158 -0
  326. package/src/tests/renderer.console-startup.test.ts +185 -0
  327. package/src/tests/renderer.control.test.ts +425 -0
  328. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  329. package/src/tests/renderer.cursor.test.ts +26 -0
  330. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  331. package/src/tests/renderer.focus-restore.test.ts +257 -0
  332. package/src/tests/renderer.focus.test.ts +294 -0
  333. package/src/tests/renderer.idle.test.ts +219 -0
  334. package/src/tests/renderer.input.test.ts +2237 -0
  335. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  336. package/src/tests/renderer.mouse.test.ts +1274 -0
  337. package/src/tests/renderer.palette.test.ts +629 -0
  338. package/src/tests/renderer.selection.test.ts +49 -0
  339. package/src/tests/renderer.slot-registry.test.ts +684 -0
  340. package/src/tests/renderer.useMouse.test.ts +47 -0
  341. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  342. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  343. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  344. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  345. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  346. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  347. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  348. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  349. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  350. package/src/tests/runtime-plugin-support.test.ts +19 -0
  351. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  352. package/src/tests/runtime-plugin.fixture.ts +40 -0
  353. package/src/tests/runtime-plugin.test.ts +354 -0
  354. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  355. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  356. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  357. package/src/tests/scrollbox.test.ts +1530 -0
  358. package/src/tests/wrap-resize-perf.test.ts +276 -0
  359. package/src/tests/yoga-setters.test.ts +921 -0
  360. package/src/text-buffer-view.test.ts +705 -0
  361. package/src/text-buffer-view.ts +189 -0
  362. package/src/text-buffer.test.ts +347 -0
  363. package/src/text-buffer.ts +250 -0
  364. package/src/types.ts +161 -0
  365. package/src/utils.ts +88 -0
  366. package/src/zig/ansi.zig +268 -0
  367. package/src/zig/bench/README.md +50 -0
  368. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  369. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  370. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  371. package/src/zig/bench/rope-markers_bench.zig +713 -0
  372. package/src/zig/bench/rope_bench.zig +514 -0
  373. package/src/zig/bench/styled-text_bench.zig +470 -0
  374. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  375. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  376. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  377. package/src/zig/bench/utf8_bench.zig +799 -0
  378. package/src/zig/bench-utils.zig +431 -0
  379. package/src/zig/bench.zig +217 -0
  380. package/src/zig/buffer-methods.zig +211 -0
  381. package/src/zig/buffer.zig +2281 -0
  382. package/src/zig/build.zig +289 -0
  383. package/src/zig/build.zig.zon +16 -0
  384. package/src/zig/edit-buffer.zig +825 -0
  385. package/src/zig/editor-view.zig +802 -0
  386. package/src/zig/event-bus.zig +13 -0
  387. package/src/zig/event-emitter.zig +65 -0
  388. package/src/zig/file-logger.zig +92 -0
  389. package/src/zig/grapheme.zig +599 -0
  390. package/src/zig/lib.zig +1854 -0
  391. package/src/zig/link.zig +333 -0
  392. package/src/zig/logger.zig +43 -0
  393. package/src/zig/mem-registry.zig +125 -0
  394. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  395. package/src/zig/native-span-feed.zig +708 -0
  396. package/src/zig/renderer.zig +1393 -0
  397. package/src/zig/rope.zig +1220 -0
  398. package/src/zig/syntax-style.zig +161 -0
  399. package/src/zig/terminal.zig +987 -0
  400. package/src/zig/test.zig +72 -0
  401. package/src/zig/tests/README.md +18 -0
  402. package/src/zig/tests/buffer-methods_test.zig +1109 -0
  403. package/src/zig/tests/buffer_test.zig +2557 -0
  404. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  405. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  406. package/src/zig/tests/editor-view_test.zig +3299 -0
  407. package/src/zig/tests/event-emitter_test.zig +249 -0
  408. package/src/zig/tests/grapheme_test.zig +1304 -0
  409. package/src/zig/tests/link_test.zig +190 -0
  410. package/src/zig/tests/mem-registry_test.zig +473 -0
  411. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  412. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  413. package/src/zig/tests/renderer_test.zig +1017 -0
  414. package/src/zig/tests/rope-nested_test.zig +712 -0
  415. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  416. package/src/zig/tests/rope_test.zig +2362 -0
  417. package/src/zig/tests/segment-merge.test.zig +148 -0
  418. package/src/zig/tests/syntax-style_test.zig +557 -0
  419. package/src/zig/tests/terminal_test.zig +754 -0
  420. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  421. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  422. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  423. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  424. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  425. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  426. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  427. package/src/zig/tests/text-buffer_test.zig +2191 -0
  428. package/src/zig/tests/unicode-width-map.zon +3909 -0
  429. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  430. package/src/zig/tests/utf8_test.zig +4057 -0
  431. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  432. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  433. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  434. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  435. package/src/zig/text-buffer-iterators.zig +499 -0
  436. package/src/zig/text-buffer-segment.zig +404 -0
  437. package/src/zig/text-buffer-view.zig +1371 -0
  438. package/src/zig/text-buffer.zig +1180 -0
  439. package/src/zig/utf8.zig +1948 -0
  440. package/src/zig/utils.zig +9 -0
  441. package/src/zig-structs.ts +261 -0
  442. package/src/zig.ts +3884 -0
  443. package/tsconfig.build.json +24 -0
  444. package/tsconfig.json +27 -0
  445. package/3d/SpriteResourceManager.d.ts +0 -74
  446. package/3d/SpriteUtils.d.ts +0 -13
  447. package/3d/TextureUtils.d.ts +0 -24
  448. package/3d/ThreeRenderable.d.ts +0 -40
  449. package/3d/WGPURenderer.d.ts +0 -61
  450. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  451. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  452. package/3d/animation/SpriteAnimator.d.ts +0 -124
  453. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  454. package/3d/canvas.d.ts +0 -44
  455. package/3d/index.d.ts +0 -12
  456. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  457. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  458. package/3d/physics/physics-interface.d.ts +0 -27
  459. package/3d.d.ts +0 -2
  460. package/3d.js +0 -34041
  461. package/3d.js.map +0 -155
  462. package/LICENSE +0 -21
  463. package/NativeSpanFeed.d.ts +0 -41
  464. package/Renderable.d.ts +0 -334
  465. package/animation/Timeline.d.ts +0 -126
  466. package/ansi.d.ts +0 -13
  467. package/buffer.d.ts +0 -111
  468. package/console.d.ts +0 -144
  469. package/edit-buffer.d.ts +0 -98
  470. package/editor-view.d.ts +0 -73
  471. package/index-8fks7yv1.js +0 -411
  472. package/index-8fks7yv1.js.map +0 -10
  473. package/index-egy5e2rs.js +0 -12267
  474. package/index-egy5e2rs.js.map +0 -42
  475. package/index-tse8gzh0.js +0 -20614
  476. package/index-tse8gzh0.js.map +0 -67
  477. package/index.d.ts +0 -23
  478. package/index.js +0 -478
  479. package/index.js.map +0 -9
  480. package/lib/KeyHandler.d.ts +0 -61
  481. package/lib/RGBA.d.ts +0 -25
  482. package/lib/ascii.font.d.ts +0 -508
  483. package/lib/border.d.ts +0 -51
  484. package/lib/bunfs.d.ts +0 -7
  485. package/lib/clipboard.d.ts +0 -17
  486. package/lib/clock.d.ts +0 -15
  487. package/lib/data-paths.d.ts +0 -26
  488. package/lib/debounce.d.ts +0 -42
  489. package/lib/detect-links.d.ts +0 -6
  490. package/lib/env.d.ts +0 -42
  491. package/lib/extmarks-history.d.ts +0 -17
  492. package/lib/extmarks.d.ts +0 -89
  493. package/lib/hast-styled-text.d.ts +0 -17
  494. package/lib/index.d.ts +0 -21
  495. package/lib/keymapping.d.ts +0 -25
  496. package/lib/objects-in-viewport.d.ts +0 -24
  497. package/lib/output.capture.d.ts +0 -24
  498. package/lib/parse.keypress-kitty.d.ts +0 -2
  499. package/lib/parse.keypress.d.ts +0 -26
  500. package/lib/parse.mouse.d.ts +0 -30
  501. package/lib/paste.d.ts +0 -7
  502. package/lib/queue.d.ts +0 -15
  503. package/lib/renderable.validations.d.ts +0 -12
  504. package/lib/scroll-acceleration.d.ts +0 -43
  505. package/lib/selection.d.ts +0 -63
  506. package/lib/singleton.d.ts +0 -7
  507. package/lib/stdin-parser.d.ts +0 -87
  508. package/lib/styled-text.d.ts +0 -63
  509. package/lib/terminal-capability-detection.d.ts +0 -30
  510. package/lib/terminal-palette.d.ts +0 -50
  511. package/lib/tree-sitter/assets/update.d.ts +0 -11
  512. package/lib/tree-sitter/client.d.ts +0 -47
  513. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  514. package/lib/tree-sitter/download-utils.d.ts +0 -21
  515. package/lib/tree-sitter/index.d.ts +0 -8
  516. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  517. package/lib/tree-sitter/parsers-config.d.ts +0 -53
  518. package/lib/tree-sitter/resolve-ft.d.ts +0 -5
  519. package/lib/tree-sitter/types.d.ts +0 -82
  520. package/lib/tree-sitter-styled-text.d.ts +0 -14
  521. package/lib/validate-dir-name.d.ts +0 -1
  522. package/lib/yoga.options.d.ts +0 -32
  523. package/parser.worker.js +0 -899
  524. package/parser.worker.js.map +0 -12
  525. package/plugins/core-slot.d.ts +0 -72
  526. package/plugins/registry.d.ts +0 -42
  527. package/plugins/types.d.ts +0 -34
  528. package/post/effects.d.ts +0 -147
  529. package/post/filters.d.ts +0 -65
  530. package/post/matrices.d.ts +0 -20
  531. package/renderables/ASCIIFont.d.ts +0 -52
  532. package/renderables/Box.d.ts +0 -81
  533. package/renderables/Code.d.ts +0 -78
  534. package/renderables/Diff.d.ts +0 -142
  535. package/renderables/EditBufferRenderable.d.ts +0 -237
  536. package/renderables/FrameBuffer.d.ts +0 -16
  537. package/renderables/Input.d.ts +0 -67
  538. package/renderables/LineNumberRenderable.d.ts +0 -78
  539. package/renderables/Markdown.d.ts +0 -185
  540. package/renderables/ScrollBar.d.ts +0 -77
  541. package/renderables/ScrollBox.d.ts +0 -124
  542. package/renderables/Select.d.ts +0 -115
  543. package/renderables/Slider.d.ts +0 -47
  544. package/renderables/TabSelect.d.ts +0 -96
  545. package/renderables/Text.d.ts +0 -36
  546. package/renderables/TextBufferRenderable.d.ts +0 -105
  547. package/renderables/TextNode.d.ts +0 -91
  548. package/renderables/TextTable.d.ts +0 -140
  549. package/renderables/Textarea.d.ts +0 -63
  550. package/renderables/TimeToFirstDraw.d.ts +0 -24
  551. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  552. package/renderables/composition/VRenderable.d.ts +0 -16
  553. package/renderables/composition/constructs.d.ts +0 -35
  554. package/renderables/composition/vnode.d.ts +0 -46
  555. package/renderables/index.d.ts +0 -23
  556. package/renderables/markdown-parser.d.ts +0 -10
  557. package/renderer.d.ts +0 -419
  558. package/runtime-plugin-support.d.ts +0 -3
  559. package/runtime-plugin-support.js +0 -29
  560. package/runtime-plugin-support.js.map +0 -10
  561. package/runtime-plugin.d.ts +0 -16
  562. package/runtime-plugin.js +0 -16
  563. package/runtime-plugin.js.map +0 -9
  564. package/syntax-style.d.ts +0 -54
  565. package/testing/manual-clock.d.ts +0 -17
  566. package/testing/mock-keys.d.ts +0 -81
  567. package/testing/mock-mouse.d.ts +0 -38
  568. package/testing/mock-tree-sitter-client.d.ts +0 -23
  569. package/testing/spy.d.ts +0 -7
  570. package/testing/test-recorder.d.ts +0 -61
  571. package/testing/test-renderer.d.ts +0 -23
  572. package/testing.d.ts +0 -6
  573. package/testing.js +0 -697
  574. package/testing.js.map +0 -15
  575. package/text-buffer-view.d.ts +0 -42
  576. package/text-buffer.d.ts +0 -67
  577. package/types.d.ts +0 -139
  578. package/utils.d.ts +0 -14
  579. package/zig-structs.d.ts +0 -155
  580. package/zig.d.ts +0 -353
  581. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  582. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  583. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  584. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  585. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  586. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  587. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  588. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  589. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  590. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  591. /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,733 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "bun:test"
2
+ import { createTestRenderer, type TestRenderer, type MockMouse } from "../../testing/test-renderer.js"
3
+ import { createTextareaRenderable, simulateFrames as _simulateFrames } from "./renderable-test-utils.js"
4
+ import { TestRecorder } from "../../testing/test-recorder.js"
5
+ import { RGBA } from "../../lib/RGBA.js"
6
+ import { ManualClock } from "../../testing/manual-clock.js"
7
+
8
+ let currentRenderer: TestRenderer
9
+ let renderOnce: () => Promise<void>
10
+ let currentMouse: MockMouse
11
+ let clock: ManualClock
12
+
13
+ const simulateFrames = (ms: number, frameInterval?: number) => _simulateFrames(clock, renderOnce, ms, frameInterval)
14
+
15
+ describe("Textarea - Scroll Tests", () => {
16
+ beforeEach(async () => {
17
+ clock = new ManualClock()
18
+ ;({
19
+ renderer: currentRenderer,
20
+ renderOnce,
21
+ mockMouse: currentMouse,
22
+ } = await createTestRenderer({
23
+ width: 80,
24
+ height: 24,
25
+ clock,
26
+ }))
27
+ })
28
+
29
+ afterEach(() => {
30
+ currentRenderer.destroy()
31
+ })
32
+
33
+ describe("Mouse Selection Auto-Scroll", () => {
34
+ it("should auto-scroll down when dragging selection below viewport", async () => {
35
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
36
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
37
+ width: 40,
38
+ height: 10,
39
+ selectable: true,
40
+ })
41
+
42
+ // Position at top
43
+ editor.editBuffer.gotoLine(0)
44
+ await renderOnce()
45
+
46
+ const viewportBefore = editor.editorView.getViewport()
47
+ expect(viewportBefore.offsetY).toBe(0)
48
+
49
+ // Start dragging from top
50
+ await currentMouse.pressDown(editor.x, editor.y)
51
+
52
+ // Move to bottom edge to trigger auto-scroll (keep button pressed)
53
+ await currentMouse.moveTo(editor.x + 5, editor.y + editor.height - 1)
54
+
55
+ // Simulate 1 second of frames for auto-scroll
56
+ await simulateFrames(1000)
57
+
58
+ const viewportAfter = editor.editorView.getViewport()
59
+
60
+ // Release mouse
61
+ await currentMouse.release(editor.x + 5, editor.y + editor.height - 1)
62
+
63
+ // Viewport should have scrolled down significantly
64
+ expect(viewportAfter.offsetY).toBeGreaterThan(viewportBefore.offsetY)
65
+
66
+ editor.destroy()
67
+ })
68
+
69
+ it("should set cursor to selection focus when selecting", async () => {
70
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
71
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
72
+ width: 40,
73
+ height: 10,
74
+ selectable: true,
75
+ })
76
+
77
+ editor.editBuffer.gotoLine(0)
78
+ await renderOnce()
79
+
80
+ const cursorBefore = editor.logicalCursor
81
+
82
+ // Start selection and drag
83
+ await currentMouse.drag(editor.x, editor.y, editor.x + 10, editor.y + 5)
84
+ await renderOnce()
85
+
86
+ const cursorAfter = editor.logicalCursor
87
+
88
+ // Cursor should have moved to the selection focus position
89
+ expect(cursorAfter.row).toBeGreaterThan(cursorBefore.row)
90
+
91
+ editor.destroy()
92
+ })
93
+
94
+ it("should auto-scroll up when dragging selection above viewport", async () => {
95
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
96
+ initialValue: Array.from({ length: 100 }, (_, i) => `Line ${i}`).join("\n"),
97
+ width: 40,
98
+ height: 10,
99
+ selectable: true,
100
+ })
101
+
102
+ // Start somewhere in the middle so we can scroll up
103
+ editor.editBuffer.gotoLine(40)
104
+ await renderOnce()
105
+
106
+ const viewportBefore = editor.editorView.getViewport()
107
+ expect(viewportBefore.offsetY).toBeGreaterThan(0)
108
+
109
+ // Start dragging from within viewport
110
+ await currentMouse.pressDown(editor.x + 2, editor.y + 5)
111
+ // Drag to the top edge (within bounds) to trigger upward auto-scroll
112
+ await currentMouse.moveTo(editor.x + 2, editor.y)
113
+
114
+ // Simulate 1 second of frames for auto-scroll
115
+ await simulateFrames(1000)
116
+
117
+ const viewportAfter = editor.editorView.getViewport()
118
+
119
+ await currentMouse.release(editor.x + 2, editor.y)
120
+
121
+ expect(viewportAfter.offsetY).toBeLessThan(viewportBefore.offsetY)
122
+
123
+ editor.destroy()
124
+ })
125
+
126
+ it("should stop auto-scroll when selection ends", async () => {
127
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
128
+ initialValue: Array.from({ length: 100 }, (_, i) => `Line ${i}`).join("\n"),
129
+ width: 40,
130
+ height: 10,
131
+ selectable: true,
132
+ })
133
+
134
+ editor.editBuffer.gotoLine(0)
135
+ await renderOnce()
136
+
137
+ await currentMouse.pressDown(editor.x + 2, editor.y)
138
+ await currentMouse.moveTo(editor.x + 2, editor.y + editor.height - 1)
139
+
140
+ // Simulate 1 second of auto-scroll
141
+ await simulateFrames(1000)
142
+
143
+ // End selection (mouse up) and render a few more frames
144
+ await currentMouse.release(editor.x + 2, editor.y + editor.height - 1)
145
+ await simulateFrames(200)
146
+
147
+ const viewportAfterRelease = editor.editorView.getViewport()
148
+
149
+ // If selection-end notifications work, viewport should remain stable
150
+ await simulateFrames(1000)
151
+
152
+ const viewportFinal = editor.editorView.getViewport()
153
+
154
+ expect(viewportFinal.offsetY).toBe(viewportAfterRelease.offsetY)
155
+
156
+ editor.destroy()
157
+ })
158
+ })
159
+
160
+ describe("Selection Focus Clamping", () => {
161
+ it("should clamp cursor when dragging selection focus beyond buffer bounds", async () => {
162
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
163
+ initialValue: Array.from({ length: 10 }, (_, i) => `Line ${i}`).join("\n"),
164
+ width: 40,
165
+ height: 10,
166
+ selectable: true,
167
+ })
168
+
169
+ await renderOnce()
170
+
171
+ // Start selection at the top of the buffer
172
+ await currentMouse.pressDown(editor.x, editor.y)
173
+ await renderOnce()
174
+
175
+ // Drag selection far below the renderable's bounds (focusY way beyond buffer)
176
+ await currentMouse.moveTo(editor.x + 2, editor.y + 200)
177
+ await renderOnce()
178
+
179
+ const cursor = editor.logicalCursor
180
+ expect(cursor.row).toBe(9)
181
+
182
+ await currentMouse.release(editor.x + 2, editor.y + 200)
183
+ await renderOnce()
184
+
185
+ editor.destroy()
186
+ })
187
+ })
188
+
189
+ describe("Mouse Click Cursor Positioning", () => {
190
+ it("should set cursor when clicking without dragging", async () => {
191
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
192
+ initialValue: "Line 0\nLine 1\nLine 2\nLine 3\nLine 4",
193
+ width: 40,
194
+ height: 10,
195
+ selectable: true,
196
+ })
197
+
198
+ editor.editBuffer.gotoLine(0)
199
+ await renderOnce()
200
+
201
+ const cursorBefore = editor.logicalCursor
202
+ expect(cursorBefore.row).toBe(0)
203
+ expect(cursorBefore.col).toBe(0)
204
+
205
+ // Click on line 2, column 3
206
+ await currentMouse.click(editor.x + 3, editor.y + 2)
207
+ await renderOnce()
208
+
209
+ const cursorAfter = editor.logicalCursor
210
+ expect(cursorAfter.row).toBe(2)
211
+ expect(cursorAfter.col).toBe(3)
212
+
213
+ editor.destroy()
214
+ })
215
+
216
+ it("should set cursor when clicking on empty line", async () => {
217
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
218
+ initialValue: "Line 0\n\nLine 2\n\nLine 4",
219
+ width: 40,
220
+ height: 10,
221
+ selectable: true,
222
+ })
223
+
224
+ await renderOnce()
225
+
226
+ // Click on empty line 1
227
+ await currentMouse.click(editor.x + 5, editor.y + 1)
228
+ await renderOnce()
229
+
230
+ const cursor1 = editor.logicalCursor
231
+ expect(cursor1.row).toBe(1)
232
+ expect(cursor1.col).toBe(0) // Empty line, cursor at column 0
233
+
234
+ // Click on empty line 3
235
+ await currentMouse.click(editor.x + 10, editor.y + 3)
236
+ await renderOnce()
237
+
238
+ const cursor2 = editor.logicalCursor
239
+ expect(cursor2.row).toBe(3)
240
+ expect(cursor2.col).toBe(0)
241
+
242
+ editor.destroy()
243
+ })
244
+
245
+ it("should clamp cursor when clicking beyond line end", async () => {
246
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
247
+ initialValue: "Short\nMedium line\nVery long line here",
248
+ width: 40,
249
+ height: 10,
250
+ selectable: true,
251
+ })
252
+
253
+ await renderOnce()
254
+
255
+ // Click way beyond the end of "Short" (5 chars)
256
+ await currentMouse.click(editor.x + 20, editor.y)
257
+ await renderOnce()
258
+
259
+ const cursor = editor.logicalCursor
260
+ expect(cursor.row).toBe(0)
261
+ expect(cursor.col).toBeLessThanOrEqual(5) // Clamped to line end
262
+
263
+ editor.destroy()
264
+ })
265
+
266
+ it("should set cursor when clicking with scrolled viewport", async () => {
267
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
268
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
269
+ width: 40,
270
+ height: 10,
271
+ selectable: true,
272
+ })
273
+
274
+ // Scroll to middle
275
+ editor.editBuffer.gotoLine(25)
276
+ await renderOnce()
277
+
278
+ const viewport = editor.editorView.getViewport()
279
+ expect(viewport.offsetY).toBeGreaterThan(10)
280
+
281
+ const offsetYBefore = viewport.offsetY
282
+
283
+ // Click on first visible line (which is line offsetY in absolute terms)
284
+ await currentMouse.click(editor.x + 3, editor.y)
285
+ await renderOnce()
286
+
287
+ const cursor = editor.logicalCursor
288
+ expect(cursor.row).toBe(offsetYBefore) // Should be the first visible line
289
+ expect(cursor.col).toBe(3)
290
+
291
+ editor.destroy()
292
+ })
293
+ })
294
+
295
+ describe("Mouse Wheel Scrolling", () => {
296
+ it("should scroll down on mouse wheel down", async () => {
297
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
298
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
299
+ width: 40,
300
+ height: 10,
301
+ selectable: true,
302
+ })
303
+
304
+ editor.editBuffer.gotoLine(0)
305
+ await renderOnce()
306
+
307
+ const viewportBefore = editor.editorView.getViewport()
308
+ expect(viewportBefore.offsetY).toBe(0)
309
+
310
+ // Scroll down by 3 lines
311
+ for (let i = 0; i < 3; i++) {
312
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "down")
313
+ }
314
+ await renderOnce()
315
+
316
+ const viewportAfter = editor.editorView.getViewport()
317
+ expect(viewportAfter.offsetY).toBe(3)
318
+
319
+ editor.destroy()
320
+ })
321
+
322
+ it("should move cursor into the viewport when wheel scrolling", async () => {
323
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
324
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
325
+ width: 40,
326
+ height: 10,
327
+ selectable: true,
328
+ })
329
+
330
+ editor.editBuffer.gotoLine(0)
331
+ await renderOnce()
332
+
333
+ const cursorBefore = editor.logicalCursor
334
+ expect(cursorBefore.row).toBe(0)
335
+
336
+ // Scroll down a few lines
337
+ for (let i = 0; i < 3; i++) {
338
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "down")
339
+ }
340
+ await renderOnce()
341
+
342
+ const viewportAfter = editor.editorView.getViewport()
343
+ const cursorAfter = editor.logicalCursor
344
+
345
+ // Wheel scrolling uses setViewport(..., moveCursor=true), which moves the cursor to stay visible
346
+ expect(cursorAfter.row).toBeGreaterThan(cursorBefore.row)
347
+ expect(cursorAfter.row).toBeGreaterThanOrEqual(viewportAfter.offsetY)
348
+ expect(cursorAfter.row).toBeLessThan(viewportAfter.offsetY + viewportAfter.height)
349
+
350
+ editor.destroy()
351
+ })
352
+
353
+ it("should scroll up on mouse wheel up", async () => {
354
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
355
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
356
+ width: 40,
357
+ height: 10,
358
+ selectable: true,
359
+ })
360
+
361
+ // Start at line 20
362
+ editor.editBuffer.gotoLine(20)
363
+ await renderOnce()
364
+
365
+ const viewportBefore = editor.editorView.getViewport()
366
+ expect(viewportBefore.offsetY).toBeGreaterThan(10)
367
+ const offsetBefore = viewportBefore.offsetY
368
+
369
+ // Scroll up by 5 lines
370
+ for (let i = 0; i < 5; i++) {
371
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "up")
372
+ }
373
+ await renderOnce()
374
+
375
+ const viewportAfter = editor.editorView.getViewport()
376
+ expect(viewportAfter.offsetY).toBe(offsetBefore - 5)
377
+
378
+ editor.destroy()
379
+ })
380
+
381
+ it("should not scroll beyond top", async () => {
382
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
383
+ initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
384
+ width: 40,
385
+ height: 10,
386
+ selectable: true,
387
+ })
388
+
389
+ editor.editBuffer.gotoLine(2)
390
+ await renderOnce()
391
+
392
+ // Scroll up by 100 lines (should clamp to 0)
393
+ for (let i = 0; i < 100; i++) {
394
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "up")
395
+ }
396
+ await renderOnce()
397
+
398
+ const viewport = editor.editorView.getViewport()
399
+ expect(viewport.offsetY).toBe(0)
400
+
401
+ editor.destroy()
402
+ })
403
+
404
+ it("should not scroll beyond bottom", async () => {
405
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
406
+ initialValue: Array.from({ length: 20 }, (_, i) => `Line ${i}`).join("\n"),
407
+ width: 40,
408
+ height: 10,
409
+ selectable: true,
410
+ })
411
+
412
+ await renderOnce()
413
+
414
+ // Scroll down by 100 lines (should clamp to maxOffsetY = 20 - 10 = 10)
415
+ for (let i = 0; i < 100; i++) {
416
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "down")
417
+ }
418
+ await renderOnce()
419
+
420
+ const viewport = editor.editorView.getViewport()
421
+ expect(viewport.offsetY).toBe(10) // 20 lines - 10 viewport height
422
+
423
+ editor.destroy()
424
+ })
425
+
426
+ it("should allow mouse wheel scroll after selection auto-scroll", async () => {
427
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
428
+ initialValue: Array.from({ length: 100 }, (_, i) => `Line ${i}`).join("\n"),
429
+ width: 40,
430
+ height: 10,
431
+ selectable: true,
432
+ })
433
+
434
+ // Position at top
435
+ editor.editBuffer.gotoLine(0)
436
+ await renderOnce()
437
+
438
+ const viewportInitial = editor.editorView.getViewport()
439
+ expect(viewportInitial.offsetY).toBe(0)
440
+
441
+ // Drag selection from top to way below viewport to trigger auto-scroll to bottom
442
+ await currentMouse.pressDown(editor.x, editor.y)
443
+ await currentMouse.moveTo(editor.x + 5, editor.y + editor.height - 1)
444
+
445
+ // Simulate 2 seconds for auto-scroll to reach near the end
446
+ await simulateFrames(2000)
447
+
448
+ // Release mouse to complete selection
449
+ await currentMouse.release(editor.x + 5, editor.y + editor.height - 1)
450
+
451
+ const viewportAfterSelection = editor.editorView.getViewport()
452
+
453
+ // Should have scrolled down significantly
454
+ expect(viewportAfterSelection.offsetY).toBeGreaterThan(20)
455
+
456
+ // Now use mouse wheel to scroll all the way back up
457
+ for (let i = 0; i < 100; i++) {
458
+ await currentMouse.scroll(editor.x + 5, editor.y + 5, "up")
459
+ }
460
+ await renderOnce()
461
+
462
+ const viewportFinal = editor.editorView.getViewport()
463
+
464
+ // Should have scrolled all the way back to top
465
+ expect(viewportFinal.offsetY).toBe(0)
466
+
467
+ editor.destroy()
468
+ })
469
+ })
470
+
471
+ describe("Mouse Wheel Horizontal Scrolling", () => {
472
+ it("should scroll horizontally with wheel when wrapping is disabled", async () => {
473
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
474
+ initialValue: "A".repeat(200),
475
+ width: 20,
476
+ height: 5,
477
+ wrapMode: "none",
478
+ selectable: true,
479
+ })
480
+
481
+ await renderOnce()
482
+
483
+ // Keep a selection active so native updateBeforeRender doesn't auto-scroll viewport back to cursor
484
+ await currentMouse.drag(editor.x, editor.y, editor.x + 1, editor.y)
485
+ await renderOnce()
486
+
487
+ const viewportBefore = editor.editorView.getViewport()
488
+ expect(viewportBefore.offsetX).toBe(0)
489
+
490
+ for (let i = 0; i < 5; i++) {
491
+ await currentMouse.scroll(editor.x + 2, editor.y + 2, "right")
492
+ }
493
+ await renderOnce()
494
+
495
+ const viewportAfterRight = editor.editorView.getViewport()
496
+ expect(viewportAfterRight.offsetX).toBe(5)
497
+
498
+ for (let i = 0; i < 3; i++) {
499
+ await currentMouse.scroll(editor.x + 2, editor.y + 2, "left")
500
+ }
501
+ await renderOnce()
502
+
503
+ const viewportAfterLeft = editor.editorView.getViewport()
504
+ expect(viewportAfterLeft.offsetX).toBe(2)
505
+
506
+ editor.destroy()
507
+ })
508
+
509
+ it("should not scroll horizontally with wheel when wrapping is enabled", async () => {
510
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
511
+ initialValue: "A".repeat(200),
512
+ width: 20,
513
+ height: 5,
514
+ wrapMode: "word",
515
+ selectable: true,
516
+ })
517
+
518
+ await renderOnce()
519
+
520
+ // Keep selection active to avoid cursor-driven viewport changes
521
+ await currentMouse.drag(editor.x, editor.y, editor.x + 1, editor.y)
522
+ await renderOnce()
523
+
524
+ const viewportBefore = editor.editorView.getViewport()
525
+ expect(viewportBefore.offsetX).toBe(0)
526
+
527
+ for (let i = 0; i < 5; i++) {
528
+ await currentMouse.scroll(editor.x + 2, editor.y + 2, "right")
529
+ }
530
+ await renderOnce()
531
+
532
+ const viewportAfter = editor.editorView.getViewport()
533
+ expect(viewportAfter.offsetX).toBe(0)
534
+
535
+ editor.destroy()
536
+ })
537
+ })
538
+
539
+ describe("Viewport Offset After Resize", () => {
540
+ it("should keep content at bottom when resizing from narrow wrapped to wide unwrapped", async () => {
541
+ const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
542
+ initialValue: Array.from(
543
+ { length: 15 },
544
+ (_, i) => `This is line ${i.toString().padStart(2, "0")} with enough text to wrap when narrow`,
545
+ ).join("\n"),
546
+ width: 10,
547
+ height: 10,
548
+ wrapMode: "word",
549
+ selectable: true,
550
+ })
551
+
552
+ await renderOnce()
553
+
554
+ editor.focus()
555
+
556
+ // Scroll to the very bottom
557
+ editor.editBuffer.gotoLine(999)
558
+ await renderOnce()
559
+
560
+ const viewportAtBottom = editor.editorView.getViewport()
561
+ const totalVirtualLinesNarrow = editor.editorView.getTotalVirtualLineCount()
562
+
563
+ expect(viewportAtBottom.offsetY).toBeGreaterThan(10)
564
+
565
+ // Resize to much wider - this will unwrap most lines
566
+ editor.width = 80
567
+ root.yogaNode.calculateLayout(80, 24)
568
+ await renderOnce()
569
+
570
+ const viewportAfterResize = editor.editorView.getViewport()
571
+ const totalVirtualLinesWide = editor.editorView.getTotalVirtualLineCount()
572
+
573
+ // After unwrapping, total lines should be much less (close to 15 logical lines)
574
+ expect(totalVirtualLinesWide).toBeLessThan(totalVirtualLinesNarrow)
575
+
576
+ // Content should still be at the bottom of the viewport
577
+ // The last line should be visible at the bottom
578
+ const maxOffsetYWide = Math.max(0, totalVirtualLinesWide - viewportAfterResize.height)
579
+ expect(viewportAfterResize.offsetY).toBe(maxOffsetYWide)
580
+
581
+ editor.destroy()
582
+ })
583
+
584
+ it("should clamp horizontal viewport offset when resizing wider with no wrap", async () => {
585
+ const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
586
+ initialValue: "A".repeat(200),
587
+ width: 20,
588
+ height: 10,
589
+ wrapMode: "none",
590
+ selectable: true,
591
+ })
592
+
593
+ await renderOnce()
594
+
595
+ // Scroll horizontally to the far right
596
+ editor.focus()
597
+ for (let i = 0; i < 100; i++) {
598
+ editor.moveCursorRight()
599
+ }
600
+ await renderOnce()
601
+
602
+ const viewportNarrow = editor.editorView.getViewport()
603
+
604
+ expect(viewportNarrow.offsetX).toBeGreaterThan(50)
605
+
606
+ // Resize to much wider - viewport offsetX might now exceed valid range
607
+ editor.width = 250
608
+ root.yogaNode.calculateLayout(80, 24)
609
+ await renderOnce()
610
+
611
+ const viewportWide = editor.editorView.getViewport()
612
+ const totalLineWidthWide = editor.lineInfo.lineWidthColsMax
613
+ const maxOffsetXWide = Math.max(0, totalLineWidthWide - viewportWide.width)
614
+
615
+ expect(viewportWide.offsetX).toBeLessThanOrEqual(maxOffsetXWide)
616
+
617
+ editor.destroy()
618
+ })
619
+
620
+ it("should allow scrolling and selecting last line immediately after resize from wide to narrow", async () => {
621
+ const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
622
+ initialValue: Array.from(
623
+ { length: 20 },
624
+ (_, i) =>
625
+ `Line ${i.toString().padStart(2, "0")} with enough text content to cause wrapping when viewport becomes narrow`,
626
+ ).join("\n"),
627
+ width: 80,
628
+ height: 10,
629
+ wrapMode: "word",
630
+ selectable: true,
631
+ })
632
+
633
+ await renderOnce()
634
+
635
+ // Resize to very narrow - this will cause heavy wrapping
636
+ editor.width = 10
637
+ root.yogaNode.calculateLayout(80, 24)
638
+ await renderOnce()
639
+
640
+ const viewportAfterResize = editor.editorView.getViewport()
641
+ const totalVirtualLinesNarrow = editor.editorView.getTotalVirtualLineCount()
642
+
643
+ expect(totalVirtualLinesNarrow).toBeGreaterThan(20)
644
+
645
+ // Immediately try to scroll down to the bottom with mouse wheel
646
+ const maxOffsetY = Math.max(0, totalVirtualLinesNarrow - viewportAfterResize.height)
647
+
648
+ for (let i = 0; i < maxOffsetY + 20; i++) {
649
+ await currentMouse.scroll(editor.x + 2, editor.y + 2, "down")
650
+ }
651
+ await renderOnce()
652
+
653
+ const viewportAfterScroll = editor.editorView.getViewport()
654
+
655
+ // Should have scrolled close to the bottom (within scroll margin tolerance)
656
+ expect(viewportAfterScroll.offsetY).toBeGreaterThan(maxOffsetY - 5)
657
+ expect(viewportAfterScroll.offsetY).toBeLessThanOrEqual(maxOffsetY)
658
+
659
+ // Now try to select text on the last visible line
660
+ await currentMouse.drag(editor.x, editor.y + editor.height - 1, editor.x + 8, editor.y + editor.height - 1)
661
+ await renderOnce()
662
+
663
+ const selectedText = editor.getSelectedText()
664
+
665
+ expect(editor.hasSelection()).toBe(true)
666
+ expect(selectedText.length).toBeGreaterThan(0)
667
+
668
+ editor.destroy()
669
+ })
670
+
671
+ it("should continuously update selection during auto-scroll without mouse movement", async () => {
672
+ const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
673
+ initialValue: Array.from({ length: 100 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`).join("\n"),
674
+ width: 40,
675
+ height: 10,
676
+ selectable: true,
677
+ selectionBg: RGBA.fromValues(0, 1, 0, 1), // Bright green for easy detection
678
+ })
679
+
680
+ await renderOnce()
681
+
682
+ const recorder = new TestRecorder(currentRenderer, { recordBuffers: { bg: true } })
683
+
684
+ editor.editBuffer.gotoLine(0)
685
+ await renderOnce()
686
+
687
+ recorder.rec()
688
+
689
+ await currentMouse.pressDown(editor.x + 2, editor.y)
690
+ await currentMouse.moveTo(editor.x + 2, editor.y + editor.height - 1)
691
+
692
+ // Simulate 2 seconds of auto-scroll WITHOUT moving mouse
693
+ await simulateFrames(2000)
694
+
695
+ await currentMouse.release(editor.x + 2, editor.y + editor.height - 1)
696
+ await renderOnce()
697
+ recorder.stop()
698
+
699
+ const frames = recorder.recordedFrames
700
+ expect(frames.length).toBeGreaterThan(10)
701
+
702
+ const bufferWidth = currentRenderer.width
703
+ const selectionCellCounts: number[] = []
704
+
705
+ for (const frame of frames) {
706
+ if (!frame.buffers?.bg) continue
707
+
708
+ let selectedCells = 0
709
+ for (let y = editor.y; y < editor.y + editor.height; y++) {
710
+ for (let x = editor.x; x < editor.x + editor.width; x++) {
711
+ const bufferIdx = y * bufferWidth + x
712
+ const bgG = frame.buffers.bg[bufferIdx * 4 + 1]
713
+ if (Math.abs(bgG - 1.0) < 0.01) {
714
+ selectedCells++
715
+ }
716
+ }
717
+ }
718
+ selectionCellCounts.push(selectedCells)
719
+ }
720
+
721
+ const firstFrameSelection = selectionCellCounts[0] || 0
722
+ const lastFrameSelection = selectionCellCounts[selectionCellCounts.length - 1] || 0
723
+
724
+ const framesWithoutSelection = selectionCellCounts.filter((count, i) => i > 0 && count === 0).length
725
+
726
+ // Selection should expand and be continuously visible (no flicker)
727
+ expect(lastFrameSelection).toBeGreaterThan(firstFrameSelection)
728
+ expect(framesWithoutSelection).toBe(0)
729
+
730
+ editor.destroy()
731
+ })
732
+ })
733
+ })