@fairyhunter13/opentui-core 0.1.90 → 0.1.92

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 (571) hide show
  1. package/3d/SpriteResourceManager.d.ts +74 -0
  2. package/3d/SpriteUtils.d.ts +13 -0
  3. package/3d/TextureUtils.d.ts +24 -0
  4. package/3d/ThreeRenderable.d.ts +40 -0
  5. package/3d/WGPURenderer.d.ts +61 -0
  6. package/3d/animation/ExplodingSpriteEffect.d.ts +71 -0
  7. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +76 -0
  8. package/3d/animation/SpriteAnimator.d.ts +124 -0
  9. package/3d/animation/SpriteParticleGenerator.d.ts +62 -0
  10. package/3d/canvas.d.ts +44 -0
  11. package/3d/index.d.ts +12 -0
  12. package/3d/physics/PlanckPhysicsAdapter.d.ts +19 -0
  13. package/3d/physics/RapierPhysicsAdapter.d.ts +19 -0
  14. package/3d/physics/physics-interface.d.ts +27 -0
  15. package/3d.d.ts +2 -0
  16. package/3d.js +34042 -0
  17. package/3d.js.map +155 -0
  18. package/LICENSE +21 -0
  19. package/NativeSpanFeed.d.ts +41 -0
  20. package/README.md +2 -2
  21. package/Renderable.d.ts +334 -0
  22. package/animation/Timeline.d.ts +126 -0
  23. package/ansi.d.ts +13 -0
  24. package/buffer.d.ts +107 -0
  25. package/console.d.ts +143 -0
  26. package/edit-buffer.d.ts +98 -0
  27. package/editor-view.d.ts +73 -0
  28. package/index-e6ec7apq.js +18415 -0
  29. package/index-e6ec7apq.js.map +64 -0
  30. package/index-h066zmrb.js +12619 -0
  31. package/index-h066zmrb.js.map +43 -0
  32. package/index-ynzawt3n.js +113 -0
  33. package/index-ynzawt3n.js.map +10 -0
  34. package/index.d.ts +21 -0
  35. package/index.js +430 -0
  36. package/index.js.map +9 -0
  37. package/lib/KeyHandler.d.ts +61 -0
  38. package/lib/RGBA.d.ts +25 -0
  39. package/lib/ascii.font.d.ts +508 -0
  40. package/lib/border.d.ts +49 -0
  41. package/lib/bunfs.d.ts +7 -0
  42. package/lib/clipboard.d.ts +17 -0
  43. package/lib/clock.d.ts +15 -0
  44. package/lib/data-paths.d.ts +26 -0
  45. package/lib/debounce.d.ts +42 -0
  46. package/lib/detect-links.d.ts +6 -0
  47. package/lib/env.d.ts +42 -0
  48. package/lib/extmarks-history.d.ts +17 -0
  49. package/lib/extmarks.d.ts +89 -0
  50. package/lib/hast-styled-text.d.ts +17 -0
  51. package/lib/index.d.ts +21 -0
  52. package/lib/keymapping.d.ts +25 -0
  53. package/lib/objects-in-viewport.d.ts +24 -0
  54. package/lib/output.capture.d.ts +24 -0
  55. package/lib/parse.keypress-kitty.d.ts +2 -0
  56. package/lib/parse.keypress.d.ts +26 -0
  57. package/lib/parse.mouse.d.ts +30 -0
  58. package/lib/paste.d.ts +7 -0
  59. package/lib/queue.d.ts +15 -0
  60. package/lib/renderable.validations.d.ts +12 -0
  61. package/lib/scroll-acceleration.d.ts +43 -0
  62. package/lib/selection.d.ts +63 -0
  63. package/lib/singleton.d.ts +7 -0
  64. package/lib/stdin-parser.d.ts +76 -0
  65. package/lib/styled-text.d.ts +63 -0
  66. package/lib/terminal-capability-detection.d.ts +30 -0
  67. package/lib/terminal-palette.d.ts +50 -0
  68. package/lib/tree-sitter/assets/update.d.ts +11 -0
  69. package/lib/tree-sitter/client.d.ts +47 -0
  70. package/lib/tree-sitter/default-parsers.d.ts +2 -0
  71. package/lib/tree-sitter/download-utils.d.ts +21 -0
  72. package/lib/tree-sitter/index.d.ts +8 -0
  73. package/lib/tree-sitter/parser.worker.d.ts +1 -0
  74. package/lib/tree-sitter/parsers-config.d.ts +38 -0
  75. package/lib/tree-sitter/resolve-ft.d.ts +2 -0
  76. package/lib/tree-sitter/types.d.ts +81 -0
  77. package/lib/tree-sitter-styled-text.d.ts +14 -0
  78. package/lib/validate-dir-name.d.ts +1 -0
  79. package/lib/yoga.options.d.ts +32 -0
  80. package/package.json +51 -63
  81. package/parser.worker.js +869 -0
  82. package/parser.worker.js.map +12 -0
  83. package/plugins/core-slot.d.ts +72 -0
  84. package/plugins/registry.d.ts +38 -0
  85. package/plugins/types.d.ts +34 -0
  86. package/post/filters.d.ts +105 -0
  87. package/renderables/ASCIIFont.d.ts +52 -0
  88. package/renderables/Box.d.ts +72 -0
  89. package/renderables/Code.d.ts +78 -0
  90. package/renderables/Diff.d.ts +142 -0
  91. package/renderables/EditBufferRenderable.d.ts +162 -0
  92. package/renderables/FrameBuffer.d.ts +16 -0
  93. package/renderables/Input.d.ts +67 -0
  94. package/renderables/LineNumberRenderable.d.ts +74 -0
  95. package/renderables/Markdown.d.ts +173 -0
  96. package/renderables/ScrollBar.d.ts +77 -0
  97. package/renderables/ScrollBox.d.ts +124 -0
  98. package/renderables/Select.d.ts +115 -0
  99. package/renderables/Slider.d.ts +44 -0
  100. package/renderables/TabSelect.d.ts +96 -0
  101. package/renderables/Text.d.ts +36 -0
  102. package/renderables/TextBufferRenderable.d.ts +105 -0
  103. package/renderables/TextNode.d.ts +91 -0
  104. package/renderables/TextTable.d.ts +140 -0
  105. package/renderables/Textarea.d.ts +114 -0
  106. package/renderables/TimeToFirstDraw.d.ts +24 -0
  107. package/renderables/__tests__/renderable-test-utils.d.ts +12 -0
  108. package/renderables/composition/VRenderable.d.ts +16 -0
  109. package/renderables/composition/constructs.d.ts +35 -0
  110. package/renderables/composition/vnode.d.ts +46 -0
  111. package/renderables/index.d.ts +22 -0
  112. package/renderables/markdown-parser.d.ts +10 -0
  113. package/renderer.d.ts +388 -0
  114. package/runtime-plugin-support.d.ts +3 -0
  115. package/runtime-plugin-support.js +29 -0
  116. package/runtime-plugin-support.js.map +10 -0
  117. package/runtime-plugin.d.ts +11 -0
  118. package/runtime-plugin.js +16 -0
  119. package/runtime-plugin.js.map +9 -0
  120. package/syntax-style.d.ts +54 -0
  121. package/testing/manual-clock.d.ts +16 -0
  122. package/testing/mock-keys.d.ts +81 -0
  123. package/testing/mock-mouse.d.ts +38 -0
  124. package/testing/mock-tree-sitter-client.d.ts +23 -0
  125. package/testing/spy.d.ts +7 -0
  126. package/testing/test-recorder.d.ts +61 -0
  127. package/testing/test-renderer.d.ts +23 -0
  128. package/testing.d.ts +6 -0
  129. package/testing.js +675 -0
  130. package/testing.js.map +15 -0
  131. package/text-buffer-view.d.ts +42 -0
  132. package/text-buffer.d.ts +67 -0
  133. package/types.d.ts +131 -0
  134. package/utils.d.ts +14 -0
  135. package/zig-structs.d.ts +155 -0
  136. package/zig.d.ts +351 -0
  137. package/dev/keypress-debug-renderer.ts +0 -148
  138. package/dev/keypress-debug.ts +0 -43
  139. package/dev/print-env-vars.ts +0 -32
  140. package/dev/test-tmux-graphics-334.sh +0 -68
  141. package/dev/thai-debug-test.ts +0 -68
  142. package/docs/development.md +0 -141
  143. package/docs/env-vars.md +0 -140
  144. package/docs/getting-started.md +0 -353
  145. package/docs/renderables-vs-constructs.md +0 -159
  146. package/docs/tree-sitter.md +0 -311
  147. package/scripts/build.ts +0 -400
  148. package/scripts/publish.ts +0 -60
  149. package/src/3d/SpriteResourceManager.ts +0 -286
  150. package/src/3d/SpriteUtils.ts +0 -71
  151. package/src/3d/TextureUtils.ts +0 -196
  152. package/src/3d/ThreeRenderable.ts +0 -197
  153. package/src/3d/WGPURenderer.ts +0 -294
  154. package/src/3d/animation/ExplodingSpriteEffect.ts +0 -513
  155. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +0 -429
  156. package/src/3d/animation/SpriteAnimator.ts +0 -633
  157. package/src/3d/animation/SpriteParticleGenerator.ts +0 -435
  158. package/src/3d/canvas.ts +0 -464
  159. package/src/3d/index.ts +0 -12
  160. package/src/3d/physics/PlanckPhysicsAdapter.ts +0 -72
  161. package/src/3d/physics/RapierPhysicsAdapter.ts +0 -66
  162. package/src/3d/physics/physics-interface.ts +0 -31
  163. package/src/3d/shaders/supersampling.wgsl +0 -201
  164. package/src/3d.ts +0 -3
  165. package/src/NativeSpanFeed.ts +0 -300
  166. package/src/Renderable.ts +0 -1698
  167. package/src/__snapshots__/buffer.test.ts.snap +0 -28
  168. package/src/animation/Timeline.test.ts +0 -2709
  169. package/src/animation/Timeline.ts +0 -598
  170. package/src/ansi.ts +0 -18
  171. package/src/benchmark/latest-all-bench-run.json +0 -707
  172. package/src/benchmark/latest-async-bench-run.json +0 -336
  173. package/src/benchmark/latest-default-bench-run.json +0 -657
  174. package/src/benchmark/latest-large-bench-run.json +0 -707
  175. package/src/benchmark/latest-quick-bench-run.json +0 -207
  176. package/src/benchmark/markdown-benchmark.ts +0 -1804
  177. package/src/benchmark/native-span-feed-async-benchmark.ts +0 -355
  178. package/src/benchmark/native-span-feed-benchmark.md +0 -56
  179. package/src/benchmark/native-span-feed-benchmark.ts +0 -596
  180. package/src/benchmark/native-span-feed-compare.ts +0 -280
  181. package/src/benchmark/renderer-benchmark.ts +0 -754
  182. package/src/benchmark/text-table-benchmark.ts +0 -947
  183. package/src/buffer.test.ts +0 -291
  184. package/src/buffer.ts +0 -519
  185. package/src/console.test.ts +0 -612
  186. package/src/console.ts +0 -1255
  187. package/src/edit-buffer.test.ts +0 -1769
  188. package/src/edit-buffer.ts +0 -411
  189. package/src/editor-view.test.ts +0 -1032
  190. package/src/editor-view.ts +0 -284
  191. package/src/examples/ascii-font-selection-demo.ts +0 -245
  192. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  193. package/src/examples/assets/concrete.png +0 -0
  194. package/src/examples/assets/crate.png +0 -0
  195. package/src/examples/assets/crate_emissive.png +0 -0
  196. package/src/examples/assets/forrest_background.png +0 -0
  197. package/src/examples/assets/hast-example.json +0 -1018
  198. package/src/examples/assets/heart.png +0 -0
  199. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  200. package/src/examples/assets/main_char_idle.png +0 -0
  201. package/src/examples/assets/main_char_jump_end.png +0 -0
  202. package/src/examples/assets/main_char_jump_landing.png +0 -0
  203. package/src/examples/assets/main_char_jump_start.png +0 -0
  204. package/src/examples/assets/main_char_run_loop.png +0 -0
  205. package/src/examples/assets/roughness_map.jpg +0 -0
  206. package/src/examples/build.ts +0 -115
  207. package/src/examples/code-demo.ts +0 -584
  208. package/src/examples/console-demo.ts +0 -358
  209. package/src/examples/core-plugin-slots-demo.ts +0 -759
  210. package/src/examples/diff-demo.ts +0 -699
  211. package/src/examples/draggable-three-demo.ts +0 -259
  212. package/src/examples/editor-demo.ts +0 -322
  213. package/src/examples/extmarks-demo.ts +0 -204
  214. package/src/examples/focus-restore-demo.ts +0 -310
  215. package/src/examples/fonts.ts +0 -245
  216. package/src/examples/fractal-shader-demo.ts +0 -268
  217. package/src/examples/framebuffer-demo.ts +0 -674
  218. package/src/examples/full-unicode-demo.ts +0 -181
  219. package/src/examples/golden-star-demo.ts +0 -933
  220. package/src/examples/grayscale-buffer-demo.ts +0 -249
  221. package/src/examples/hast-syntax-highlighting-demo.ts +0 -129
  222. package/src/examples/index.ts +0 -925
  223. package/src/examples/input-demo.ts +0 -377
  224. package/src/examples/input-select-layout-demo.ts +0 -425
  225. package/src/examples/install.sh +0 -143
  226. package/src/examples/keypress-debug-demo.ts +0 -452
  227. package/src/examples/lib/HexList.ts +0 -122
  228. package/src/examples/lib/PaletteGrid.ts +0 -125
  229. package/src/examples/lib/standalone-keys.ts +0 -25
  230. package/src/examples/lib/tab-controller.ts +0 -243
  231. package/src/examples/lights-phong-demo.ts +0 -290
  232. package/src/examples/link-demo.ts +0 -220
  233. package/src/examples/live-state-demo.ts +0 -480
  234. package/src/examples/markdown-demo.ts +0 -620
  235. package/src/examples/mouse-interaction-demo.ts +0 -428
  236. package/src/examples/nested-zindex-demo.ts +0 -357
  237. package/src/examples/opacity-example.ts +0 -235
  238. package/src/examples/opentui-demo.ts +0 -1057
  239. package/src/examples/physx-planck-2d-demo.ts +0 -507
  240. package/src/examples/physx-rapier-2d-demo.ts +0 -526
  241. package/src/examples/relative-positioning-demo.ts +0 -323
  242. package/src/examples/scroll-example.ts +0 -214
  243. package/src/examples/scrollbox-mouse-test.ts +0 -112
  244. package/src/examples/scrollbox-overlay-hit-test.ts +0 -206
  245. package/src/examples/select-demo.ts +0 -237
  246. package/src/examples/shader-cube-demo.ts +0 -772
  247. package/src/examples/simple-layout-example.ts +0 -591
  248. package/src/examples/slider-demo.ts +0 -617
  249. package/src/examples/split-mode-demo.ts +0 -445
  250. package/src/examples/sprite-animation-demo.ts +0 -443
  251. package/src/examples/sprite-particle-generator-demo.ts +0 -486
  252. package/src/examples/static-sprite-demo.ts +0 -193
  253. package/src/examples/sticky-scroll-example.ts +0 -308
  254. package/src/examples/styled-text-demo.ts +0 -282
  255. package/src/examples/tab-select-demo.ts +0 -219
  256. package/src/examples/terminal-title.ts +0 -29
  257. package/src/examples/terminal.ts +0 -305
  258. package/src/examples/text-node-demo.ts +0 -416
  259. package/src/examples/text-selection-demo.ts +0 -377
  260. package/src/examples/text-table-demo.ts +0 -503
  261. package/src/examples/text-truncation-demo.ts +0 -481
  262. package/src/examples/text-wrap.ts +0 -757
  263. package/src/examples/texture-loading-demo.ts +0 -259
  264. package/src/examples/timeline-example.ts +0 -670
  265. package/src/examples/transparency-demo.ts +0 -241
  266. package/src/examples/vnode-composition-demo.ts +0 -404
  267. package/src/index.ts +0 -22
  268. package/src/lib/KeyHandler.integration.test.ts +0 -292
  269. package/src/lib/KeyHandler.stopPropagation.test.ts +0 -289
  270. package/src/lib/KeyHandler.test.ts +0 -662
  271. package/src/lib/KeyHandler.ts +0 -222
  272. package/src/lib/RGBA.test.ts +0 -984
  273. package/src/lib/RGBA.ts +0 -204
  274. package/src/lib/ascii.font.ts +0 -330
  275. package/src/lib/border.test.ts +0 -83
  276. package/src/lib/border.ts +0 -168
  277. package/src/lib/bunfs.test.ts +0 -27
  278. package/src/lib/bunfs.ts +0 -18
  279. package/src/lib/clipboard.test.ts +0 -41
  280. package/src/lib/clipboard.ts +0 -47
  281. package/src/lib/clock.ts +0 -31
  282. package/src/lib/data-paths.test.ts +0 -133
  283. package/src/lib/data-paths.ts +0 -109
  284. package/src/lib/debounce.ts +0 -106
  285. package/src/lib/detect-links.test.ts +0 -98
  286. package/src/lib/detect-links.ts +0 -56
  287. package/src/lib/env.test.ts +0 -228
  288. package/src/lib/env.ts +0 -209
  289. package/src/lib/extmarks-history.ts +0 -51
  290. package/src/lib/extmarks-multiwidth.test.ts +0 -322
  291. package/src/lib/extmarks.test.ts +0 -3457
  292. package/src/lib/extmarks.ts +0 -843
  293. package/src/lib/fonts/block.json +0 -405
  294. package/src/lib/fonts/grid.json +0 -265
  295. package/src/lib/fonts/huge.json +0 -741
  296. package/src/lib/fonts/pallet.json +0 -314
  297. package/src/lib/fonts/shade.json +0 -591
  298. package/src/lib/fonts/slick.json +0 -321
  299. package/src/lib/fonts/tiny.json +0 -69
  300. package/src/lib/hast-styled-text.ts +0 -59
  301. package/src/lib/index.ts +0 -21
  302. package/src/lib/keymapping.test.ts +0 -280
  303. package/src/lib/keymapping.ts +0 -87
  304. package/src/lib/objects-in-viewport.test.ts +0 -787
  305. package/src/lib/objects-in-viewport.ts +0 -153
  306. package/src/lib/output.capture.ts +0 -58
  307. package/src/lib/parse.keypress-kitty.protocol.test.ts +0 -340
  308. package/src/lib/parse.keypress-kitty.test.ts +0 -663
  309. package/src/lib/parse.keypress-kitty.ts +0 -439
  310. package/src/lib/parse.keypress.test.ts +0 -1849
  311. package/src/lib/parse.keypress.ts +0 -397
  312. package/src/lib/parse.mouse.test.ts +0 -552
  313. package/src/lib/parse.mouse.ts +0 -232
  314. package/src/lib/paste.ts +0 -16
  315. package/src/lib/queue.ts +0 -65
  316. package/src/lib/renderable.validations.test.ts +0 -87
  317. package/src/lib/renderable.validations.ts +0 -83
  318. package/src/lib/scroll-acceleration.ts +0 -98
  319. package/src/lib/selection.ts +0 -240
  320. package/src/lib/singleton.ts +0 -28
  321. package/src/lib/stdin-parser.test.ts +0 -1676
  322. package/src/lib/stdin-parser.ts +0 -1248
  323. package/src/lib/styled-text.ts +0 -178
  324. package/src/lib/terminal-capability-detection.test.ts +0 -202
  325. package/src/lib/terminal-capability-detection.ts +0 -79
  326. package/src/lib/terminal-palette.test.ts +0 -878
  327. package/src/lib/terminal-palette.ts +0 -383
  328. package/src/lib/tree-sitter/assets/README.md +0 -118
  329. package/src/lib/tree-sitter/assets/update.ts +0 -331
  330. package/src/lib/tree-sitter/assets.d.ts +0 -9
  331. package/src/lib/tree-sitter/cache.test.ts +0 -270
  332. package/src/lib/tree-sitter/client.test.ts +0 -1061
  333. package/src/lib/tree-sitter/client.ts +0 -615
  334. package/src/lib/tree-sitter/default-parsers.ts +0 -80
  335. package/src/lib/tree-sitter/download-utils.ts +0 -148
  336. package/src/lib/tree-sitter/index.ts +0 -28
  337. package/src/lib/tree-sitter/parser.worker.ts +0 -1001
  338. package/src/lib/tree-sitter/parsers-config.ts +0 -75
  339. package/src/lib/tree-sitter/resolve-ft.ts +0 -62
  340. package/src/lib/tree-sitter/types.ts +0 -81
  341. package/src/lib/tree-sitter-styled-text.test.ts +0 -1253
  342. package/src/lib/tree-sitter-styled-text.ts +0 -306
  343. package/src/lib/validate-dir-name.ts +0 -55
  344. package/src/lib/yoga.options.test.ts +0 -628
  345. package/src/lib/yoga.options.ts +0 -346
  346. package/src/plugins/core-slot.ts +0 -579
  347. package/src/plugins/registry.ts +0 -377
  348. package/src/plugins/types.ts +0 -46
  349. package/src/post/filters.ts +0 -888
  350. package/src/renderables/ASCIIFont.ts +0 -219
  351. package/src/renderables/Box.test.ts +0 -160
  352. package/src/renderables/Box.ts +0 -295
  353. package/src/renderables/Code.test.ts +0 -2062
  354. package/src/renderables/Code.ts +0 -357
  355. package/src/renderables/Diff.regression.test.ts +0 -226
  356. package/src/renderables/Diff.test.ts +0 -3027
  357. package/src/renderables/Diff.ts +0 -1209
  358. package/src/renderables/EditBufferRenderable.ts +0 -764
  359. package/src/renderables/FrameBuffer.ts +0 -47
  360. package/src/renderables/Input.test.ts +0 -1228
  361. package/src/renderables/Input.ts +0 -245
  362. package/src/renderables/LineNumberRenderable.ts +0 -675
  363. package/src/renderables/Markdown.ts +0 -1106
  364. package/src/renderables/ScrollBar.ts +0 -422
  365. package/src/renderables/ScrollBox.ts +0 -883
  366. package/src/renderables/Select.test.ts +0 -1010
  367. package/src/renderables/Select.ts +0 -523
  368. package/src/renderables/Slider.test.ts +0 -456
  369. package/src/renderables/Slider.ts +0 -347
  370. package/src/renderables/TabSelect.test.ts +0 -197
  371. package/src/renderables/TabSelect.ts +0 -455
  372. package/src/renderables/Text.selection-buffer.test.ts +0 -123
  373. package/src/renderables/Text.test.ts +0 -2660
  374. package/src/renderables/Text.ts +0 -147
  375. package/src/renderables/TextBufferRenderable.ts +0 -518
  376. package/src/renderables/TextNode.test.ts +0 -1058
  377. package/src/renderables/TextNode.ts +0 -325
  378. package/src/renderables/TextTable.test.ts +0 -1421
  379. package/src/renderables/TextTable.ts +0 -1344
  380. package/src/renderables/Textarea.ts +0 -732
  381. package/src/renderables/TimeToFirstDraw.ts +0 -89
  382. package/src/renderables/__snapshots__/Code.test.ts.snap +0 -13
  383. package/src/renderables/__snapshots__/Diff.test.ts.snap +0 -785
  384. package/src/renderables/__snapshots__/Text.test.ts.snap +0 -421
  385. package/src/renderables/__snapshots__/TextTable.test.ts.snap +0 -215
  386. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +0 -144
  387. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +0 -816
  388. package/src/renderables/__tests__/LineNumberRenderable.test.ts +0 -1787
  389. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +0 -85
  390. package/src/renderables/__tests__/Markdown.test.ts +0 -2287
  391. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +0 -87
  392. package/src/renderables/__tests__/Textarea.buffer.test.ts +0 -682
  393. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +0 -675
  394. package/src/renderables/__tests__/Textarea.editing.test.ts +0 -2041
  395. package/src/renderables/__tests__/Textarea.error-handling.test.ts +0 -35
  396. package/src/renderables/__tests__/Textarea.events.test.ts +0 -738
  397. package/src/renderables/__tests__/Textarea.highlights.test.ts +0 -590
  398. package/src/renderables/__tests__/Textarea.keybinding.test.ts +0 -3149
  399. package/src/renderables/__tests__/Textarea.paste.test.ts +0 -357
  400. package/src/renderables/__tests__/Textarea.rendering.test.ts +0 -1864
  401. package/src/renderables/__tests__/Textarea.scroll.test.ts +0 -733
  402. package/src/renderables/__tests__/Textarea.selection.test.ts +0 -1590
  403. package/src/renderables/__tests__/Textarea.stress.test.ts +0 -670
  404. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +0 -383
  405. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +0 -310
  406. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +0 -221
  407. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +0 -89
  408. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +0 -457
  409. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +0 -158
  410. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +0 -387
  411. package/src/renderables/__tests__/markdown-parser.test.ts +0 -217
  412. package/src/renderables/__tests__/renderable-test-utils.ts +0 -60
  413. package/src/renderables/composition/README.md +0 -8
  414. package/src/renderables/composition/VRenderable.ts +0 -32
  415. package/src/renderables/composition/constructs.ts +0 -127
  416. package/src/renderables/composition/vnode.ts +0 -289
  417. package/src/renderables/index.ts +0 -22
  418. package/src/renderables/markdown-parser.ts +0 -66
  419. package/src/renderer.ts +0 -2363
  420. package/src/runtime-plugin-support.ts +0 -39
  421. package/src/runtime-plugin.ts +0 -144
  422. package/src/syntax-style.test.ts +0 -841
  423. package/src/syntax-style.ts +0 -264
  424. package/src/testing/README.md +0 -210
  425. package/src/testing/capture-spans.test.ts +0 -194
  426. package/src/testing/integration.test.ts +0 -276
  427. package/src/testing/manual-clock.ts +0 -106
  428. package/src/testing/mock-keys.test.ts +0 -1356
  429. package/src/testing/mock-keys.ts +0 -449
  430. package/src/testing/mock-mouse.test.ts +0 -218
  431. package/src/testing/mock-mouse.ts +0 -247
  432. package/src/testing/mock-tree-sitter-client.ts +0 -73
  433. package/src/testing/spy.ts +0 -13
  434. package/src/testing/test-recorder.test.ts +0 -415
  435. package/src/testing/test-recorder.ts +0 -145
  436. package/src/testing/test-renderer.ts +0 -116
  437. package/src/testing.ts +0 -7
  438. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +0 -481
  439. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +0 -19
  440. package/src/tests/__snapshots__/scrollbox.test.ts.snap +0 -29
  441. package/src/tests/absolute-positioning.snapshot.test.ts +0 -638
  442. package/src/tests/allocator-stats.test.ts +0 -38
  443. package/src/tests/destroy-during-render.test.ts +0 -200
  444. package/src/tests/hover-cursor.test.ts +0 -98
  445. package/src/tests/native-span-feed-async.test.ts +0 -173
  446. package/src/tests/native-span-feed-close.test.ts +0 -120
  447. package/src/tests/native-span-feed-coverage.test.ts +0 -227
  448. package/src/tests/native-span-feed-edge-cases.test.ts +0 -352
  449. package/src/tests/native-span-feed-use-after-free.test.ts +0 -45
  450. package/src/tests/opacity.test.ts +0 -123
  451. package/src/tests/renderable.snapshot.test.ts +0 -524
  452. package/src/tests/renderable.test.ts +0 -1281
  453. package/src/tests/renderer.console-startup.test.ts +0 -65
  454. package/src/tests/renderer.control.test.ts +0 -364
  455. package/src/tests/renderer.core-slot-binding.test.ts +0 -952
  456. package/src/tests/renderer.cursor.test.ts +0 -26
  457. package/src/tests/renderer.destroy-during-render.test.ts +0 -110
  458. package/src/tests/renderer.focus-restore.test.ts +0 -228
  459. package/src/tests/renderer.focus.test.ts +0 -251
  460. package/src/tests/renderer.idle.test.ts +0 -219
  461. package/src/tests/renderer.input.test.ts +0 -2145
  462. package/src/tests/renderer.kitty-flags.test.ts +0 -195
  463. package/src/tests/renderer.mouse.test.ts +0 -1269
  464. package/src/tests/renderer.palette.test.ts +0 -629
  465. package/src/tests/renderer.selection.test.ts +0 -49
  466. package/src/tests/renderer.slot-registry.test.ts +0 -649
  467. package/src/tests/renderer.useMouse.test.ts +0 -50
  468. package/src/tests/runtime-plugin-support.fixture.ts +0 -11
  469. package/src/tests/runtime-plugin-support.test.ts +0 -28
  470. package/src/tests/runtime-plugin.fixture.ts +0 -40
  471. package/src/tests/runtime-plugin.test.ts +0 -190
  472. package/src/tests/scrollbox-culling-bug.test.ts +0 -114
  473. package/src/tests/scrollbox-hitgrid-resize.test.ts +0 -136
  474. package/src/tests/scrollbox-hitgrid.test.ts +0 -909
  475. package/src/tests/scrollbox.test.ts +0 -1530
  476. package/src/tests/wrap-resize-perf.test.ts +0 -229
  477. package/src/tests/yoga-setters.test.ts +0 -921
  478. package/src/text-buffer-view.test.ts +0 -705
  479. package/src/text-buffer-view.ts +0 -189
  480. package/src/text-buffer.test.ts +0 -347
  481. package/src/text-buffer.ts +0 -250
  482. package/src/types.ts +0 -152
  483. package/src/utils.ts +0 -88
  484. package/src/zig/ansi.zig +0 -268
  485. package/src/zig/bench/README.md +0 -50
  486. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +0 -887
  487. package/src/zig/bench/edit-buffer_bench.zig +0 -476
  488. package/src/zig/bench/native-span-feed_bench.zig +0 -100
  489. package/src/zig/bench/rope-markers_bench.zig +0 -713
  490. package/src/zig/bench/rope_bench.zig +0 -514
  491. package/src/zig/bench/styled-text_bench.zig +0 -470
  492. package/src/zig/bench/text-buffer-coords_bench.zig +0 -362
  493. package/src/zig/bench/text-buffer-view_bench.zig +0 -459
  494. package/src/zig/bench/text-chunk-graphemes_bench.zig +0 -273
  495. package/src/zig/bench/utf8_bench.zig +0 -799
  496. package/src/zig/bench-utils.zig +0 -431
  497. package/src/zig/bench.zig +0 -217
  498. package/src/zig/buffer.zig +0 -2223
  499. package/src/zig/build.zig +0 -289
  500. package/src/zig/build.zig.zon +0 -16
  501. package/src/zig/edit-buffer.zig +0 -825
  502. package/src/zig/editor-view.zig +0 -802
  503. package/src/zig/event-bus.zig +0 -13
  504. package/src/zig/event-emitter.zig +0 -65
  505. package/src/zig/file-logger.zig +0 -92
  506. package/src/zig/grapheme.zig +0 -599
  507. package/src/zig/lib.zig +0 -1834
  508. package/src/zig/link.zig +0 -333
  509. package/src/zig/logger.zig +0 -43
  510. package/src/zig/mem-registry.zig +0 -125
  511. package/src/zig/native-span-feed-bench-lib.zig +0 -7
  512. package/src/zig/native-span-feed.zig +0 -708
  513. package/src/zig/renderer.zig +0 -1386
  514. package/src/zig/rope.zig +0 -1220
  515. package/src/zig/syntax-style.zig +0 -161
  516. package/src/zig/terminal.zig +0 -975
  517. package/src/zig/test.zig +0 -70
  518. package/src/zig/tests/README.md +0 -18
  519. package/src/zig/tests/buffer_test.zig +0 -2526
  520. package/src/zig/tests/edit-buffer-history_test.zig +0 -271
  521. package/src/zig/tests/edit-buffer_test.zig +0 -1689
  522. package/src/zig/tests/editor-view_test.zig +0 -3299
  523. package/src/zig/tests/event-emitter_test.zig +0 -249
  524. package/src/zig/tests/grapheme_test.zig +0 -1304
  525. package/src/zig/tests/link_test.zig +0 -190
  526. package/src/zig/tests/mem-registry_test.zig +0 -473
  527. package/src/zig/tests/memory_leak_regression_test.zig +0 -159
  528. package/src/zig/tests/native-span-feed_test.zig +0 -1264
  529. package/src/zig/tests/renderer_test.zig +0 -1010
  530. package/src/zig/tests/rope-nested_test.zig +0 -712
  531. package/src/zig/tests/rope_fuzz_test.zig +0 -238
  532. package/src/zig/tests/rope_test.zig +0 -2362
  533. package/src/zig/tests/segment-merge.test.zig +0 -148
  534. package/src/zig/tests/syntax-style_test.zig +0 -557
  535. package/src/zig/tests/terminal_test.zig +0 -719
  536. package/src/zig/tests/text-buffer-drawing_test.zig +0 -3237
  537. package/src/zig/tests/text-buffer-highlights_test.zig +0 -666
  538. package/src/zig/tests/text-buffer-iterators_test.zig +0 -776
  539. package/src/zig/tests/text-buffer-segment_test.zig +0 -320
  540. package/src/zig/tests/text-buffer-selection_test.zig +0 -1035
  541. package/src/zig/tests/text-buffer-selection_viewport_test.zig +0 -358
  542. package/src/zig/tests/text-buffer-view_test.zig +0 -3649
  543. package/src/zig/tests/text-buffer_test.zig +0 -2191
  544. package/src/zig/tests/unicode-width-map.zon +0 -3909
  545. package/src/zig/tests/utf8_no_zwj_test.zig +0 -260
  546. package/src/zig/tests/utf8_test.zig +0 -4057
  547. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +0 -267
  548. package/src/zig/tests/utf8_wcwidth_test.zig +0 -357
  549. package/src/zig/tests/word-wrap-editing_test.zig +0 -498
  550. package/src/zig/tests/wrap-cache-perf_test.zig +0 -113
  551. package/src/zig/text-buffer-iterators.zig +0 -499
  552. package/src/zig/text-buffer-segment.zig +0 -404
  553. package/src/zig/text-buffer-view.zig +0 -1371
  554. package/src/zig/text-buffer.zig +0 -1180
  555. package/src/zig/utf8.zig +0 -1948
  556. package/src/zig/utils.zig +0 -9
  557. package/src/zig-structs.ts +0 -261
  558. package/src/zig.ts +0 -3843
  559. package/tsconfig.build.json +0 -22
  560. package/tsconfig.json +0 -28
  561. /package/{src/lib/tree-sitter/assets → assets}/javascript/highlights.scm +0 -0
  562. /package/{src/lib/tree-sitter/assets → assets}/javascript/tree-sitter-javascript.wasm +0 -0
  563. /package/{src/lib/tree-sitter/assets → assets}/markdown/highlights.scm +0 -0
  564. /package/{src/lib/tree-sitter/assets → assets}/markdown/injections.scm +0 -0
  565. /package/{src/lib/tree-sitter/assets → assets}/markdown/tree-sitter-markdown.wasm +0 -0
  566. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/highlights.scm +0 -0
  567. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  568. /package/{src/lib/tree-sitter/assets → assets}/typescript/highlights.scm +0 -0
  569. /package/{src/lib/tree-sitter/assets → assets}/typescript/tree-sitter-typescript.wasm +0 -0
  570. /package/{src/lib/tree-sitter/assets → assets}/zig/highlights.scm +0 -0
  571. /package/{src/lib/tree-sitter/assets → assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -1,1248 +0,0 @@
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
- }