@fairyhunter13/opentui-core 0.1.91 → 0.1.94

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