@fairyhunter13/opentui-core 0.1.88 → 0.1.90

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 (570) 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 +141 -0
  7. package/docs/env-vars.md +140 -0
  8. package/docs/getting-started.md +353 -0
  9. package/docs/renderables-vs-constructs.md +159 -0
  10. package/docs/tree-sitter.md +311 -0
  11. package/package.json +61 -52
  12. package/scripts/build.ts +400 -0
  13. package/scripts/publish.ts +60 -0
  14. package/src/3d/SpriteResourceManager.ts +286 -0
  15. package/src/3d/SpriteUtils.ts +71 -0
  16. package/src/3d/TextureUtils.ts +196 -0
  17. package/src/3d/ThreeRenderable.ts +197 -0
  18. package/src/3d/WGPURenderer.ts +294 -0
  19. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  20. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  21. package/src/3d/animation/SpriteAnimator.ts +633 -0
  22. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  23. package/src/3d/canvas.ts +464 -0
  24. package/src/3d/index.ts +12 -0
  25. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  26. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  27. package/src/3d/physics/physics-interface.ts +31 -0
  28. package/src/3d/shaders/supersampling.wgsl +201 -0
  29. package/src/3d.ts +3 -0
  30. package/src/NativeSpanFeed.ts +300 -0
  31. package/src/Renderable.ts +1698 -0
  32. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  33. package/src/animation/Timeline.test.ts +2709 -0
  34. package/src/animation/Timeline.ts +598 -0
  35. package/src/ansi.ts +18 -0
  36. package/src/benchmark/latest-all-bench-run.json +707 -0
  37. package/src/benchmark/latest-async-bench-run.json +336 -0
  38. package/src/benchmark/latest-default-bench-run.json +657 -0
  39. package/src/benchmark/latest-large-bench-run.json +707 -0
  40. package/src/benchmark/latest-quick-bench-run.json +207 -0
  41. package/src/benchmark/markdown-benchmark.ts +1804 -0
  42. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  43. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  44. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  45. package/src/benchmark/native-span-feed-compare.ts +280 -0
  46. package/src/benchmark/renderer-benchmark.ts +754 -0
  47. package/src/benchmark/text-table-benchmark.ts +947 -0
  48. package/src/buffer.test.ts +291 -0
  49. package/src/buffer.ts +519 -0
  50. package/src/console.test.ts +612 -0
  51. package/src/console.ts +1255 -0
  52. package/src/edit-buffer.test.ts +1769 -0
  53. package/src/edit-buffer.ts +411 -0
  54. package/src/editor-view.test.ts +1032 -0
  55. package/src/editor-view.ts +284 -0
  56. package/src/examples/ascii-font-selection-demo.ts +245 -0
  57. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  58. package/src/examples/assets/concrete.png +0 -0
  59. package/src/examples/assets/crate.png +0 -0
  60. package/src/examples/assets/crate_emissive.png +0 -0
  61. package/src/examples/assets/forrest_background.png +0 -0
  62. package/src/examples/assets/hast-example.json +1018 -0
  63. package/src/examples/assets/heart.png +0 -0
  64. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  65. package/src/examples/assets/main_char_idle.png +0 -0
  66. package/src/examples/assets/main_char_jump_end.png +0 -0
  67. package/src/examples/assets/main_char_jump_landing.png +0 -0
  68. package/src/examples/assets/main_char_jump_start.png +0 -0
  69. package/src/examples/assets/main_char_run_loop.png +0 -0
  70. package/src/examples/assets/roughness_map.jpg +0 -0
  71. package/src/examples/build.ts +115 -0
  72. package/src/examples/code-demo.ts +584 -0
  73. package/src/examples/console-demo.ts +358 -0
  74. package/src/examples/core-plugin-slots-demo.ts +759 -0
  75. package/src/examples/diff-demo.ts +699 -0
  76. package/src/examples/draggable-three-demo.ts +259 -0
  77. package/src/examples/editor-demo.ts +322 -0
  78. package/src/examples/extmarks-demo.ts +204 -0
  79. package/src/examples/focus-restore-demo.ts +310 -0
  80. package/src/examples/fonts.ts +245 -0
  81. package/src/examples/fractal-shader-demo.ts +268 -0
  82. package/src/examples/framebuffer-demo.ts +674 -0
  83. package/src/examples/full-unicode-demo.ts +181 -0
  84. package/src/examples/golden-star-demo.ts +933 -0
  85. package/src/examples/grayscale-buffer-demo.ts +249 -0
  86. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  87. package/src/examples/index.ts +925 -0
  88. package/src/examples/input-demo.ts +377 -0
  89. package/src/examples/input-select-layout-demo.ts +425 -0
  90. package/src/examples/install.sh +143 -0
  91. package/src/examples/keypress-debug-demo.ts +452 -0
  92. package/src/examples/lib/HexList.ts +122 -0
  93. package/src/examples/lib/PaletteGrid.ts +125 -0
  94. package/src/examples/lib/standalone-keys.ts +25 -0
  95. package/src/examples/lib/tab-controller.ts +243 -0
  96. package/src/examples/lights-phong-demo.ts +290 -0
  97. package/src/examples/link-demo.ts +220 -0
  98. package/src/examples/live-state-demo.ts +480 -0
  99. package/src/examples/markdown-demo.ts +620 -0
  100. package/src/examples/mouse-interaction-demo.ts +428 -0
  101. package/src/examples/nested-zindex-demo.ts +357 -0
  102. package/src/examples/opacity-example.ts +235 -0
  103. package/src/examples/opentui-demo.ts +1057 -0
  104. package/src/examples/physx-planck-2d-demo.ts +507 -0
  105. package/src/examples/physx-rapier-2d-demo.ts +526 -0
  106. package/src/examples/relative-positioning-demo.ts +323 -0
  107. package/src/examples/scroll-example.ts +214 -0
  108. package/src/examples/scrollbox-mouse-test.ts +112 -0
  109. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  110. package/src/examples/select-demo.ts +237 -0
  111. package/src/examples/shader-cube-demo.ts +772 -0
  112. package/src/examples/simple-layout-example.ts +591 -0
  113. package/src/examples/slider-demo.ts +617 -0
  114. package/src/examples/split-mode-demo.ts +445 -0
  115. package/src/examples/sprite-animation-demo.ts +443 -0
  116. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  117. package/src/examples/static-sprite-demo.ts +193 -0
  118. package/src/examples/sticky-scroll-example.ts +308 -0
  119. package/src/examples/styled-text-demo.ts +282 -0
  120. package/src/examples/tab-select-demo.ts +219 -0
  121. package/src/examples/terminal-title.ts +29 -0
  122. package/src/examples/terminal.ts +305 -0
  123. package/src/examples/text-node-demo.ts +416 -0
  124. package/src/examples/text-selection-demo.ts +377 -0
  125. package/src/examples/text-table-demo.ts +503 -0
  126. package/src/examples/text-truncation-demo.ts +481 -0
  127. package/src/examples/text-wrap.ts +757 -0
  128. package/src/examples/texture-loading-demo.ts +259 -0
  129. package/src/examples/timeline-example.ts +670 -0
  130. package/src/examples/transparency-demo.ts +241 -0
  131. package/src/examples/vnode-composition-demo.ts +404 -0
  132. package/src/index.ts +22 -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 +168 -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 +31 -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 +280 -0
  168. package/src/lib/keymapping.ts +87 -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 +1676 -0
  187. package/src/lib/stdin-parser.ts +1248 -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 +331 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +270 -0
  197. package/src/lib/tree-sitter/client.test.ts +1061 -0
  198. package/src/lib/tree-sitter/client.ts +615 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +80 -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 +1001 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +75 -0
  204. package/src/lib/tree-sitter/resolve-ft.ts +62 -0
  205. package/src/lib/tree-sitter/types.ts +81 -0
  206. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  207. package/src/lib/tree-sitter-styled-text.ts +306 -0
  208. package/src/lib/validate-dir-name.ts +55 -0
  209. package/src/lib/yoga.options.test.ts +628 -0
  210. package/src/lib/yoga.options.ts +346 -0
  211. package/src/plugins/core-slot.ts +579 -0
  212. package/src/plugins/registry.ts +377 -0
  213. package/src/plugins/types.ts +46 -0
  214. package/src/post/filters.ts +888 -0
  215. package/src/renderables/ASCIIFont.ts +219 -0
  216. package/src/renderables/Box.test.ts +160 -0
  217. package/src/renderables/Box.ts +295 -0
  218. package/src/renderables/Code.test.ts +2062 -0
  219. package/src/renderables/Code.ts +357 -0
  220. package/src/renderables/Diff.regression.test.ts +226 -0
  221. package/src/renderables/Diff.test.ts +3027 -0
  222. package/src/renderables/Diff.ts +1209 -0
  223. package/src/renderables/EditBufferRenderable.ts +764 -0
  224. package/src/renderables/FrameBuffer.ts +47 -0
  225. package/src/renderables/Input.test.ts +1228 -0
  226. package/src/renderables/Input.ts +245 -0
  227. package/src/renderables/LineNumberRenderable.ts +675 -0
  228. package/src/renderables/Markdown.ts +1106 -0
  229. package/src/renderables/ScrollBar.ts +422 -0
  230. package/src/renderables/ScrollBox.ts +883 -0
  231. package/src/renderables/Select.test.ts +1010 -0
  232. package/src/renderables/Select.ts +523 -0
  233. package/src/renderables/Slider.test.ts +456 -0
  234. package/src/renderables/Slider.ts +347 -0
  235. package/src/renderables/TabSelect.test.ts +197 -0
  236. package/src/renderables/TabSelect.ts +455 -0
  237. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  238. package/src/renderables/Text.test.ts +2660 -0
  239. package/src/renderables/Text.ts +147 -0
  240. package/src/renderables/TextBufferRenderable.ts +518 -0
  241. package/src/renderables/TextNode.test.ts +1058 -0
  242. package/src/renderables/TextNode.ts +325 -0
  243. package/src/renderables/TextTable.test.ts +1421 -0
  244. package/src/renderables/TextTable.ts +1344 -0
  245. package/src/renderables/Textarea.ts +732 -0
  246. package/src/renderables/TimeToFirstDraw.ts +89 -0
  247. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  248. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  249. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  250. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  251. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  252. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  253. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1787 -0
  254. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  255. package/src/renderables/__tests__/Markdown.test.ts +2287 -0
  256. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  257. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  258. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  259. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  260. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  261. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  262. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  263. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  264. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  265. package/src/renderables/__tests__/Textarea.rendering.test.ts +1864 -0
  266. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  267. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  268. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  269. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  270. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  271. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  272. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  273. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  274. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  275. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  276. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  277. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  278. package/src/renderables/composition/README.md +8 -0
  279. package/src/renderables/composition/VRenderable.ts +32 -0
  280. package/src/renderables/composition/constructs.ts +127 -0
  281. package/src/renderables/composition/vnode.ts +289 -0
  282. package/src/renderables/index.ts +22 -0
  283. package/src/renderables/markdown-parser.ts +66 -0
  284. package/src/renderer.ts +2363 -0
  285. package/src/runtime-plugin-support.ts +39 -0
  286. package/src/runtime-plugin.ts +144 -0
  287. package/src/syntax-style.test.ts +841 -0
  288. package/src/syntax-style.ts +264 -0
  289. package/src/testing/README.md +210 -0
  290. package/src/testing/capture-spans.test.ts +194 -0
  291. package/src/testing/integration.test.ts +276 -0
  292. package/src/testing/manual-clock.ts +106 -0
  293. package/src/testing/mock-keys.test.ts +1356 -0
  294. package/src/testing/mock-keys.ts +449 -0
  295. package/src/testing/mock-mouse.test.ts +218 -0
  296. package/src/testing/mock-mouse.ts +247 -0
  297. package/src/testing/mock-tree-sitter-client.ts +73 -0
  298. package/src/testing/spy.ts +13 -0
  299. package/src/testing/test-recorder.test.ts +415 -0
  300. package/src/testing/test-recorder.ts +145 -0
  301. package/src/testing/test-renderer.ts +116 -0
  302. package/src/testing.ts +7 -0
  303. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  304. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  305. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  306. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  307. package/src/tests/allocator-stats.test.ts +38 -0
  308. package/src/tests/destroy-during-render.test.ts +200 -0
  309. package/src/tests/hover-cursor.test.ts +98 -0
  310. package/src/tests/native-span-feed-async.test.ts +173 -0
  311. package/src/tests/native-span-feed-close.test.ts +120 -0
  312. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  313. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  314. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  315. package/src/tests/opacity.test.ts +123 -0
  316. package/src/tests/renderable.snapshot.test.ts +524 -0
  317. package/src/tests/renderable.test.ts +1281 -0
  318. package/src/tests/renderer.console-startup.test.ts +65 -0
  319. package/src/tests/renderer.control.test.ts +364 -0
  320. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  321. package/src/tests/renderer.cursor.test.ts +26 -0
  322. package/src/tests/renderer.destroy-during-render.test.ts +110 -0
  323. package/src/tests/renderer.focus-restore.test.ts +228 -0
  324. package/src/tests/renderer.focus.test.ts +251 -0
  325. package/src/tests/renderer.idle.test.ts +219 -0
  326. package/src/tests/renderer.input.test.ts +2145 -0
  327. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  328. package/src/tests/renderer.mouse.test.ts +1269 -0
  329. package/src/tests/renderer.palette.test.ts +629 -0
  330. package/src/tests/renderer.selection.test.ts +49 -0
  331. package/src/tests/renderer.slot-registry.test.ts +649 -0
  332. package/src/tests/renderer.useMouse.test.ts +50 -0
  333. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  334. package/src/tests/runtime-plugin-support.test.ts +28 -0
  335. package/src/tests/runtime-plugin.fixture.ts +40 -0
  336. package/src/tests/runtime-plugin.test.ts +190 -0
  337. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  338. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  339. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  340. package/src/tests/scrollbox.test.ts +1530 -0
  341. package/src/tests/wrap-resize-perf.test.ts +229 -0
  342. package/src/tests/yoga-setters.test.ts +921 -0
  343. package/src/text-buffer-view.test.ts +705 -0
  344. package/src/text-buffer-view.ts +189 -0
  345. package/src/text-buffer.test.ts +347 -0
  346. package/src/text-buffer.ts +250 -0
  347. package/src/types.ts +152 -0
  348. package/src/utils.ts +88 -0
  349. package/src/zig/ansi.zig +268 -0
  350. package/src/zig/bench/README.md +50 -0
  351. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  352. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  353. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  354. package/src/zig/bench/rope-markers_bench.zig +713 -0
  355. package/src/zig/bench/rope_bench.zig +514 -0
  356. package/src/zig/bench/styled-text_bench.zig +470 -0
  357. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  358. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  359. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  360. package/src/zig/bench/utf8_bench.zig +799 -0
  361. package/src/zig/bench-utils.zig +431 -0
  362. package/src/zig/bench.zig +217 -0
  363. package/src/zig/buffer.zig +2223 -0
  364. package/src/zig/build.zig +289 -0
  365. package/src/zig/build.zig.zon +16 -0
  366. package/src/zig/edit-buffer.zig +825 -0
  367. package/src/zig/editor-view.zig +802 -0
  368. package/src/zig/event-bus.zig +13 -0
  369. package/src/zig/event-emitter.zig +65 -0
  370. package/src/zig/file-logger.zig +92 -0
  371. package/src/zig/grapheme.zig +599 -0
  372. package/src/zig/lib.zig +1834 -0
  373. package/src/zig/link.zig +333 -0
  374. package/src/zig/logger.zig +43 -0
  375. package/src/zig/mem-registry.zig +125 -0
  376. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  377. package/src/zig/native-span-feed.zig +708 -0
  378. package/src/zig/renderer.zig +1386 -0
  379. package/src/zig/rope.zig +1220 -0
  380. package/src/zig/syntax-style.zig +161 -0
  381. package/src/zig/terminal.zig +975 -0
  382. package/src/zig/test.zig +70 -0
  383. package/src/zig/tests/README.md +18 -0
  384. package/src/zig/tests/buffer_test.zig +2526 -0
  385. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  386. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  387. package/src/zig/tests/editor-view_test.zig +3299 -0
  388. package/src/zig/tests/event-emitter_test.zig +249 -0
  389. package/src/zig/tests/grapheme_test.zig +1304 -0
  390. package/src/zig/tests/link_test.zig +190 -0
  391. package/src/zig/tests/mem-registry_test.zig +473 -0
  392. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  393. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  394. package/src/zig/tests/renderer_test.zig +1010 -0
  395. package/src/zig/tests/rope-nested_test.zig +712 -0
  396. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  397. package/src/zig/tests/rope_test.zig +2362 -0
  398. package/src/zig/tests/segment-merge.test.zig +148 -0
  399. package/src/zig/tests/syntax-style_test.zig +557 -0
  400. package/src/zig/tests/terminal_test.zig +719 -0
  401. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  402. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  403. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  404. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  405. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  406. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  407. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  408. package/src/zig/tests/text-buffer_test.zig +2191 -0
  409. package/src/zig/tests/unicode-width-map.zon +3909 -0
  410. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  411. package/src/zig/tests/utf8_test.zig +4057 -0
  412. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  413. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  414. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  415. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  416. package/src/zig/text-buffer-iterators.zig +499 -0
  417. package/src/zig/text-buffer-segment.zig +404 -0
  418. package/src/zig/text-buffer-view.zig +1371 -0
  419. package/src/zig/text-buffer.zig +1180 -0
  420. package/src/zig/utf8.zig +1948 -0
  421. package/src/zig/utils.zig +9 -0
  422. package/src/zig-structs.ts +261 -0
  423. package/src/zig.ts +3843 -0
  424. package/tsconfig.build.json +22 -0
  425. package/tsconfig.json +28 -0
  426. package/3d/SpriteResourceManager.d.ts +0 -74
  427. package/3d/SpriteUtils.d.ts +0 -13
  428. package/3d/TextureUtils.d.ts +0 -24
  429. package/3d/ThreeRenderable.d.ts +0 -40
  430. package/3d/WGPURenderer.d.ts +0 -61
  431. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  432. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  433. package/3d/animation/SpriteAnimator.d.ts +0 -124
  434. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  435. package/3d/canvas.d.ts +0 -44
  436. package/3d/index.d.ts +0 -12
  437. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  438. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  439. package/3d/physics/physics-interface.d.ts +0 -27
  440. package/3d.d.ts +0 -2
  441. package/3d.js +0 -34042
  442. package/3d.js.map +0 -155
  443. package/LICENSE +0 -21
  444. package/NativeSpanFeed.d.ts +0 -41
  445. package/Renderable.d.ts +0 -334
  446. package/animation/Timeline.d.ts +0 -126
  447. package/ansi.d.ts +0 -13
  448. package/buffer.d.ts +0 -107
  449. package/console.d.ts +0 -143
  450. package/edit-buffer.d.ts +0 -98
  451. package/editor-view.d.ts +0 -73
  452. package/index-e4hzc2j2.js +0 -113
  453. package/index-e4hzc2j2.js.map +0 -10
  454. package/index-nkrr8a4c.js +0 -18415
  455. package/index-nkrr8a4c.js.map +0 -64
  456. package/index-nyw5p3ep.js +0 -12619
  457. package/index-nyw5p3ep.js.map +0 -43
  458. package/index.d.ts +0 -21
  459. package/index.js +0 -430
  460. package/index.js.map +0 -9
  461. package/lib/KeyHandler.d.ts +0 -61
  462. package/lib/RGBA.d.ts +0 -25
  463. package/lib/ascii.font.d.ts +0 -508
  464. package/lib/border.d.ts +0 -49
  465. package/lib/bunfs.d.ts +0 -7
  466. package/lib/clipboard.d.ts +0 -17
  467. package/lib/clock.d.ts +0 -15
  468. package/lib/data-paths.d.ts +0 -26
  469. package/lib/debounce.d.ts +0 -42
  470. package/lib/detect-links.d.ts +0 -6
  471. package/lib/env.d.ts +0 -42
  472. package/lib/extmarks-history.d.ts +0 -17
  473. package/lib/extmarks.d.ts +0 -89
  474. package/lib/hast-styled-text.d.ts +0 -17
  475. package/lib/index.d.ts +0 -21
  476. package/lib/keymapping.d.ts +0 -25
  477. package/lib/objects-in-viewport.d.ts +0 -24
  478. package/lib/output.capture.d.ts +0 -24
  479. package/lib/parse.keypress-kitty.d.ts +0 -2
  480. package/lib/parse.keypress.d.ts +0 -26
  481. package/lib/parse.mouse.d.ts +0 -30
  482. package/lib/paste.d.ts +0 -7
  483. package/lib/queue.d.ts +0 -15
  484. package/lib/renderable.validations.d.ts +0 -12
  485. package/lib/scroll-acceleration.d.ts +0 -43
  486. package/lib/selection.d.ts +0 -63
  487. package/lib/singleton.d.ts +0 -7
  488. package/lib/stdin-parser.d.ts +0 -76
  489. package/lib/styled-text.d.ts +0 -63
  490. package/lib/terminal-capability-detection.d.ts +0 -30
  491. package/lib/terminal-palette.d.ts +0 -50
  492. package/lib/tree-sitter/assets/update.d.ts +0 -11
  493. package/lib/tree-sitter/client.d.ts +0 -47
  494. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  495. package/lib/tree-sitter/download-utils.d.ts +0 -21
  496. package/lib/tree-sitter/index.d.ts +0 -8
  497. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  498. package/lib/tree-sitter/parsers-config.d.ts +0 -38
  499. package/lib/tree-sitter/resolve-ft.d.ts +0 -2
  500. package/lib/tree-sitter/types.d.ts +0 -81
  501. package/lib/tree-sitter-styled-text.d.ts +0 -14
  502. package/lib/validate-dir-name.d.ts +0 -1
  503. package/lib/yoga.options.d.ts +0 -32
  504. package/parser.worker.js +0 -869
  505. package/parser.worker.js.map +0 -12
  506. package/plugins/core-slot.d.ts +0 -72
  507. package/plugins/registry.d.ts +0 -38
  508. package/plugins/types.d.ts +0 -34
  509. package/post/filters.d.ts +0 -105
  510. package/renderables/ASCIIFont.d.ts +0 -52
  511. package/renderables/Box.d.ts +0 -72
  512. package/renderables/Code.d.ts +0 -78
  513. package/renderables/Diff.d.ts +0 -142
  514. package/renderables/EditBufferRenderable.d.ts +0 -162
  515. package/renderables/FrameBuffer.d.ts +0 -16
  516. package/renderables/Input.d.ts +0 -67
  517. package/renderables/LineNumberRenderable.d.ts +0 -74
  518. package/renderables/Markdown.d.ts +0 -173
  519. package/renderables/ScrollBar.d.ts +0 -77
  520. package/renderables/ScrollBox.d.ts +0 -124
  521. package/renderables/Select.d.ts +0 -115
  522. package/renderables/Slider.d.ts +0 -44
  523. package/renderables/TabSelect.d.ts +0 -96
  524. package/renderables/Text.d.ts +0 -36
  525. package/renderables/TextBufferRenderable.d.ts +0 -105
  526. package/renderables/TextNode.d.ts +0 -91
  527. package/renderables/TextTable.d.ts +0 -140
  528. package/renderables/Textarea.d.ts +0 -114
  529. package/renderables/TimeToFirstDraw.d.ts +0 -24
  530. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  531. package/renderables/composition/VRenderable.d.ts +0 -16
  532. package/renderables/composition/constructs.d.ts +0 -35
  533. package/renderables/composition/vnode.d.ts +0 -46
  534. package/renderables/index.d.ts +0 -22
  535. package/renderables/markdown-parser.d.ts +0 -10
  536. package/renderer.d.ts +0 -388
  537. package/runtime-plugin-support.d.ts +0 -3
  538. package/runtime-plugin-support.js +0 -29
  539. package/runtime-plugin-support.js.map +0 -10
  540. package/runtime-plugin.d.ts +0 -11
  541. package/runtime-plugin.js +0 -16
  542. package/runtime-plugin.js.map +0 -9
  543. package/syntax-style.d.ts +0 -54
  544. package/testing/manual-clock.d.ts +0 -16
  545. package/testing/mock-keys.d.ts +0 -81
  546. package/testing/mock-mouse.d.ts +0 -38
  547. package/testing/mock-tree-sitter-client.d.ts +0 -23
  548. package/testing/spy.d.ts +0 -7
  549. package/testing/test-recorder.d.ts +0 -61
  550. package/testing/test-renderer.d.ts +0 -23
  551. package/testing.d.ts +0 -6
  552. package/testing.js +0 -675
  553. package/testing.js.map +0 -15
  554. package/text-buffer-view.d.ts +0 -42
  555. package/text-buffer.d.ts +0 -67
  556. package/types.d.ts +0 -131
  557. package/utils.d.ts +0 -14
  558. package/zig-structs.d.ts +0 -155
  559. package/zig.d.ts +0 -351
  560. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  561. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  562. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  563. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  564. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  565. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  566. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  567. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  568. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  569. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  570. /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,1248 @@
1
+ // Byte-level stdin parser that turns raw terminal input into typed StdinEvents.
2
+ //
3
+ // This replaces a two-phase token -> decode pipeline with a single state machine
4
+ // that produces fully typed events (key, mouse, paste, response) directly from
5
+ // bytes. The parser owns all byte framing and protocol recognition. It does NOT
6
+ // own event dispatch — that belongs to KeyHandler and the renderer.
7
+
8
+ import { Buffer } from "node:buffer"
9
+ import { SystemClock, type Clock, type TimerHandle } from "./clock"
10
+ import { parseKeypress, type ParsedKey } from "./parse.keypress"
11
+ import { MouseParser, type RawMouseEvent } from "./parse.mouse"
12
+ import type { PasteMetadata } from "./paste"
13
+
14
+ export { SystemClock, type Clock, type TimerHandle } from "./clock"
15
+
16
+ export type StdinResponseProtocol = "csi" | "osc" | "dcs" | "apc" | "unknown"
17
+
18
+ // The four event types the parser produces. Everything stdin sends becomes
19
+ // exactly one of these.
20
+ export type StdinEvent =
21
+ | {
22
+ type: "key"
23
+ raw: string
24
+ key: ParsedKey
25
+ }
26
+ | {
27
+ type: "mouse"
28
+ raw: string
29
+ encoding: "sgr" | "x10"
30
+ event: RawMouseEvent
31
+ }
32
+ | {
33
+ type: "paste"
34
+ bytes: Uint8Array
35
+ metadata?: PasteMetadata
36
+ }
37
+ | {
38
+ type: "response"
39
+ protocol: StdinResponseProtocol
40
+ sequence: string
41
+ }
42
+
43
+ export interface StdinParserOptions {
44
+ timeoutMs?: number
45
+ maxPendingBytes?: number
46
+ armTimeouts?: boolean
47
+ onTimeoutFlush?: () => void
48
+ useKittyKeyboard?: boolean
49
+ clock?: Clock
50
+ }
51
+
52
+ // State machine tags for the byte scanner. Each tag represents which protocol
53
+ // framing mode the parser is currently inside. The sawEsc flag in osc/dcs/apc
54
+ // tracks whether the previous byte was ESC, since the two-byte ST terminator
55
+ // (ESC \) can split across push() calls.
56
+ type ParserState =
57
+ | { tag: "ground" }
58
+ | { tag: "utf8"; expected: number; seen: number }
59
+ | { tag: "esc" }
60
+ | { tag: "ss3" }
61
+ | { tag: "csi" }
62
+ | { tag: "osc"; sawEsc: boolean }
63
+ | { tag: "dcs"; sawEsc: boolean }
64
+ | { tag: "apc"; sawEsc: boolean }
65
+ | { tag: "esc_recovery" }
66
+ | { tag: "esc_less_mouse" }
67
+ | { tag: "esc_less_x10_mouse" }
68
+
69
+ // Collects paste body incrementally, bypassing the main ByteQueue so large
70
+ // pastes don't grow the parser buffer. Keeps only a small tail for end-marker
71
+ // detection across chunk boundaries.
72
+ interface PasteCollector {
73
+ tail: Uint8Array
74
+ parts: Uint8Array[]
75
+ totalLength: number
76
+ }
77
+
78
+ // 10ms is enough to distinguish a lone ESC keypress from the start of an
79
+ // escape sequence on all but the slowest connections.
80
+ const DEFAULT_TIMEOUT_MS = 10
81
+ const DEFAULT_MAX_PENDING_BYTES = 64 * 1024
82
+ const INITIAL_PENDING_CAPACITY = 256
83
+ const ESC = 0x1b
84
+ const BEL = 0x07
85
+ const BRACKETED_PASTE_START = Buffer.from("\x1b[200~")
86
+ const BRACKETED_PASTE_END = Buffer.from("\x1b[201~")
87
+ const EMPTY_BYTES = new Uint8Array(0)
88
+ const KEY_DECODER = new TextDecoder()
89
+ // rxvt uses $-terminated CSI sequences for shifted function keys (e.g. ESC[2$).
90
+ // Standard CSI treats $ as an intermediate byte, not a final, so we match these
91
+ // explicitly to avoid waiting for a "real" final byte that never arrives.
92
+ const RXVT_DOLLAR_CSI_RE = /^\x1b\[\d+\$$/
93
+
94
+ const SYSTEM_CLOCK = new SystemClock()
95
+
96
+ // Byte buffer for pending input. Uses start/end offsets so consume() just
97
+ // advances the start pointer without copying. Compacts (via copyWithin) only
98
+ // when the consumed prefix exceeds half the buffer, keeping amortized cost low.
99
+ class ByteQueue {
100
+ private buf: Uint8Array
101
+ private start = 0
102
+ private end = 0
103
+
104
+ constructor(capacity = INITIAL_PENDING_CAPACITY) {
105
+ this.buf = new Uint8Array(capacity)
106
+ }
107
+
108
+ get length(): number {
109
+ return this.end - this.start
110
+ }
111
+
112
+ get capacity(): number {
113
+ return this.buf.length
114
+ }
115
+
116
+ view(): Uint8Array {
117
+ return this.buf.subarray(this.start, this.end)
118
+ }
119
+
120
+ // Returns a view of the contents and resets the queue. The view shares
121
+ // the underlying buffer, so it becomes invalid on the next append().
122
+ take(): Uint8Array {
123
+ const chunk = this.view()
124
+ this.start = 0
125
+ this.end = 0
126
+ return chunk
127
+ }
128
+
129
+ append(chunk: Uint8Array): void {
130
+ if (chunk.length === 0) {
131
+ return
132
+ }
133
+
134
+ this.ensureCapacity(this.length + chunk.length)
135
+ this.buf.set(chunk, this.end)
136
+ this.end += chunk.length
137
+ }
138
+
139
+ // Drops the first `count` bytes. Compacts when the consumed prefix
140
+ // exceeds half the buffer to reclaim wasted space at the front.
141
+ consume(count: number): void {
142
+ if (count <= 0) {
143
+ return
144
+ }
145
+
146
+ if (count >= this.length) {
147
+ this.start = 0
148
+ this.end = 0
149
+ return
150
+ }
151
+
152
+ this.start += count
153
+ if (this.start >= this.buf.length / 2) {
154
+ this.buf.copyWithin(0, this.start, this.end)
155
+ this.end -= this.start
156
+ this.start = 0
157
+ }
158
+ }
159
+
160
+ clear(): void {
161
+ this.start = 0
162
+ this.end = 0
163
+ }
164
+
165
+ reset(capacity = INITIAL_PENDING_CAPACITY): void {
166
+ this.buf = new Uint8Array(capacity)
167
+ this.start = 0
168
+ this.end = 0
169
+ }
170
+
171
+ // Tries reclaiming space by compacting data to the front first.
172
+ // Doubles the allocation if that still isn't enough.
173
+ private ensureCapacity(requiredLength: number): void {
174
+ const currentLength = this.length
175
+ if (requiredLength <= this.buf.length) {
176
+ const availableAtEnd = this.buf.length - this.end
177
+ if (availableAtEnd >= requiredLength - currentLength) {
178
+ return
179
+ }
180
+
181
+ this.buf.copyWithin(0, this.start, this.end)
182
+ this.end = currentLength
183
+ this.start = 0
184
+ if (requiredLength <= this.buf.length) {
185
+ return
186
+ }
187
+ }
188
+
189
+ let nextCapacity = this.buf.length
190
+ while (nextCapacity < requiredLength) {
191
+ nextCapacity *= 2
192
+ }
193
+
194
+ const next = new Uint8Array(nextCapacity)
195
+ next.set(this.view(), 0)
196
+ this.buf = next
197
+ this.start = 0
198
+ this.end = currentLength
199
+ }
200
+ }
201
+
202
+ function normalizePositiveOption(value: number | undefined, fallback: number): number {
203
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
204
+ return fallback
205
+ }
206
+
207
+ return Math.floor(value)
208
+ }
209
+
210
+ // Returns the expected byte count for a UTF-8 sequence given its lead byte,
211
+ // or 0 for bytes that aren't valid UTF-8 leads. Returning 0 tells the parser
212
+ // this is a legacy high-byte character (0x80–0xBF, 0xC0–0xC1, 0xF5+) that
213
+ // goes through the parseKeypress() meta-key path instead.
214
+ function utf8SequenceLength(first: number): number {
215
+ if (first < 0x80) return 1
216
+ if (first >= 0xc2 && first <= 0xdf) return 2
217
+ if (first >= 0xe0 && first <= 0xef) return 3
218
+ if (first >= 0xf0 && first <= 0xf4) return 4
219
+ return 0
220
+ }
221
+
222
+ function bytesEqual(left: Uint8Array, right: Uint8Array): boolean {
223
+ if (left.length !== right.length) {
224
+ return false
225
+ }
226
+
227
+ for (let index = 0; index < left.length; index += 1) {
228
+ if (left[index] !== right[index]) {
229
+ return false
230
+ }
231
+ }
232
+
233
+ return true
234
+ }
235
+
236
+ // Checks whether a byte sequence is a complete SGR mouse report:
237
+ // ESC [ < Ps ; Ps ; Ps M/m (three semicolon-separated digit groups).
238
+ function isMouseSgrSequence(sequence: Uint8Array): boolean {
239
+ if (sequence.length < 7) {
240
+ return false
241
+ }
242
+
243
+ if (sequence[0] !== ESC || sequence[1] !== 0x5b || sequence[2] !== 0x3c) {
244
+ return false
245
+ }
246
+
247
+ const final = sequence[sequence.length - 1]
248
+ if (final !== 0x4d && final !== 0x6d) {
249
+ return false
250
+ }
251
+
252
+ let part = 0
253
+ let hasDigit = false
254
+ for (let index = 3; index < sequence.length - 1; index += 1) {
255
+ const byte = sequence[index]!
256
+ if (byte >= 0x30 && byte <= 0x39) {
257
+ hasDigit = true
258
+ continue
259
+ }
260
+
261
+ if (byte === 0x3b && hasDigit && part < 2) {
262
+ part += 1
263
+ hasDigit = false
264
+ continue
265
+ }
266
+
267
+ return false
268
+ }
269
+
270
+ return part === 2 && hasDigit
271
+ }
272
+
273
+ function concatBytes(left: Uint8Array, right: Uint8Array): Uint8Array {
274
+ if (left.length === 0) {
275
+ return right
276
+ }
277
+
278
+ if (right.length === 0) {
279
+ return left
280
+ }
281
+
282
+ const combined = new Uint8Array(left.length + right.length)
283
+ combined.set(left, 0)
284
+ combined.set(right, left.length)
285
+ return combined
286
+ }
287
+
288
+ function indexOfBytes(haystack: Uint8Array, needle: Uint8Array): number {
289
+ if (needle.length === 0) {
290
+ return 0
291
+ }
292
+
293
+ const limit = haystack.length - needle.length
294
+ for (let offset = 0; offset <= limit; offset += 1) {
295
+ let matched = true
296
+ for (let index = 0; index < needle.length; index += 1) {
297
+ if (haystack[offset + index] !== needle[index]) {
298
+ matched = false
299
+ break
300
+ }
301
+ }
302
+
303
+ if (matched) {
304
+ return offset
305
+ }
306
+ }
307
+
308
+ return -1
309
+ }
310
+
311
+ // Decodes raw protocol bytes as latin1. Used for mouse and response events
312
+ // where the wire bytes may not be valid UTF-8 but need a lossless string
313
+ // form for downstream sequence handlers.
314
+ function decodeLatin1(bytes: Uint8Array): string {
315
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("latin1")
316
+ }
317
+
318
+ function decodeUtf8(bytes: Uint8Array): string {
319
+ return KEY_DECODER.decode(bytes)
320
+ }
321
+
322
+ function createPasteCollector(): PasteCollector {
323
+ return {
324
+ tail: EMPTY_BYTES,
325
+ parts: [],
326
+ totalLength: 0,
327
+ }
328
+ }
329
+
330
+ function joinPasteBytes(parts: Uint8Array[], totalLength: number): Uint8Array {
331
+ if (totalLength === 0) {
332
+ return EMPTY_BYTES
333
+ }
334
+
335
+ if (parts.length === 1) {
336
+ return parts[0]!
337
+ }
338
+
339
+ const bytes = new Uint8Array(totalLength)
340
+ let offset = 0
341
+ for (const part of parts) {
342
+ bytes.set(part, offset)
343
+ offset += part.length
344
+ }
345
+
346
+ return bytes
347
+ }
348
+
349
+ // Push-driven stdin parser. Callers feed raw bytes via push(), then read
350
+ // typed events via read() or drain(). At most one incomplete protocol unit
351
+ // is buffered at a time; everything else is immediately converted to events.
352
+ //
353
+ // The parser guarantees chunk-shape invariance: the same bytes always produce
354
+ // the same events, regardless of chunk boundaries. A lone ESC resolves via
355
+ // timeout, split UTF-8 codepoints reassemble correctly, and bracketed paste
356
+ // markers may split across any chunk boundary.
357
+ export class StdinParser {
358
+ private readonly pending = new ByteQueue(INITIAL_PENDING_CAPACITY)
359
+ private readonly events: StdinEvent[] = []
360
+ private readonly timeoutMs: number
361
+ private readonly maxPendingBytes: number
362
+ private readonly armTimeouts: boolean
363
+ private readonly onTimeoutFlush: (() => void) | null
364
+ private readonly useKittyKeyboard: boolean
365
+ private readonly mouseParser = new MouseParser()
366
+ private readonly clock: Clock
367
+ private timeoutId: TimerHandle | null = null
368
+ private destroyed = false
369
+ // When the current incomplete unit first appeared. Null when nothing is pending.
370
+ private pendingSinceMs: number | null = null
371
+ // When true, the state machine treats the current incomplete prefix as
372
+ // final and emits it as one atomic event (e.g. a lone ESC becomes an
373
+ // Escape key). Set by the timeout, consumed by the next read() or drain().
374
+ private forceFlush = false
375
+ // True only immediately after a timeout flush emits a lone ESC key. The next
376
+ // `[` may begin a delayed `[<...M/m` mouse continuation recovery path.
377
+ private justFlushedEsc = false
378
+ private state: ParserState = { tag: "ground" }
379
+ // Scan position within pending.view() during scanPending().
380
+ private cursor = 0
381
+ // Start of the protocol unit currently being parsed. The bytes from
382
+ // unitStart through cursor all belong to one atomic unit.
383
+ private unitStart = 0
384
+ // When non-null, the parser is inside a bracketed paste. All incoming
385
+ // bytes flow through consumePasteBytes() instead of the normal state machine.
386
+ private paste: PasteCollector | null = null
387
+
388
+ constructor(options: StdinParserOptions = {}) {
389
+ this.timeoutMs = normalizePositiveOption(options.timeoutMs, DEFAULT_TIMEOUT_MS)
390
+ this.maxPendingBytes = normalizePositiveOption(options.maxPendingBytes, DEFAULT_MAX_PENDING_BYTES)
391
+ this.armTimeouts = options.armTimeouts ?? true
392
+ this.onTimeoutFlush = options.onTimeoutFlush ?? null
393
+ this.useKittyKeyboard = options.useKittyKeyboard ?? true
394
+ this.clock = options.clock ?? SYSTEM_CLOCK
395
+ }
396
+
397
+ public get bufferCapacity(): number {
398
+ return this.pending.capacity
399
+ }
400
+
401
+ // Feeds raw stdin bytes into the parser. Converts as much as possible into
402
+ // queued events and leaves at most one incomplete unit behind in pending.
403
+ //
404
+ // When a chunk contains a paste start marker, bytes before the marker go
405
+ // through normal parsing, then paste mode takes over for the rest. This
406
+ // prevents large pastes from growing the main buffer.
407
+ public push(data: Uint8Array): void {
408
+ this.ensureAlive()
409
+ if (data.length === 0) {
410
+ // Preserve the existing empty-chunk -> empty-keypress behavior.
411
+ this.emitKeyOrResponse("unknown", "")
412
+ return
413
+ }
414
+
415
+ let remainder = data
416
+ while (remainder.length > 0) {
417
+ if (this.paste) {
418
+ remainder = this.consumePasteBytes(remainder)
419
+ continue
420
+ }
421
+
422
+ // If we're in ground state with nothing pending, scan the incoming
423
+ // chunk for a paste start marker. Only append through the marker so
424
+ // scanPending() enters paste mode without buffering the full paste.
425
+ const immediatePasteStartIndex =
426
+ this.state.tag === "ground" && this.pending.length === 0 ? indexOfBytes(remainder, BRACKETED_PASTE_START) : -1
427
+ const appendEnd =
428
+ immediatePasteStartIndex === -1 ? remainder.length : immediatePasteStartIndex + BRACKETED_PASTE_START.length
429
+
430
+ this.pending.append(remainder.subarray(0, appendEnd))
431
+ remainder = remainder.subarray(appendEnd)
432
+ this.scanPending()
433
+
434
+ if (this.paste && this.pending.length > 0) {
435
+ remainder = this.consumePasteBytes(this.takePendingBytes())
436
+ continue
437
+ }
438
+
439
+ if (!this.paste && this.pending.length > this.maxPendingBytes) {
440
+ this.flushPendingOverflow()
441
+ this.scanPending()
442
+
443
+ if (this.paste && this.pending.length > 0) {
444
+ remainder = this.consumePasteBytes(this.takePendingBytes())
445
+ }
446
+ }
447
+ }
448
+
449
+ this.reconcileTimeoutState()
450
+ }
451
+
452
+ // Pops one event from the queue. If the queue is empty and a timeout has
453
+ // set forceFlush, re-scans pending to convert the timed-out incomplete
454
+ // unit into one final event before returning it.
455
+ public read(): StdinEvent | null {
456
+ this.ensureAlive()
457
+
458
+ if (this.events.length === 0 && this.forceFlush) {
459
+ this.scanPending()
460
+ this.reconcileTimeoutState()
461
+ }
462
+
463
+ return this.events.shift() ?? null
464
+ }
465
+
466
+ // Delivers all queued events. Stops early if the parser is destroyed
467
+ // during a callback (e.g. an event handler triggers teardown).
468
+ public drain(onEvent: (event: StdinEvent) => void): void {
469
+ this.ensureAlive()
470
+
471
+ while (true) {
472
+ if (this.destroyed) {
473
+ return
474
+ }
475
+
476
+ const event = this.read()
477
+ if (!event) {
478
+ return
479
+ }
480
+
481
+ onEvent(event)
482
+ }
483
+ }
484
+
485
+ // Marks the parser for forced flush if enough time has passed since
486
+ // incomplete data arrived. Does not immediately emit events — the next
487
+ // read() or drain() does the actual flush. This separation keeps the
488
+ // timer callback from emitting events mid-flight in user code.
489
+ public flushTimeout(nowMsValue: number = this.clock.now()): void {
490
+ this.ensureAlive()
491
+
492
+ if (this.paste || this.pendingSinceMs === null || this.pending.length === 0) {
493
+ return
494
+ }
495
+
496
+ if (nowMsValue < this.pendingSinceMs || nowMsValue - this.pendingSinceMs < this.timeoutMs) {
497
+ return
498
+ }
499
+
500
+ this.forceFlush = true
501
+ }
502
+
503
+ public reset(): void {
504
+ if (this.destroyed) {
505
+ return
506
+ }
507
+
508
+ this.clearTimeout()
509
+ this.resetState()
510
+ }
511
+
512
+ public resetMouseState(): void {
513
+ this.ensureAlive()
514
+ this.mouseParser.reset()
515
+ }
516
+
517
+ public destroy(): void {
518
+ if (this.destroyed) {
519
+ return
520
+ }
521
+
522
+ this.clearTimeout()
523
+ this.destroyed = true
524
+ this.resetState()
525
+ }
526
+
527
+ private ensureAlive(): void {
528
+ if (this.destroyed) {
529
+ throw new Error("StdinParser has been destroyed")
530
+ }
531
+ }
532
+
533
+ // Scans the pending byte buffer one byte at a time, dispatching on the
534
+ // current parser state. All protocol framing lives in this single switch
535
+ // — intentionally not split into per-mode scan helpers.
536
+ //
537
+ // Exits when: all bytes consumed (ground), more bytes needed (incomplete
538
+ // unit), or paste mode entered (body handled by consumePasteBytes).
539
+ private scanPending(): void {
540
+ while (!this.paste) {
541
+ const bytes = this.pending.view()
542
+ if (this.state.tag === "ground" && this.cursor >= bytes.length) {
543
+ this.pending.clear()
544
+ this.cursor = 0
545
+ this.unitStart = 0
546
+ this.pendingSinceMs = null
547
+ this.forceFlush = false
548
+ return
549
+ }
550
+
551
+ const byte = this.cursor < bytes.length ? bytes[this.cursor]! : -1
552
+ switch (this.state.tag) {
553
+ case "ground": {
554
+ this.unitStart = this.cursor
555
+
556
+ // After a timeout-flushed lone ESC, a following `[` may be the start
557
+ // of a delayed `[<...M/m` mouse continuation. Recover only this narrow
558
+ // case; otherwise clear the recovery flag and parse bytes normally.
559
+ if (this.justFlushedEsc) {
560
+ if (byte === 0x5b) {
561
+ this.justFlushedEsc = false
562
+ this.cursor += 1
563
+ this.state = { tag: "esc_recovery" }
564
+ continue
565
+ }
566
+
567
+ this.justFlushedEsc = false
568
+ }
569
+
570
+ if (byte === ESC) {
571
+ this.cursor += 1
572
+ this.state = { tag: "esc" }
573
+ continue
574
+ }
575
+
576
+ if (byte < 0x80) {
577
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.cursor, this.cursor + 1)))
578
+ this.consumePrefix(this.cursor + 1)
579
+ continue
580
+ }
581
+
582
+ // Invalid UTF-8 lead byte. Could be a legacy high-byte from an
583
+ // older terminal. If it's the last byte in the buffer, wait for
584
+ // more data or a timeout before committing. On timeout, emit
585
+ // through parseKeypress() which handles meta-key behavior.
586
+ const expected = utf8SequenceLength(byte)
587
+ if (expected === 0) {
588
+ if (!this.forceFlush && this.cursor + 1 === bytes.length) {
589
+ this.markPending()
590
+ return
591
+ }
592
+
593
+ this.emitLegacyHighByte(byte)
594
+ this.consumePrefix(this.cursor + 1)
595
+ continue
596
+ }
597
+
598
+ this.cursor += 1
599
+ this.state = { tag: "utf8", expected, seen: 1 }
600
+ continue
601
+ }
602
+
603
+ case "utf8": {
604
+ if (this.cursor >= bytes.length) {
605
+ if (!this.forceFlush) {
606
+ this.markPending()
607
+ return
608
+ }
609
+
610
+ this.emitLegacyHighByte(bytes[this.unitStart]!)
611
+ this.state = { tag: "ground" }
612
+ this.consumePrefix(this.unitStart + 1)
613
+ continue
614
+ }
615
+
616
+ // Not a valid continuation byte. Treat the lead byte as a legacy
617
+ // high-byte character and restart parsing from this position.
618
+ if ((byte & 0xc0) !== 0x80) {
619
+ this.emitLegacyHighByte(bytes[this.unitStart]!)
620
+ this.state = { tag: "ground" }
621
+ this.consumePrefix(this.unitStart + 1)
622
+ continue
623
+ }
624
+
625
+ const nextSeen = this.state.seen + 1
626
+ this.cursor += 1
627
+ if (nextSeen < this.state.expected) {
628
+ this.state = { tag: "utf8", expected: this.state.expected, seen: nextSeen }
629
+ continue
630
+ }
631
+
632
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.cursor)))
633
+ this.state = { tag: "ground" }
634
+ this.consumePrefix(this.cursor)
635
+ continue
636
+ }
637
+
638
+ case "esc": {
639
+ if (this.cursor >= bytes.length) {
640
+ if (!this.forceFlush) {
641
+ this.markPending()
642
+ return
643
+ }
644
+
645
+ const flushedLoneEsc = this.cursor === this.unitStart + 1 && bytes[this.unitStart] === ESC
646
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.cursor)))
647
+ this.justFlushedEsc = flushedLoneEsc
648
+ this.state = { tag: "ground" }
649
+ this.consumePrefix(this.cursor)
650
+ continue
651
+ }
652
+
653
+ // The byte after ESC determines the sub-protocol:
654
+ // [ -> CSI, O -> SS3, ] -> OSC, P -> DCS, _ -> APC.
655
+ switch (byte) {
656
+ case 0x5b:
657
+ this.cursor += 1
658
+ this.state = { tag: "csi" }
659
+ continue
660
+ case 0x4f:
661
+ this.cursor += 1
662
+ this.state = { tag: "ss3" }
663
+ continue
664
+ case 0x5d:
665
+ this.cursor += 1
666
+ this.state = { tag: "osc", sawEsc: false }
667
+ continue
668
+ case 0x50:
669
+ this.cursor += 1
670
+ this.state = { tag: "dcs", sawEsc: false }
671
+ continue
672
+ case 0x5f:
673
+ this.cursor += 1
674
+ this.state = { tag: "apc", sawEsc: false }
675
+ continue
676
+ // ESC ESC: stay in esc state. Terminals encode Alt+ESC and
677
+ // similar sequences as ESC ESC [...], so we keep scanning.
678
+ case ESC:
679
+ this.cursor += 1
680
+ continue
681
+ default:
682
+ this.cursor += 1
683
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.cursor)))
684
+ this.state = { tag: "ground" }
685
+ this.consumePrefix(this.cursor)
686
+ continue
687
+ }
688
+ }
689
+
690
+ case "ss3": {
691
+ if (this.cursor >= bytes.length) {
692
+ if (!this.forceFlush) {
693
+ this.markPending()
694
+ return
695
+ }
696
+
697
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
698
+ this.state = { tag: "ground" }
699
+ this.consumePrefix(this.cursor)
700
+ continue
701
+ }
702
+
703
+ if (byte === ESC) {
704
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
705
+ this.state = { tag: "ground" }
706
+ this.consumePrefix(this.cursor)
707
+ continue
708
+ }
709
+
710
+ this.cursor += 1
711
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.cursor)))
712
+ this.state = { tag: "ground" }
713
+ this.consumePrefix(this.cursor)
714
+ continue
715
+ }
716
+
717
+ // Narrow recovery path for delayed mouse continuations after a
718
+ // timeout-flushed lone ESC. Wait for either `<` (SGR) or `M` (X10); if
719
+ // neither arrives, flush `[` as a normal key.
720
+ case "esc_recovery": {
721
+ if (this.cursor >= bytes.length) {
722
+ if (!this.forceFlush) {
723
+ this.markPending()
724
+ return
725
+ }
726
+
727
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.cursor)))
728
+ this.state = { tag: "ground" }
729
+ this.consumePrefix(this.cursor)
730
+ continue
731
+ }
732
+
733
+ if (byte === 0x3c) {
734
+ this.cursor += 1
735
+ this.state = { tag: "esc_less_mouse" }
736
+ continue
737
+ }
738
+
739
+ if (byte === 0x4d) {
740
+ this.cursor += 1
741
+ this.state = { tag: "esc_less_x10_mouse" }
742
+ continue
743
+ }
744
+
745
+ this.emitKeyOrResponse("unknown", decodeUtf8(bytes.subarray(this.unitStart, this.unitStart + 1)))
746
+ this.state = { tag: "ground" }
747
+ this.consumePrefix(this.unitStart + 1)
748
+ continue
749
+ }
750
+
751
+ case "csi": {
752
+ if (this.cursor >= bytes.length) {
753
+ if (!this.forceFlush) {
754
+ this.markPending()
755
+ return
756
+ }
757
+
758
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
759
+ this.state = { tag: "ground" }
760
+ this.consumePrefix(this.cursor)
761
+ continue
762
+ }
763
+
764
+ // A new ESC inside an incomplete CSI means the previous sequence
765
+ // was interrupted. Flush everything before the new ESC as one
766
+ // opaque response, then restart parsing at the new ESC.
767
+ if (byte === ESC) {
768
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
769
+ this.state = { tag: "ground" }
770
+ this.consumePrefix(this.cursor)
771
+ continue
772
+ }
773
+
774
+ // X10 mouse: ESC [ M plus 3 raw payload bytes (button, x, y).
775
+ // cursor === unitStart + 2 confirms M comes right after ESC[,
776
+ // not as a later final byte in a different CSI sequence.
777
+ if (byte === 0x4d && this.cursor === this.unitStart + 2) {
778
+ const end = this.cursor + 4
779
+ if (bytes.length < end) {
780
+ if (!this.forceFlush) {
781
+ this.markPending()
782
+ return
783
+ }
784
+
785
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, bytes.length))
786
+ this.state = { tag: "ground" }
787
+ this.consumePrefix(bytes.length)
788
+ continue
789
+ }
790
+
791
+ this.emitMouse(bytes.subarray(this.unitStart, end), "x10")
792
+ this.state = { tag: "ground" }
793
+ this.consumePrefix(end)
794
+ continue
795
+ }
796
+
797
+ if (byte === 0x24) {
798
+ const candidateEnd = this.cursor + 1
799
+ const candidate = decodeUtf8(bytes.subarray(this.unitStart, candidateEnd))
800
+ if (RXVT_DOLLAR_CSI_RE.test(candidate)) {
801
+ this.emitKeyOrResponse("csi", candidate)
802
+ this.state = { tag: "ground" }
803
+ this.consumePrefix(candidateEnd)
804
+ continue
805
+ }
806
+
807
+ if (!this.forceFlush && candidateEnd >= bytes.length) {
808
+ this.markPending()
809
+ return
810
+ }
811
+ }
812
+
813
+ // Some terminals use ESC [[A..E / ESC [[5~ / ESC [[6~ variants.
814
+ // Treat the second `[` immediately after ESC[ as part of the CSI
815
+ // payload instead of as a final byte so parseKeypress() can match
816
+ // `[[A`, `[[B`, `[[5~`, etc.
817
+ if (byte === 0x5b && this.cursor === this.unitStart + 2) {
818
+ this.cursor += 1
819
+ continue
820
+ }
821
+
822
+ // Standard CSI final byte (0x40–0x7E). Check for bracketed paste
823
+ // start, SGR mouse, or a regular CSI key/response.
824
+ if (byte >= 0x40 && byte <= 0x7e) {
825
+ const end = this.cursor + 1
826
+ const rawBytes = bytes.subarray(this.unitStart, end)
827
+
828
+ if (bytesEqual(rawBytes, BRACKETED_PASTE_START)) {
829
+ this.state = { tag: "ground" }
830
+ this.consumePrefix(end)
831
+ this.paste = createPasteCollector()
832
+ continue
833
+ }
834
+
835
+ if (isMouseSgrSequence(rawBytes)) {
836
+ this.emitMouse(rawBytes, "sgr")
837
+ this.state = { tag: "ground" }
838
+ this.consumePrefix(end)
839
+ continue
840
+ }
841
+
842
+ this.emitKeyOrResponse("csi", decodeUtf8(rawBytes))
843
+ this.state = { tag: "ground" }
844
+ this.consumePrefix(end)
845
+ continue
846
+ }
847
+
848
+ this.cursor += 1
849
+ continue
850
+ }
851
+
852
+ // OSC sequences end at BEL or ESC \. DCS and APC end at ESC \
853
+ // only. The sawEsc flag tracks whether the previous byte was ESC,
854
+ // since the two-byte ESC \ can split across push() calls.
855
+ case "osc": {
856
+ if (this.cursor >= bytes.length) {
857
+ if (!this.forceFlush) {
858
+ this.markPending()
859
+ return
860
+ }
861
+
862
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
863
+ this.state = { tag: "ground" }
864
+ this.consumePrefix(this.cursor)
865
+ continue
866
+ }
867
+
868
+ if (this.state.sawEsc) {
869
+ if (byte === 0x5c) {
870
+ const end = this.cursor + 1
871
+ this.emitOpaqueResponse("osc", bytes.subarray(this.unitStart, end))
872
+ this.state = { tag: "ground" }
873
+ this.consumePrefix(end)
874
+ continue
875
+ }
876
+
877
+ this.state = { tag: "osc", sawEsc: false }
878
+ continue
879
+ }
880
+
881
+ if (byte === BEL) {
882
+ const end = this.cursor + 1
883
+ this.emitOpaqueResponse("osc", bytes.subarray(this.unitStart, end))
884
+ this.state = { tag: "ground" }
885
+ this.consumePrefix(end)
886
+ continue
887
+ }
888
+
889
+ if (byte === ESC) {
890
+ this.cursor += 1
891
+ this.state = { tag: "osc", sawEsc: true }
892
+ continue
893
+ }
894
+
895
+ this.cursor += 1
896
+ continue
897
+ }
898
+
899
+ case "dcs": {
900
+ if (this.cursor >= bytes.length) {
901
+ if (!this.forceFlush) {
902
+ this.markPending()
903
+ return
904
+ }
905
+
906
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
907
+ this.state = { tag: "ground" }
908
+ this.consumePrefix(this.cursor)
909
+ continue
910
+ }
911
+
912
+ if (this.state.sawEsc) {
913
+ if (byte === 0x5c) {
914
+ const end = this.cursor + 1
915
+ this.emitOpaqueResponse("dcs", bytes.subarray(this.unitStart, end))
916
+ this.state = { tag: "ground" }
917
+ this.consumePrefix(end)
918
+ continue
919
+ }
920
+
921
+ this.state = { tag: "dcs", sawEsc: false }
922
+ continue
923
+ }
924
+
925
+ if (byte === ESC) {
926
+ this.cursor += 1
927
+ this.state = { tag: "dcs", sawEsc: true }
928
+ continue
929
+ }
930
+
931
+ this.cursor += 1
932
+ continue
933
+ }
934
+
935
+ case "apc": {
936
+ if (this.cursor >= bytes.length) {
937
+ if (!this.forceFlush) {
938
+ this.markPending()
939
+ return
940
+ }
941
+
942
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
943
+ this.state = { tag: "ground" }
944
+ this.consumePrefix(this.cursor)
945
+ continue
946
+ }
947
+
948
+ if (this.state.sawEsc) {
949
+ if (byte === 0x5c) {
950
+ const end = this.cursor + 1
951
+ this.emitOpaqueResponse("apc", bytes.subarray(this.unitStart, end))
952
+ this.state = { tag: "ground" }
953
+ this.consumePrefix(end)
954
+ continue
955
+ }
956
+
957
+ this.state = { tag: "apc", sawEsc: false }
958
+ continue
959
+ }
960
+
961
+ if (byte === ESC) {
962
+ this.cursor += 1
963
+ this.state = { tag: "apc", sawEsc: true }
964
+ continue
965
+ }
966
+
967
+ this.cursor += 1
968
+ continue
969
+ }
970
+
971
+ // Delayed SGR mouse continuation after `esc_recovery` has consumed the
972
+ // leading `[`. Consume the rest of `<digits;digits;digitsM/m` as one
973
+ // opaque response so split mouse bytes never leak into text.
974
+ case "esc_less_mouse": {
975
+ if (this.cursor >= bytes.length) {
976
+ if (!this.forceFlush) {
977
+ this.markPending()
978
+ return
979
+ }
980
+
981
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
982
+ this.state = { tag: "ground" }
983
+ this.consumePrefix(this.cursor)
984
+ continue
985
+ }
986
+
987
+ if ((byte >= 0x30 && byte <= 0x39) || byte === 0x3b) {
988
+ this.cursor += 1
989
+ continue
990
+ }
991
+
992
+ if (byte === 0x4d || byte === 0x6d) {
993
+ const end = this.cursor + 1
994
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, end))
995
+ this.state = { tag: "ground" }
996
+ this.consumePrefix(end)
997
+ continue
998
+ }
999
+
1000
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, this.cursor))
1001
+ this.state = { tag: "ground" }
1002
+ this.consumePrefix(this.cursor)
1003
+ continue
1004
+ }
1005
+
1006
+ // Delayed X10 mouse continuation after `esc_recovery` has consumed the
1007
+ // leading `[`. Consume `[M` plus its three raw payload bytes as one
1008
+ // opaque response so split mouse bytes never leak into text.
1009
+ case "esc_less_x10_mouse": {
1010
+ const end = this.unitStart + 5
1011
+
1012
+ if (bytes.length < end) {
1013
+ if (!this.forceFlush) {
1014
+ this.markPending()
1015
+ return
1016
+ }
1017
+
1018
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, bytes.length))
1019
+ this.state = { tag: "ground" }
1020
+ this.consumePrefix(bytes.length)
1021
+ continue
1022
+ }
1023
+
1024
+ this.emitOpaqueResponse("unknown", bytes.subarray(this.unitStart, end))
1025
+ this.state = { tag: "ground" }
1026
+ this.consumePrefix(end)
1027
+ continue
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+
1033
+ // Tries to parse the raw string as a key via parseKeypress(). If it
1034
+ // recognizes the sequence (printable char, arrow, function key, etc.),
1035
+ // emits a key event. Otherwise emits a response event — this is how
1036
+ // capability responses, focus sequences, and other non-key CSI traffic
1037
+ // avoids becoming text.
1038
+ private emitKeyOrResponse(protocol: StdinResponseProtocol, raw: string): void {
1039
+ const parsed = parseKeypress(raw, { useKittyKeyboard: this.useKittyKeyboard })
1040
+ if (parsed) {
1041
+ this.events.push({
1042
+ type: "key",
1043
+ raw: parsed.raw,
1044
+ key: parsed,
1045
+ })
1046
+ return
1047
+ }
1048
+
1049
+ this.events.push({
1050
+ type: "response",
1051
+ protocol,
1052
+ sequence: raw,
1053
+ })
1054
+ }
1055
+
1056
+ private emitMouse(rawBytes: Uint8Array, encoding: "sgr" | "x10"): void {
1057
+ const event = this.mouseParser.parseMouseEvent(rawBytes)
1058
+ if (!event) {
1059
+ this.emitOpaqueResponse("unknown", rawBytes)
1060
+ return
1061
+ }
1062
+
1063
+ this.events.push({
1064
+ type: "mouse",
1065
+ raw: decodeLatin1(rawBytes),
1066
+ encoding,
1067
+ event,
1068
+ })
1069
+ }
1070
+
1071
+ // Handles single bytes in the 0x80–0xFF range that aren't valid UTF-8
1072
+ // leads. Passes them through parseKeypress() which maps them to the
1073
+ // existing meta-key behavior (e.g. Alt+letter in terminals that send
1074
+ // high bytes instead of ESC-prefixed sequences).
1075
+ private emitLegacyHighByte(byte: number): void {
1076
+ const parsed = parseKeypress(Buffer.from([byte]), { useKittyKeyboard: this.useKittyKeyboard })
1077
+ if (parsed) {
1078
+ this.events.push({
1079
+ type: "key",
1080
+ raw: parsed.raw,
1081
+ key: parsed,
1082
+ })
1083
+ return
1084
+ }
1085
+
1086
+ this.events.push({
1087
+ type: "response",
1088
+ protocol: "unknown",
1089
+ sequence: String.fromCharCode(byte),
1090
+ })
1091
+ }
1092
+
1093
+ private emitOpaqueResponse(protocol: StdinResponseProtocol, rawBytes: Uint8Array): void {
1094
+ this.events.push({
1095
+ type: "response",
1096
+ protocol,
1097
+ sequence: decodeLatin1(rawBytes),
1098
+ })
1099
+ }
1100
+
1101
+ // Advances past a completed protocol unit. Resets cursor, unitStart,
1102
+ // and timeout state so the next scan iteration starts clean.
1103
+ private consumePrefix(endExclusive: number): void {
1104
+ this.pending.consume(endExclusive)
1105
+ this.cursor = 0
1106
+ this.unitStart = 0
1107
+ this.pendingSinceMs = null
1108
+ this.forceFlush = false
1109
+ }
1110
+
1111
+ // Removes all bytes from the pending queue and returns them. Used when
1112
+ // entering paste mode — leftover bytes after the paste start marker
1113
+ // need to flow through consumePasteBytes() instead.
1114
+ private takePendingBytes(): Uint8Array {
1115
+ const buffered = this.pending.take()
1116
+ this.cursor = 0
1117
+ this.unitStart = 0
1118
+ this.pendingSinceMs = null
1119
+ this.forceFlush = false
1120
+ return buffered
1121
+ }
1122
+
1123
+ // Emits all pending bytes as one opaque response and clears the buffer.
1124
+ // This keeps the parser buffer bounded at maxPendingBytes without
1125
+ // dropping data or splitting it into per-character events.
1126
+ private flushPendingOverflow(): void {
1127
+ if (this.pending.length === 0) {
1128
+ return
1129
+ }
1130
+
1131
+ this.emitOpaqueResponse("unknown", this.pending.view())
1132
+ this.pending.clear()
1133
+ this.cursor = 0
1134
+ this.unitStart = 0
1135
+ this.pendingSinceMs = null
1136
+ this.forceFlush = false
1137
+ this.state = { tag: "ground" }
1138
+ }
1139
+
1140
+ // Records when incomplete data first appeared so flushTimeout() can
1141
+ // decide whether enough time has elapsed to force-flush it.
1142
+ private markPending(): void {
1143
+ this.pendingSinceMs = this.clock.now()
1144
+ }
1145
+
1146
+ // Processes bytes during an active bracketed paste. Searches for the end
1147
+ // marker (ESC[201~) using a sliding tail window so the marker can split
1148
+ // across chunk boundaries. Bytes that can't be part of the end marker are
1149
+ // appended to the paste collector without decoding.
1150
+ //
1151
+ // Returns any bytes that follow the end marker — those go back through
1152
+ // normal parsing in the push() loop.
1153
+ private consumePasteBytes(chunk: Uint8Array): Uint8Array {
1154
+ const paste = this.paste!
1155
+ const combined = concatBytes(paste.tail, chunk)
1156
+ const endIndex = indexOfBytes(combined, BRACKETED_PASTE_END)
1157
+
1158
+ if (endIndex !== -1) {
1159
+ this.pushPasteBytes(combined.subarray(0, endIndex))
1160
+
1161
+ this.events.push({
1162
+ type: "paste",
1163
+ bytes: joinPasteBytes(paste.parts, paste.totalLength),
1164
+ })
1165
+
1166
+ this.paste = null
1167
+ return combined.subarray(endIndex + BRACKETED_PASTE_END.length)
1168
+ }
1169
+
1170
+ // Keep enough trailing bytes to detect an end marker split across chunks.
1171
+ // Everything before that point is safe to retain immediately.
1172
+ const keep = Math.min(BRACKETED_PASTE_END.length - 1, combined.length)
1173
+ const stableLength = combined.length - keep
1174
+ if (stableLength > 0) {
1175
+ this.pushPasteBytes(combined.subarray(0, stableLength))
1176
+ }
1177
+
1178
+ paste.tail = Uint8Array.from(combined.subarray(stableLength))
1179
+ return EMPTY_BYTES
1180
+ }
1181
+
1182
+ private pushPasteBytes(bytes: Uint8Array): void {
1183
+ if (bytes.length === 0) {
1184
+ return
1185
+ }
1186
+
1187
+ // Copy here because subarray() inputs may alias the caller's chunk or the
1188
+ // parser's pending buffer across pushes. The emitted paste event must keep
1189
+ // the original bytes even if those backing buffers are later reused.
1190
+ this.paste!.parts.push(Uint8Array.from(bytes))
1191
+ this.paste!.totalLength += bytes.length
1192
+ }
1193
+
1194
+ // Arms or disarms the timeout after every push(). If there's an incomplete
1195
+ // unit in the buffer, starts a timer. When the timer fires, it sets
1196
+ // forceFlush so the next read() converts the incomplete unit into one
1197
+ // atomic event (e.g. a lone ESC becoming an Escape key).
1198
+ private reconcileTimeoutState(): void {
1199
+ if (!this.armTimeouts) {
1200
+ return
1201
+ }
1202
+
1203
+ if (this.paste || this.pendingSinceMs === null || this.pending.length === 0) {
1204
+ this.clearTimeout()
1205
+ return
1206
+ }
1207
+
1208
+ this.clearTimeout()
1209
+ this.timeoutId = this.clock.setTimeout(() => {
1210
+ this.timeoutId = null
1211
+ if (this.destroyed) {
1212
+ return
1213
+ }
1214
+
1215
+ try {
1216
+ this.flushTimeout(this.clock.now())
1217
+ this.onTimeoutFlush?.()
1218
+ } catch (error) {
1219
+ console.error("stdin parser timeout flush failed", error)
1220
+ }
1221
+ }, this.timeoutMs)
1222
+ }
1223
+
1224
+ private clearTimeout(): void {
1225
+ if (!this.timeoutId) {
1226
+ return
1227
+ }
1228
+
1229
+ this.clock.clearTimeout(this.timeoutId)
1230
+ this.timeoutId = null
1231
+ }
1232
+
1233
+ // Clears all parser state: pending bytes, queued events, timeout tracking,
1234
+ // and any active paste collector. Called by both reset() (suspend/resume)
1235
+ // and destroy() to ensure no stale state survives.
1236
+ private resetState(): void {
1237
+ this.pending.reset(INITIAL_PENDING_CAPACITY)
1238
+ this.events.length = 0
1239
+ this.pendingSinceMs = null
1240
+ this.forceFlush = false
1241
+ this.justFlushedEsc = false
1242
+ this.state = { tag: "ground" }
1243
+ this.cursor = 0
1244
+ this.unitStart = 0
1245
+ this.paste = null
1246
+ this.mouseParser.reset()
1247
+ }
1248
+ }