@xincli/opentui-core 0.4.6 → 0.4.7

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 (619) hide show
  1. package/LICENSE +21 -0
  2. package/NativeSpanFeed.d.ts +52 -0
  3. package/README.md +0 -0
  4. package/Renderable.d.ts +346 -0
  5. package/animation/Timeline.d.ts +126 -0
  6. package/ansi.d.ts +17 -0
  7. package/audio.d.ts +89 -0
  8. package/buffer.d.ts +113 -0
  9. package/console.d.ts +146 -0
  10. package/edit-buffer.d.ts +97 -0
  11. package/editor-view.d.ts +72 -0
  12. package/index-m1y9e40w.js +10045 -0
  13. package/index-m1y9e40w.js.map +32 -0
  14. package/index-sqk4ajdf.js +16121 -0
  15. package/index-sqk4ajdf.js.map +58 -0
  16. package/index.d.ts +24 -0
  17. package/index.js +11709 -0
  18. package/index.js.map +35 -0
  19. package/lib/KeyHandler.d.ts +61 -0
  20. package/lib/RGBA.d.ts +42 -0
  21. package/lib/ascii.font.d.ts +508 -0
  22. package/lib/border.d.ts +51 -0
  23. package/lib/bunfs.d.ts +7 -0
  24. package/lib/clipboard.d.ts +15 -0
  25. package/lib/clock.d.ts +15 -0
  26. package/lib/data-paths.d.ts +26 -0
  27. package/lib/debounce.d.ts +42 -0
  28. package/lib/detect-links.d.ts +6 -0
  29. package/lib/env.d.ts +42 -0
  30. package/lib/extmarks-history.d.ts +17 -0
  31. package/lib/extmarks.d.ts +90 -0
  32. package/lib/hast-styled-text.d.ts +17 -0
  33. package/lib/index.d.ts +22 -0
  34. package/lib/keybinding.internal.d.ts +33 -0
  35. package/lib/objects-in-viewport.d.ts +24 -0
  36. package/lib/output.capture.d.ts +24 -0
  37. package/lib/parse.keypress-kitty.d.ts +3 -0
  38. package/lib/parse.keypress.d.ts +27 -0
  39. package/lib/parse.mouse.d.ts +30 -0
  40. package/lib/paste.d.ts +7 -0
  41. package/lib/queue.d.ts +15 -0
  42. package/lib/render-geometry.d.ts +8 -0
  43. package/lib/renderable.validations.d.ts +12 -0
  44. package/lib/scroll-acceleration.d.ts +43 -0
  45. package/lib/selection.d.ts +64 -0
  46. package/lib/singleton.d.ts +8 -0
  47. package/lib/stdin-parser.d.ts +90 -0
  48. package/lib/styled-text.d.ts +63 -0
  49. package/lib/terminal-capability-detection.d.ts +32 -0
  50. package/lib/terminal-palette.d.ts +65 -0
  51. package/lib/tree-sitter/assets/update.d.ts +12 -0
  52. package/lib/tree-sitter/client.d.ts +65 -0
  53. package/lib/tree-sitter/default-parsers.d.ts +2 -0
  54. package/lib/tree-sitter/download-utils.d.ts +21 -0
  55. package/lib/tree-sitter/index.d.ts +7 -0
  56. package/lib/tree-sitter/parser.worker.d.ts +1 -0
  57. package/lib/tree-sitter/parsers-config.d.ts +53 -0
  58. package/lib/tree-sitter/resolve-ft.d.ts +5 -0
  59. package/lib/tree-sitter/types.d.ts +185 -0
  60. package/lib/tree-sitter/update-assets.d.ts +3 -0
  61. package/lib/tree-sitter/update-assets.js +379 -0
  62. package/lib/tree-sitter/update-assets.js.map +12 -0
  63. package/lib/tree-sitter-styled-text.d.ts +16 -0
  64. package/lib/validate-dir-name.d.ts +1 -0
  65. package/lib/yoga.options.d.ts +32 -0
  66. package/native-event-worker-repro.worker.d.ts +1 -0
  67. package/package.json +52 -74
  68. package/parser.worker.js +1399 -0
  69. package/parser.worker.js.map +20 -0
  70. package/platform/ffi.d.ts +129 -0
  71. package/platform/runtime.d.ts +14 -0
  72. package/platform/test.d.ts +12 -0
  73. package/platform/worker.d.ts +26 -0
  74. package/platform/worker.node-test.d.ts +1 -0
  75. package/plugins/core-slot.d.ts +72 -0
  76. package/plugins/registry.d.ts +42 -0
  77. package/plugins/types.d.ts +34 -0
  78. package/post/effects.d.ts +147 -0
  79. package/post/filters.d.ts +65 -0
  80. package/post/matrices.d.ts +20 -0
  81. package/renderables/ASCIIFont.d.ts +52 -0
  82. package/renderables/Box.d.ts +85 -0
  83. package/renderables/Code.d.ts +97 -0
  84. package/renderables/Diff.d.ts +147 -0
  85. package/renderables/EditBufferRenderable.d.ts +238 -0
  86. package/renderables/FrameBuffer.d.ts +16 -0
  87. package/renderables/Input.d.ts +72 -0
  88. package/renderables/LineNumberRenderable.d.ts +78 -0
  89. package/renderables/Markdown.d.ts +255 -0
  90. package/renderables/ScrollBar.d.ts +77 -0
  91. package/renderables/ScrollBox.d.ts +128 -0
  92. package/renderables/Select.d.ts +122 -0
  93. package/renderables/Slider.d.ts +47 -0
  94. package/renderables/TabSelect.d.ts +98 -0
  95. package/renderables/Text.d.ts +36 -0
  96. package/renderables/TextBufferRenderable.d.ts +107 -0
  97. package/renderables/TextNode.d.ts +92 -0
  98. package/renderables/TextTable.d.ts +154 -0
  99. package/renderables/Textarea.d.ts +65 -0
  100. package/renderables/TimeToFirstDraw.d.ts +24 -0
  101. package/renderables/__tests__/renderable-test-utils.d.ts +12 -0
  102. package/renderables/composition/VRenderable.d.ts +16 -0
  103. package/renderables/composition/constructs.d.ts +35 -0
  104. package/renderables/composition/vnode.d.ts +46 -0
  105. package/renderables/index.d.ts +23 -0
  106. package/renderables/markdown-parser.d.ts +11 -0
  107. package/renderer-theme-mode.d.ts +29 -0
  108. package/renderer.d.ts +601 -0
  109. package/runtime-plugin-support-configure.d.ts +4 -0
  110. package/runtime-plugin-support-configure.js +36 -0
  111. package/runtime-plugin-support-configure.js.map +1 -0
  112. package/runtime-plugin-support-configure.node.js +15 -0
  113. package/runtime-plugin-support.d.ts +3 -0
  114. package/runtime-plugin-support.js +5 -0
  115. package/runtime-plugin-support.js.map +1 -0
  116. package/runtime-plugin-support.node.js +15 -0
  117. package/runtime-plugin.d.ts +16 -0
  118. package/runtime-plugin.js +473 -0
  119. package/runtime-plugin.js.map +1 -0
  120. package/runtime-plugin.node.js +15 -0
  121. package/syntax-style.d.ts +61 -0
  122. package/testing/bun-test-node.d.ts +87 -0
  123. package/testing/manual-clock.d.ts +17 -0
  124. package/testing/mock-keys.d.ts +81 -0
  125. package/testing/mock-mouse.d.ts +38 -0
  126. package/testing/mock-tree-sitter-client.d.ts +27 -0
  127. package/testing/spy.d.ts +7 -0
  128. package/testing/terminal-capabilities.d.ts +7 -0
  129. package/testing/test-recorder.d.ts +61 -0
  130. package/testing/test-renderer.d.ts +54 -0
  131. package/testing/test-streams.d.ts +12 -0
  132. package/testing.d.ts +8 -0
  133. package/testing.js +1004 -0
  134. package/testing.js.map +18 -0
  135. package/tests/yoga-upstream/tools/MeasureCounter.d.ts +16 -0
  136. package/tests/yoga-upstream/tools/utils.d.ts +11 -0
  137. package/text-buffer-view.d.ts +42 -0
  138. package/text-buffer.d.ts +66 -0
  139. package/types.d.ts +175 -0
  140. package/utils.d.ts +14 -0
  141. package/yoga.d.ts +437 -0
  142. package/yoga.js +193 -0
  143. package/yoga.js.map +9 -0
  144. package/zig-structs.d.ts +256 -0
  145. package/zig.d.ts +496 -0
  146. package/dev/keypress-debug-renderer.ts +0 -148
  147. package/dev/keypress-debug.ts +0 -43
  148. package/dev/print-env-vars.ts +0 -32
  149. package/dev/test-tmux-graphics-334.sh +0 -68
  150. package/dev/test-tmux-theme-queries.sh +0 -151
  151. package/dev/thai-debug-test.ts +0 -68
  152. package/dev/theme-mode.ts +0 -290
  153. package/docs/development.md +0 -144
  154. package/prebuilt/aarch64-android/libopentui.so +0 -0
  155. package/scripts/build-android.ts +0 -226
  156. package/scripts/build-native-termux.sh +0 -525
  157. package/scripts/build.ts +0 -530
  158. package/scripts/dist-test.ts +0 -312
  159. package/scripts/package-prebuilt.ts +0 -110
  160. package/scripts/publish.ts +0 -60
  161. package/scripts/test-node-hook.mjs +0 -16
  162. package/scripts/test-node.ts +0 -241
  163. package/scripts/vendor-deps.sh +0 -73
  164. package/src/NativeSpanFeed.ts +0 -375
  165. package/src/Renderable.ts +0 -1878
  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 -22
  170. package/src/audio.ts +0 -479
  171. package/src/benchmark/attenuation-benchmark.ts +0 -81
  172. package/src/benchmark/audio-playback-benchmark.ts +0 -344
  173. package/src/benchmark/box-draw-benchmark.ts +0 -1042
  174. package/src/benchmark/colormatrix-benchmark.ts +0 -123
  175. package/src/benchmark/gain-benchmark.ts +0 -80
  176. package/src/benchmark/latest-all-bench-run.json +0 -707
  177. package/src/benchmark/latest-async-bench-run.json +0 -336
  178. package/src/benchmark/latest-default-bench-run.json +0 -657
  179. package/src/benchmark/latest-large-bench-run.json +0 -707
  180. package/src/benchmark/latest-quick-bench-run.json +0 -207
  181. package/src/benchmark/layout-benchmark.ts +0 -2547
  182. package/src/benchmark/markdown-benchmark.ts +0 -1796
  183. package/src/benchmark/native-span-feed-async-benchmark.ts +0 -355
  184. package/src/benchmark/native-span-feed-benchmark.md +0 -56
  185. package/src/benchmark/native-span-feed-benchmark.ts +0 -596
  186. package/src/benchmark/native-span-feed-compare.ts +0 -280
  187. package/src/benchmark/render-traversal-benchmark.ts +0 -928
  188. package/src/benchmark/text-buffer-render-benchmark.ts +0 -874
  189. package/src/benchmark/text-table-benchmark.ts +0 -948
  190. package/src/buffer.test.ts +0 -292
  191. package/src/buffer.ts +0 -564
  192. package/src/console.test.ts +0 -629
  193. package/src/console.ts +0 -1253
  194. package/src/edit-buffer.test.ts +0 -1852
  195. package/src/edit-buffer.ts +0 -417
  196. package/src/editor-view.test.ts +0 -1056
  197. package/src/editor-view.ts +0 -290
  198. package/src/index.ts +0 -25
  199. package/src/lib/KeyHandler.integration.test.ts +0 -292
  200. package/src/lib/KeyHandler.stopPropagation.test.ts +0 -289
  201. package/src/lib/KeyHandler.test.ts +0 -662
  202. package/src/lib/KeyHandler.ts +0 -222
  203. package/src/lib/RGBA.test.ts +0 -60
  204. package/src/lib/RGBA.ts +0 -343
  205. package/src/lib/ascii.font.ts +0 -330
  206. package/src/lib/border.test.ts +0 -83
  207. package/src/lib/border.ts +0 -170
  208. package/src/lib/bunfs.test.ts +0 -27
  209. package/src/lib/bunfs.ts +0 -18
  210. package/src/lib/clipboard.test.ts +0 -63
  211. package/src/lib/clipboard.ts +0 -41
  212. package/src/lib/clock.ts +0 -35
  213. package/src/lib/data-paths.test.ts +0 -133
  214. package/src/lib/data-paths.ts +0 -109
  215. package/src/lib/debounce.ts +0 -106
  216. package/src/lib/detect-links.test.ts +0 -98
  217. package/src/lib/detect-links.ts +0 -56
  218. package/src/lib/env.test.ts +0 -228
  219. package/src/lib/env.ts +0 -209
  220. package/src/lib/extmarks-history.ts +0 -51
  221. package/src/lib/extmarks-multiwidth.test.ts +0 -324
  222. package/src/lib/extmarks.test.ts +0 -3547
  223. package/src/lib/extmarks.ts +0 -861
  224. package/src/lib/fonts/block.json +0 -405
  225. package/src/lib/fonts/grid.json +0 -265
  226. package/src/lib/fonts/huge.json +0 -741
  227. package/src/lib/fonts/pallet.json +0 -314
  228. package/src/lib/fonts/shade.json +0 -591
  229. package/src/lib/fonts/slick.json +0 -321
  230. package/src/lib/fonts/tiny.json +0 -69
  231. package/src/lib/hast-styled-text.ts +0 -59
  232. package/src/lib/index.ts +0 -22
  233. package/src/lib/keybinding.internal.test.ts +0 -342
  234. package/src/lib/keybinding.internal.ts +0 -182
  235. package/src/lib/objects-in-viewport.test.ts +0 -789
  236. package/src/lib/objects-in-viewport.ts +0 -153
  237. package/src/lib/output.capture.ts +0 -58
  238. package/src/lib/parse.keypress-kitty.protocol.test.ts +0 -347
  239. package/src/lib/parse.keypress-kitty.test.ts +0 -712
  240. package/src/lib/parse.keypress-kitty.ts +0 -470
  241. package/src/lib/parse.keypress.test.ts +0 -2020
  242. package/src/lib/parse.keypress.ts +0 -480
  243. package/src/lib/parse.mouse.test.ts +0 -552
  244. package/src/lib/parse.mouse.ts +0 -232
  245. package/src/lib/paste.ts +0 -18
  246. package/src/lib/queue.ts +0 -65
  247. package/src/lib/render-geometry.ts +0 -36
  248. package/src/lib/renderable.validations.test.ts +0 -87
  249. package/src/lib/renderable.validations.ts +0 -83
  250. package/src/lib/scroll-acceleration.ts +0 -98
  251. package/src/lib/selection.ts +0 -261
  252. package/src/lib/singleton.ts +0 -34
  253. package/src/lib/stdin-parser.test.ts +0 -2439
  254. package/src/lib/stdin-parser.ts +0 -2007
  255. package/src/lib/styled-text.ts +0 -178
  256. package/src/lib/terminal-capability-detection.test.ts +0 -226
  257. package/src/lib/terminal-capability-detection.ts +0 -98
  258. package/src/lib/terminal-palette.test.ts +0 -954
  259. package/src/lib/terminal-palette.ts +0 -441
  260. package/src/lib/tree-sitter/assets/README.md +0 -119
  261. package/src/lib/tree-sitter/assets/update.ts +0 -360
  262. package/src/lib/tree-sitter/assets.d.ts +0 -9
  263. package/src/lib/tree-sitter/cache.test.ts +0 -291
  264. package/src/lib/tree-sitter/client.test.ts +0 -1434
  265. package/src/lib/tree-sitter/client.ts +0 -847
  266. package/src/lib/tree-sitter/default-parsers.ts +0 -133
  267. package/src/lib/tree-sitter/download-utils.ts +0 -148
  268. package/src/lib/tree-sitter/index.ts +0 -75
  269. package/src/lib/tree-sitter/parser.worker.ts +0 -1098
  270. package/src/lib/tree-sitter/parsers-config.ts +0 -81
  271. package/src/lib/tree-sitter/resolve-ft.test.ts +0 -55
  272. package/src/lib/tree-sitter/resolve-ft.ts +0 -189
  273. package/src/lib/tree-sitter/types.ts +0 -132
  274. package/src/lib/tree-sitter/update-assets.ts +0 -10
  275. package/src/lib/tree-sitter-styled-text.test.ts +0 -1253
  276. package/src/lib/tree-sitter-styled-text.ts +0 -314
  277. package/src/lib/validate-dir-name.ts +0 -55
  278. package/src/lib/yoga.options.test.ts +0 -628
  279. package/src/lib/yoga.options.ts +0 -346
  280. package/src/native-event-worker-repro.test.ts +0 -48
  281. package/src/native-event-worker-repro.worker.ts +0 -9
  282. package/src/native-handle.test.ts +0 -221
  283. package/src/platform/ffi.test.ts +0 -516
  284. package/src/platform/ffi.ts +0 -653
  285. package/src/platform/runtime.test.ts +0 -52
  286. package/src/platform/runtime.ts +0 -121
  287. package/src/platform/test.ts +0 -45
  288. package/src/platform/worker-handler-cleanup.fixture.ts +0 -12
  289. package/src/platform/worker-onmessage-startup.fixture.ts +0 -10
  290. package/src/platform/worker-startup.fixture.ts +0 -9
  291. package/src/platform/worker.node-test.ts +0 -153
  292. package/src/platform/worker.ts +0 -425
  293. package/src/plugins/core-slot.ts +0 -579
  294. package/src/plugins/registry.ts +0 -402
  295. package/src/plugins/types.ts +0 -46
  296. package/src/post/effects.ts +0 -948
  297. package/src/post/filters.ts +0 -512
  298. package/src/post/matrices.ts +0 -288
  299. package/src/renderables/ASCIIFont.ts +0 -219
  300. package/src/renderables/Box.test.ts +0 -474
  301. package/src/renderables/Box.ts +0 -353
  302. package/src/renderables/Code.test.ts +0 -2388
  303. package/src/renderables/Code.ts +0 -555
  304. package/src/renderables/Diff.regression.test.ts +0 -381
  305. package/src/renderables/Diff.test.ts +0 -3212
  306. package/src/renderables/Diff.ts +0 -1278
  307. package/src/renderables/EditBufferRenderable.test.ts +0 -288
  308. package/src/renderables/EditBufferRenderable.ts +0 -1152
  309. package/src/renderables/FrameBuffer.ts +0 -47
  310. package/src/renderables/Input.test.ts +0 -1310
  311. package/src/renderables/Input.ts +0 -273
  312. package/src/renderables/LineNumberRenderable.ts +0 -725
  313. package/src/renderables/Markdown.ts +0 -2144
  314. package/src/renderables/ScrollBar.ts +0 -423
  315. package/src/renderables/ScrollBox.ts +0 -903
  316. package/src/renderables/Select.test.ts +0 -1134
  317. package/src/renderables/Select.ts +0 -535
  318. package/src/renderables/Slider.test.ts +0 -456
  319. package/src/renderables/Slider.ts +0 -342
  320. package/src/renderables/TabSelect.test.ts +0 -238
  321. package/src/renderables/TabSelect.ts +0 -447
  322. package/src/renderables/Text.selection-buffer.test.ts +0 -118
  323. package/src/renderables/Text.test.ts +0 -2662
  324. package/src/renderables/Text.ts +0 -147
  325. package/src/renderables/TextBufferRenderable.ts +0 -504
  326. package/src/renderables/TextNode.test.ts +0 -1137
  327. package/src/renderables/TextNode.ts +0 -379
  328. package/src/renderables/TextTable.test.ts +0 -1411
  329. package/src/renderables/TextTable.ts +0 -1434
  330. package/src/renderables/Textarea.ts +0 -425
  331. package/src/renderables/TimeToFirstDraw.ts +0 -89
  332. package/src/renderables/__snapshots__/Code.test.ts.snap +0 -13
  333. package/src/renderables/__snapshots__/Diff.test.ts.snap +0 -785
  334. package/src/renderables/__snapshots__/Text.test.ts.snap +0 -421
  335. package/src/renderables/__snapshots__/TextTable.test.ts.snap +0 -215
  336. package/src/renderables/__tests__/Code.test.ts +0 -55
  337. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +0 -144
  338. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +0 -827
  339. package/src/renderables/__tests__/LineNumberRenderable.test.ts +0 -1906
  340. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +0 -85
  341. package/src/renderables/__tests__/Markdown.code-colors.test.ts +0 -288
  342. package/src/renderables/__tests__/Markdown.test.ts +0 -4175
  343. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +0 -87
  344. package/src/renderables/__tests__/Textarea.buffer.test.ts +0 -683
  345. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +0 -675
  346. package/src/renderables/__tests__/Textarea.editing.test.ts +0 -2120
  347. package/src/renderables/__tests__/Textarea.error-handling.test.ts +0 -35
  348. package/src/renderables/__tests__/Textarea.events.test.ts +0 -738
  349. package/src/renderables/__tests__/Textarea.highlights.test.ts +0 -590
  350. package/src/renderables/__tests__/Textarea.keybinding.test.ts +0 -3275
  351. package/src/renderables/__tests__/Textarea.paste.test.ts +0 -357
  352. package/src/renderables/__tests__/Textarea.rendering.test.ts +0 -1927
  353. package/src/renderables/__tests__/Textarea.scroll.test.ts +0 -733
  354. package/src/renderables/__tests__/Textarea.selection.test.ts +0 -1589
  355. package/src/renderables/__tests__/Textarea.stress.test.ts +0 -670
  356. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +0 -383
  357. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +0 -310
  358. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +0 -221
  359. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +0 -89
  360. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +0 -457
  361. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +0 -158
  362. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +0 -387
  363. package/src/renderables/__tests__/markdown-parser.test.ts +0 -241
  364. package/src/renderables/__tests__/renderable-test-utils.ts +0 -60
  365. package/src/renderables/composition/README.md +0 -8
  366. package/src/renderables/composition/VRenderable.ts +0 -32
  367. package/src/renderables/composition/constructs.ts +0 -127
  368. package/src/renderables/composition/vnode.ts +0 -277
  369. package/src/renderables/index.ts +0 -23
  370. package/src/renderables/markdown-parser.ts +0 -79
  371. package/src/renderer-theme-mode.ts +0 -199
  372. package/src/renderer.ts +0 -4975
  373. package/src/runtime-plugin-support-configure.ts +0 -63
  374. package/src/runtime-plugin-support.ts +0 -13
  375. package/src/runtime-plugin.ts +0 -626
  376. package/src/specs/terminal-startup.md +0 -64
  377. package/src/syntax-style.test.ts +0 -841
  378. package/src/syntax-style.ts +0 -274
  379. package/src/testing/README.md +0 -219
  380. package/src/testing/bun-test-node.ts +0 -880
  381. package/src/testing/capture-spans.test.ts +0 -216
  382. package/src/testing/integration.test.ts +0 -276
  383. package/src/testing/manual-clock.ts +0 -117
  384. package/src/testing/mock-keys.test.ts +0 -1378
  385. package/src/testing/mock-keys.ts +0 -457
  386. package/src/testing/mock-mouse.test.ts +0 -218
  387. package/src/testing/mock-mouse.ts +0 -247
  388. package/src/testing/mock-tree-sitter-client.ts +0 -81
  389. package/src/testing/spy.ts +0 -13
  390. package/src/testing/terminal-capabilities.ts +0 -47
  391. package/src/testing/test-recorder.test.ts +0 -410
  392. package/src/testing/test-recorder.ts +0 -133
  393. package/src/testing/test-renderer.ts +0 -375
  394. package/src/testing/test-renderer.wait.test.ts +0 -278
  395. package/src/testing/test-streams.ts +0 -31
  396. package/src/testing.ts +0 -9
  397. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +0 -481
  398. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +0 -19
  399. package/src/tests/__snapshots__/scrollbox.test.ts.snap +0 -29
  400. package/src/tests/absolute-positioning.snapshot.test.ts +0 -638
  401. package/src/tests/allocator-stats.test.ts +0 -53
  402. package/src/tests/audio.test.ts +0 -303
  403. package/src/tests/destroy-during-render.test.ts +0 -200
  404. package/src/tests/destroy-on-exit.fixture.ts +0 -36
  405. package/src/tests/destroy-on-exit.test.ts +0 -62
  406. package/src/tests/ffi-borrowed-pointer-callsites.test.ts +0 -153
  407. package/src/tests/hover-cursor.test.ts +0 -98
  408. package/src/tests/native-backed-measurement-lifecycle.test.ts +0 -133
  409. package/src/tests/native-backed-measurement-parity.test.ts +0 -322
  410. package/src/tests/native-span-feed-async.test.ts +0 -173
  411. package/src/tests/native-span-feed-close.test.ts +0 -120
  412. package/src/tests/native-span-feed-coverage.test.ts +0 -227
  413. package/src/tests/native-span-feed-edge-cases.test.ts +0 -384
  414. package/src/tests/native-span-feed-use-after-free.test.ts +0 -45
  415. package/src/tests/opacity.test.ts +0 -123
  416. package/src/tests/renderable.snapshot.test.ts +0 -524
  417. package/src/tests/renderable.test.ts +0 -1851
  418. package/src/tests/renderer.clock.test.ts +0 -270
  419. package/src/tests/renderer.console-startup.test.ts +0 -2151
  420. package/src/tests/renderer.control.test.ts +0 -458
  421. package/src/tests/renderer.core-slot-binding.test.ts +0 -962
  422. package/src/tests/renderer.cursor.test.ts +0 -26
  423. package/src/tests/renderer.custom-stdout.test.ts +0 -1112
  424. package/src/tests/renderer.destroy-during-render.test.ts +0 -147
  425. package/src/tests/renderer.focus-restore.test.ts +0 -257
  426. package/src/tests/renderer.focus.test.ts +0 -329
  427. package/src/tests/renderer.idle.test.ts +0 -219
  428. package/src/tests/renderer.input.test.ts +0 -2615
  429. package/src/tests/renderer.kitty-flags.test.ts +0 -195
  430. package/src/tests/renderer.lifecycle.test.ts +0 -34
  431. package/src/tests/renderer.mouse.test.ts +0 -1315
  432. package/src/tests/renderer.notifications.test.ts +0 -27
  433. package/src/tests/renderer.palette.test.ts +0 -1199
  434. package/src/tests/renderer.remote.test.ts +0 -247
  435. package/src/tests/renderer.render-stats.test.ts +0 -146
  436. package/src/tests/renderer.scrollback-surface.test.ts +0 -474
  437. package/src/tests/renderer.selection.test.ts +0 -138
  438. package/src/tests/renderer.slot-registry.test.ts +0 -684
  439. package/src/tests/renderer.tracker.test.ts +0 -202
  440. package/src/tests/renderer.useMouse.test.ts +0 -83
  441. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +0 -76
  442. package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +0 -48
  443. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +0 -43
  444. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +0 -67
  445. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +0 -72
  446. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +0 -44
  447. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +0 -85
  448. package/src/tests/runtime-plugin-path-alias.fixture.ts +0 -43
  449. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +0 -65
  450. package/src/tests/runtime-plugin-support-configure.fixture.ts +0 -31
  451. package/src/tests/runtime-plugin-support-late-addition.fixture.ts +0 -17
  452. package/src/tests/runtime-plugin-support.fixture.ts +0 -11
  453. package/src/tests/runtime-plugin-support.test.ts +0 -54
  454. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +0 -30
  455. package/src/tests/runtime-plugin.fixture.ts +0 -40
  456. package/src/tests/runtime-plugin.test.ts +0 -369
  457. package/src/tests/scrollbox-culling-bug.test.ts +0 -114
  458. package/src/tests/scrollbox-hitgrid-resize.test.ts +0 -136
  459. package/src/tests/scrollbox-hitgrid.test.ts +0 -909
  460. package/src/tests/scrollbox.test.ts +0 -1895
  461. package/src/tests/wrap-resize-perf.test.ts +0 -276
  462. package/src/tests/yoga-api-coverage.test.ts +0 -409
  463. package/src/tests/yoga-callback-stress.test.ts +0 -122
  464. package/src/tests/yoga-ffi.test.ts +0 -117
  465. package/src/tests/yoga-setters.test.ts +0 -921
  466. package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +0 -124
  467. package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +0 -27
  468. package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +0 -27
  469. package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +0 -27
  470. package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +0 -157
  471. package/src/tests/yoga-upstream/YGErrataTest.test.ts +0 -40
  472. package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +0 -23
  473. package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +0 -81
  474. package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +0 -31
  475. package/src/tests/yoga-upstream/YGMeasureTest.test.ts +0 -70
  476. package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +0 -1864
  477. package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +0 -5829
  478. package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +0 -2444
  479. package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +0 -293
  480. package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +0 -315
  481. package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +0 -231
  482. package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +0 -347
  483. package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +0 -256
  484. package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +0 -2747
  485. package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +0 -129
  486. package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +0 -109
  487. package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +0 -1097
  488. package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +0 -4562
  489. package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +0 -677
  490. package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +0 -2071
  491. package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +0 -3303
  492. package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +0 -582
  493. package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +0 -2150
  494. package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +0 -1963
  495. package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +0 -1446
  496. package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +0 -354
  497. package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +0 -1760
  498. package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +0 -1161
  499. package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +0 -207
  500. package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +0 -6338
  501. package/src/tests/yoga-upstream/tools/MeasureCounter.ts +0 -54
  502. package/src/tests/yoga-upstream/tools/utils.ts +0 -81
  503. package/src/text-buffer-view.test.ts +0 -722
  504. package/src/text-buffer-view.ts +0 -193
  505. package/src/text-buffer.test.ts +0 -512
  506. package/src/text-buffer.ts +0 -250
  507. package/src/types.ts +0 -199
  508. package/src/utils.ts +0 -88
  509. package/src/yoga.ts +0 -1146
  510. package/src/zig/ansi.zig +0 -502
  511. package/src/zig/audio.zig +0 -1206
  512. package/src/zig/bench/README.md +0 -50
  513. package/src/zig/bench/buffer-color-blending_bench.zig +0 -350
  514. package/src/zig/bench/buffer-draw-box_bench.zig +0 -786
  515. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +0 -894
  516. package/src/zig/bench/edit-buffer_bench.zig +0 -476
  517. package/src/zig/bench/editor-view_bench.zig +0 -169
  518. package/src/zig/bench/native-span-feed_bench.zig +0 -107
  519. package/src/zig/bench/rope-markers_bench.zig +0 -713
  520. package/src/zig/bench/rope_bench.zig +0 -514
  521. package/src/zig/bench/styled-text_bench.zig +0 -476
  522. package/src/zig/bench/text-buffer-coords_bench.zig +0 -362
  523. package/src/zig/bench/text-buffer-view_bench.zig +0 -459
  524. package/src/zig/bench/text-chunk-graphemes_bench.zig +0 -272
  525. package/src/zig/bench/utf8_bench.zig +0 -799
  526. package/src/zig/bench-utils.zig +0 -431
  527. package/src/zig/bench.zig +0 -223
  528. package/src/zig/buffer-methods.zig +0 -215
  529. package/src/zig/buffer.zig +0 -2515
  530. package/src/zig/build.zig +0 -700
  531. package/src/zig/build.zig.zon +0 -19
  532. package/src/zig/edit-buffer.zig +0 -900
  533. package/src/zig/editor-view.zig +0 -812
  534. package/src/zig/event-bus.zig +0 -28
  535. package/src/zig/event-emitter.zig +0 -65
  536. package/src/zig/file-logger.zig +0 -92
  537. package/src/zig/grapheme.zig +0 -838
  538. package/src/zig/handles.zig +0 -267
  539. package/src/zig/lib.zig +0 -2813
  540. package/src/zig/link.zig +0 -349
  541. package/src/zig/logger.zig +0 -43
  542. package/src/zig/mem-registry.zig +0 -126
  543. package/src/zig/miniaudio_shim.c +0 -21
  544. package/src/zig/native-renderable.zig +0 -112
  545. package/src/zig/native-span-feed-bench-lib.zig +0 -7
  546. package/src/zig/native-span-feed.zig +0 -747
  547. package/src/zig/renderer-output.zig +0 -677
  548. package/src/zig/renderer.zig +0 -2106
  549. package/src/zig/rope.zig +0 -1220
  550. package/src/zig/split-scrollback.zig +0 -88
  551. package/src/zig/syntax-style.zig +0 -174
  552. package/src/zig/terminal.zig +0 -1652
  553. package/src/zig/test.zig +0 -82
  554. package/src/zig/tests/README.md +0 -18
  555. package/src/zig/tests/ansi_test.zig +0 -18
  556. package/src/zig/tests/audio_test.zig +0 -439
  557. package/src/zig/tests/buffer-methods_test.zig +0 -1111
  558. package/src/zig/tests/buffer_test.zig +0 -2926
  559. package/src/zig/tests/edit-buffer-history_test.zig +0 -299
  560. package/src/zig/tests/edit-buffer_test.zig +0 -1688
  561. package/src/zig/tests/editor-view_test.zig +0 -3454
  562. package/src/zig/tests/event-emitter_test.zig +0 -249
  563. package/src/zig/tests/grapheme_test.zig +0 -1329
  564. package/src/zig/tests/handles_test.zig +0 -123
  565. package/src/zig/tests/link_test.zig +0 -220
  566. package/src/zig/tests/mem-registry_test.zig +0 -473
  567. package/src/zig/tests/memory_leak_regression_test.zig +0 -234
  568. package/src/zig/tests/native-renderable_test.zig +0 -16
  569. package/src/zig/tests/native-span-feed_test.zig +0 -1266
  570. package/src/zig/tests/renderer_test.zig +0 -2604
  571. package/src/zig/tests/rope-nested_test.zig +0 -712
  572. package/src/zig/tests/rope_fuzz_test.zig +0 -238
  573. package/src/zig/tests/rope_test.zig +0 -2362
  574. package/src/zig/tests/segment-merge.test.zig +0 -148
  575. package/src/zig/tests/split-scrollback_test.zig +0 -70
  576. package/src/zig/tests/syntax-style_test.zig +0 -558
  577. package/src/zig/tests/terminal_test.zig +0 -1489
  578. package/src/zig/tests/test-renderer.zig +0 -126
  579. package/src/zig/tests/text-buffer-drawing_test.zig +0 -3291
  580. package/src/zig/tests/text-buffer-highlights_test.zig +0 -706
  581. package/src/zig/tests/text-buffer-iterators_test.zig +0 -776
  582. package/src/zig/tests/text-buffer-segment_test.zig +0 -320
  583. package/src/zig/tests/text-buffer-selection_test.zig +0 -1056
  584. package/src/zig/tests/text-buffer-selection_viewport_test.zig +0 -358
  585. package/src/zig/tests/text-buffer-view_test.zig +0 -3672
  586. package/src/zig/tests/text-buffer_test.zig +0 -2191
  587. package/src/zig/tests/unicode-width-map.zon +0 -3909
  588. package/src/zig/tests/utf8_no_zwj_test.zig +0 -260
  589. package/src/zig/tests/utf8_test.zig +0 -4060
  590. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +0 -267
  591. package/src/zig/tests/utf8_wcwidth_test.zig +0 -357
  592. package/src/zig/tests/word-wrap-editing_test.zig +0 -498
  593. package/src/zig/tests/wrap-cache-perf_test.zig +0 -113
  594. package/src/zig/tests/yoga_test.zig +0 -65
  595. package/src/zig/text-buffer-iterators.zig +0 -496
  596. package/src/zig/text-buffer-segment.zig +0 -403
  597. package/src/zig/text-buffer-view.zig +0 -1428
  598. package/src/zig/text-buffer.zig +0 -1272
  599. package/src/zig/utf8.zig +0 -1954
  600. package/src/zig/utils.zig +0 -12
  601. package/src/zig/uucode-stub.zig +0 -76
  602. package/src/zig/vendor/miniaudio/miniaudio.h +0 -93468
  603. package/src/zig/yoga.zig +0 -658
  604. package/src/zig-structs.ts +0 -381
  605. package/src/zig.ts +0 -5260
  606. package/tsconfig.build.json +0 -17
  607. package/tsconfig.json +0 -27
  608. package/tsconfig.node-test.json +0 -138
  609. /package/{src/lib/tree-sitter/assets → assets}/javascript/highlights.scm +0 -0
  610. /package/{src/lib/tree-sitter/assets → assets}/javascript/tree-sitter-javascript.wasm +0 -0
  611. /package/{src/lib/tree-sitter/assets → assets}/markdown/highlights.scm +0 -0
  612. /package/{src/lib/tree-sitter/assets → assets}/markdown/injections.scm +0 -0
  613. /package/{src/lib/tree-sitter/assets → assets}/markdown/tree-sitter-markdown.wasm +0 -0
  614. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/highlights.scm +0 -0
  615. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  616. /package/{src/lib/tree-sitter/assets → assets}/typescript/highlights.scm +0 -0
  617. /package/{src/lib/tree-sitter/assets → assets}/typescript/tree-sitter-typescript.wasm +0 -0
  618. /package/{src/lib/tree-sitter/assets → assets}/zig/highlights.scm +0 -0
  619. /package/{src/lib/tree-sitter/assets → assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -1,2615 +0,0 @@
1
- import { test, expect, beforeEach, afterEach, describe } from "bun:test"
2
- import { decodePasteBytes } from "../lib/paste.js"
3
- import { nonAlphanumericKeys, type KeyEventType, type ParsedKey } from "../lib/parse.keypress.js"
4
- import { type KeyEvent } from "../lib/KeyHandler.js"
5
- import { Buffer } from "node:buffer"
6
- import { Renderable, type RenderableOptions } from "../Renderable.js"
7
- import { createTestRenderer, type TestRenderer, type TestRendererOptions } from "../testing/test-renderer.js"
8
- import { createTerminalCapabilities } from "../testing/terminal-capabilities.js"
9
- import { ManualClock } from "../testing/manual-clock.js"
10
- import type { RenderContext } from "../types.js"
11
- import type { RenderLib } from "../zig.js"
12
-
13
- let currentRenderer: TestRenderer
14
- let kittyRenderer: TestRenderer
15
- let mockProcessCapabilityResponse: any
16
- let mockGetTerminalCapabilities: RenderLib["getTerminalCapabilities"]
17
- let currentClock: ManualClock
18
- let kittyClock: ManualClock
19
-
20
- beforeEach(async () => {
21
- currentClock = new ManualClock()
22
- kittyClock = new ManualClock()
23
- ;({ renderer: currentRenderer } = await createTestRenderer({ clock: currentClock }))
24
- ;({ renderer: kittyRenderer } = await createTestRenderer({ kittyKeyboard: true, clock: kittyClock }))
25
-
26
- // Mock native capability functions to avoid interfering with the test terminal
27
- // @ts-expect-error - mocking for test
28
- mockProcessCapabilityResponse = currentRenderer.lib.processCapabilityResponse
29
- // @ts-expect-error - mocking for test
30
- mockGetTerminalCapabilities = currentRenderer.lib.getTerminalCapabilities
31
-
32
- // @ts-expect-error - mocking for test
33
- currentRenderer.lib.processCapabilityResponse = () => {}
34
- // @ts-expect-error - mocking for test
35
- currentRenderer.lib.getTerminalCapabilities = () => createTerminalCapabilities()
36
-
37
- // @ts-expect-error - mocking for test
38
- kittyRenderer.lib.processCapabilityResponse = () => {}
39
- // @ts-expect-error - mocking for test
40
- kittyRenderer.lib.getTerminalCapabilities = () => createTerminalCapabilities()
41
- })
42
-
43
- afterEach(() => {
44
- // Restore mocks
45
- // @ts-expect-error - restore mock
46
- currentRenderer.lib.processCapabilityResponse = mockProcessCapabilityResponse
47
- // @ts-expect-error - restore mock
48
- currentRenderer.lib.getTerminalCapabilities = mockGetTerminalCapabilities
49
- // @ts-expect-error - restore mock
50
- kittyRenderer.lib.processCapabilityResponse = mockProcessCapabilityResponse
51
- // @ts-expect-error - restore mock
52
- kittyRenderer.lib.getTerminalCapabilities = mockGetTerminalCapabilities
53
-
54
- currentRenderer.destroy()
55
- kittyRenderer.destroy()
56
- })
57
-
58
- async function triggerInput(sequence: string): Promise<KeyEvent> {
59
- return new Promise((resolve) => {
60
- const onKeypress = (parsedKey: KeyEvent) => {
61
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
62
- resolve(parsedKey)
63
- }
64
-
65
- currentRenderer.keyInput.once("keypress", onKeypress)
66
-
67
- currentRenderer.stdin.emit("data", Buffer.from(sequence))
68
- advanceCurrentClock()
69
- })
70
- }
71
-
72
- async function triggerKittyInput(sequence: string): Promise<KeyEvent> {
73
- return new Promise((resolve) => {
74
- const onKeypress = (parsedKey: KeyEvent) => {
75
- kittyRenderer.keyInput.removeListener("keypress", onKeypress)
76
- kittyRenderer.keyInput.removeListener("keyrelease", onKeypress)
77
- resolve(parsedKey)
78
- }
79
-
80
- kittyRenderer.keyInput.on("keypress", onKeypress)
81
- kittyRenderer.keyInput.on("keyrelease", onKeypress)
82
-
83
- kittyRenderer.stdin.emit("data", Buffer.from(sequence))
84
- advanceKittyClock()
85
- })
86
- }
87
-
88
- function advanceCurrentClock(ms: number = 20): void {
89
- currentClock.advance(ms)
90
- }
91
-
92
- function advanceKittyClock(ms: number = 20): void {
93
- kittyClock.advance(ms)
94
- }
95
-
96
- async function flushMicrotasks(): Promise<void> {
97
- await Promise.resolve()
98
- }
99
-
100
- class MouseTarget extends Renderable {
101
- constructor(context: RenderContext, options: RenderableOptions) {
102
- super(context, options)
103
- }
104
- }
105
-
106
- function advanceClock(clock: ManualClock, ms: number = 20): void {
107
- clock.advance(ms)
108
- }
109
-
110
- async function createRoutingRenderer(options: Partial<TestRendererOptions> = {}): Promise<{
111
- renderer: TestRenderer
112
- renderOnce: () => Promise<void>
113
- resize: (width: number, height: number) => void
114
- clock: ManualClock
115
- }> {
116
- const clock = new ManualClock()
117
- const { renderer, renderOnce, resize } = await createTestRenderer({
118
- width: 40,
119
- height: 20,
120
- useMouse: true,
121
- clock,
122
- ...options,
123
- })
124
-
125
- return { renderer, renderOnce, resize, clock }
126
- }
127
-
128
- async function createThemeQueryRenderer(): Promise<{
129
- renderer: TestRenderer
130
- queryThemeColorsCalls: { count: number }
131
- clock: ManualClock
132
- }> {
133
- const clock = new ManualClock()
134
- const stdout = {
135
- isTTY: true,
136
- columns: 80,
137
- rows: 24,
138
- write: () => true,
139
- getColorDepth: () => 24,
140
- } as any
141
-
142
- const { renderer } = await createTestRenderer({
143
- clock,
144
- stdout,
145
- useThread: false,
146
- })
147
-
148
- // @ts-expect-error - mocking for test
149
- renderer.lib.processCapabilityResponse = () => {}
150
- // @ts-expect-error - mocking for test
151
- renderer.lib.getTerminalCapabilities = () => createTerminalCapabilities()
152
-
153
- const queryThemeColorsCalls = { count: 0 }
154
- // @ts-expect-error - mocking for test
155
- renderer.lib.queryThemeColors = () => {
156
- queryThemeColorsCalls.count += 1
157
- }
158
-
159
- return { renderer, queryThemeColorsCalls, clock }
160
- }
161
-
162
- test("basic letters via keyInput events", async () => {
163
- const result = await triggerInput("a")
164
- expect(result).toMatchObject({
165
- name: "a",
166
- ctrl: false,
167
- meta: false,
168
- shift: false,
169
- option: false,
170
- number: false,
171
- sequence: "a",
172
- raw: "a",
173
- eventType: "press",
174
- })
175
-
176
- const resultShift = await triggerInput("A")
177
- expect(resultShift).toMatchObject({
178
- eventType: "press",
179
- name: "a",
180
- ctrl: false,
181
- meta: false,
182
- shift: true,
183
- option: false,
184
- number: false,
185
- sequence: "A",
186
- raw: "A",
187
- })
188
- })
189
-
190
- test("numbers via keyInput events", async () => {
191
- const result = await triggerInput("1")
192
- expect(result).toMatchObject({
193
- eventType: "press",
194
- name: "1",
195
- ctrl: false,
196
- meta: false,
197
- shift: false,
198
- option: false,
199
- number: true,
200
- sequence: "1",
201
- raw: "1",
202
- })
203
- })
204
-
205
- test("special keys via keyInput events", async () => {
206
- const resultReturn = await triggerInput("\r")
207
- expect(resultReturn).toMatchObject({
208
- eventType: "press",
209
- name: "return",
210
- ctrl: false,
211
- meta: false,
212
- shift: false,
213
- option: false,
214
- number: false,
215
- sequence: "\r",
216
- raw: "\r",
217
- })
218
-
219
- const resultEnter = await triggerInput("\n")
220
- expect(resultEnter).toMatchObject({
221
- eventType: "press",
222
- name: "linefeed",
223
- ctrl: false,
224
- meta: false,
225
- shift: false,
226
- option: false,
227
- number: false,
228
- sequence: "\n",
229
- raw: "\n",
230
- })
231
-
232
- const resultTab = await triggerInput("\t")
233
- expect(resultTab).toMatchObject({
234
- eventType: "press",
235
- name: "tab",
236
- ctrl: false,
237
- meta: false,
238
- shift: false,
239
- option: false,
240
- number: false,
241
- sequence: "\t",
242
- raw: "\t",
243
- })
244
-
245
- const resultBackspace = await triggerInput("\b")
246
- expect(resultBackspace).toMatchObject({
247
- eventType: "press",
248
- name: "backspace",
249
- ctrl: false,
250
- meta: false,
251
- shift: false,
252
- option: false,
253
- number: false,
254
- sequence: "\b",
255
- raw: "\b",
256
- })
257
-
258
- const resultEscape = await triggerInput("\x1b")
259
- expect(resultEscape).toMatchObject({
260
- name: "escape",
261
- ctrl: false,
262
- meta: false,
263
- shift: false,
264
- option: false,
265
- number: false,
266
- sequence: "\x1b",
267
- raw: "\x1b",
268
- eventType: "press",
269
- })
270
-
271
- const resultSpace = await triggerInput(" ")
272
- expect(resultSpace).toMatchObject({
273
- eventType: "press",
274
- name: "space",
275
- ctrl: false,
276
- meta: false,
277
- shift: false,
278
- option: false,
279
- number: false,
280
- sequence: " ",
281
- raw: " ",
282
- })
283
- })
284
-
285
- test("ctrl+letter combinations via keyInput events", async () => {
286
- const resultCtrlA = await triggerInput("\x01")
287
- expect(resultCtrlA).toMatchObject({
288
- eventType: "press",
289
- name: "a",
290
- ctrl: true,
291
- meta: false,
292
- shift: false,
293
- option: false,
294
- number: false,
295
- sequence: "\x01",
296
- raw: "\x01",
297
- })
298
-
299
- const resultCtrlZ = await triggerInput("\x1a")
300
- expect(resultCtrlZ).toMatchObject({
301
- eventType: "press",
302
- name: "z",
303
- ctrl: true,
304
- meta: false,
305
- shift: false,
306
- option: false,
307
- number: false,
308
- sequence: "\x1a",
309
- raw: "\x1a",
310
- })
311
- })
312
-
313
- test("meta+character combinations via keyInput events", async () => {
314
- const resultMetaA = await triggerInput("\x1ba")
315
- expect(resultMetaA).toMatchObject({
316
- eventType: "press",
317
- name: "a",
318
- ctrl: false,
319
- meta: true,
320
- shift: false,
321
- option: false,
322
- number: false,
323
- sequence: "\x1ba",
324
- raw: "\x1ba",
325
- })
326
-
327
- const resultMetaShiftA = await triggerInput("\x1bA")
328
- expect(resultMetaShiftA).toMatchObject({
329
- eventType: "press",
330
- name: "A",
331
- ctrl: false,
332
- meta: true,
333
- shift: true,
334
- option: false,
335
- number: false,
336
- sequence: "\x1bA",
337
- raw: "\x1bA",
338
- })
339
- })
340
-
341
- test("function keys via keyInput events", async () => {
342
- const resultF1 = await triggerInput("\x1bOP")
343
- expect(resultF1).toMatchObject({
344
- eventType: "press",
345
- name: "f1",
346
- ctrl: false,
347
- meta: false,
348
- shift: false,
349
- option: false,
350
- number: false,
351
- sequence: "\x1bOP",
352
- raw: "\x1bOP",
353
- code: "OP",
354
- })
355
-
356
- const resultF1Alt = await triggerInput("\x1b[11~")
357
- expect(resultF1Alt).toMatchObject({
358
- eventType: "press",
359
- name: "f1",
360
- ctrl: false,
361
- meta: false,
362
- shift: false,
363
- option: false,
364
- number: false,
365
- sequence: "\x1b[11~",
366
- raw: "\x1b[11~",
367
- code: "[11~",
368
- })
369
-
370
- const resultF12 = await triggerInput("\x1b[24~")
371
- expect(resultF12).toMatchObject({
372
- eventType: "press",
373
- name: "f12",
374
- ctrl: false,
375
- meta: false,
376
- shift: false,
377
- option: false,
378
- number: false,
379
- sequence: "\x1b[24~",
380
- raw: "\x1b[24~",
381
- code: "[24~",
382
- })
383
- })
384
-
385
- test("arrow keys via keyInput events", async () => {
386
- const resultUp = await triggerInput("\x1b[A")
387
- expect(resultUp).toMatchObject({
388
- eventType: "press",
389
- name: "up",
390
- ctrl: false,
391
- meta: false,
392
- shift: false,
393
- option: false,
394
- number: false,
395
- sequence: "\x1b[A",
396
- raw: "\x1b[A",
397
- code: "[A",
398
- })
399
-
400
- const resultDown = await triggerInput("\x1b[B")
401
- expect(resultDown).toMatchObject({
402
- eventType: "press",
403
- name: "down",
404
- ctrl: false,
405
- meta: false,
406
- shift: false,
407
- option: false,
408
- number: false,
409
- sequence: "\x1b[B",
410
- raw: "\x1b[B",
411
- code: "[B",
412
- })
413
-
414
- const resultRight = await triggerInput("\x1b[C")
415
- expect(resultRight).toMatchObject({
416
- eventType: "press",
417
- name: "right",
418
- ctrl: false,
419
- meta: false,
420
- shift: false,
421
- option: false,
422
- number: false,
423
- sequence: "\x1b[C",
424
- raw: "\x1b[C",
425
- code: "[C",
426
- })
427
-
428
- const resultLeft = await triggerInput("\x1b[D")
429
- expect(resultLeft).toMatchObject({
430
- eventType: "press",
431
- name: "left",
432
- ctrl: false,
433
- meta: false,
434
- shift: false,
435
- option: false,
436
- number: false,
437
- sequence: "\x1b[D",
438
- raw: "\x1b[D",
439
- code: "[D",
440
- })
441
- })
442
-
443
- test("navigation keys via keyInput events", async () => {
444
- const resultHome = await triggerInput("\x1b[H")
445
- expect(resultHome).toMatchObject({
446
- eventType: "press",
447
- name: "home",
448
- ctrl: false,
449
- meta: false,
450
- shift: false,
451
- option: false,
452
- number: false,
453
- sequence: "\x1b[H",
454
- raw: "\x1b[H",
455
- code: "[H",
456
- })
457
-
458
- const resultEnd = await triggerInput("\x1b[F")
459
- expect(resultEnd).toMatchObject({
460
- eventType: "press",
461
- name: "end",
462
- ctrl: false,
463
- meta: false,
464
- shift: false,
465
- option: false,
466
- number: false,
467
- sequence: "\x1b[F",
468
- raw: "\x1b[F",
469
- code: "[F",
470
- })
471
-
472
- const resultPageUp = await triggerInput("\x1b[5~")
473
- expect(resultPageUp).toMatchObject({
474
- eventType: "press",
475
- name: "pageup",
476
- ctrl: false,
477
- meta: false,
478
- shift: false,
479
- option: false,
480
- number: false,
481
- sequence: "\x1b[5~",
482
- raw: "\x1b[5~",
483
- code: "[5~",
484
- })
485
-
486
- const resultPageDown = await triggerInput("\x1b[6~")
487
- expect(resultPageDown).toMatchObject({
488
- eventType: "press",
489
- name: "pagedown",
490
- ctrl: false,
491
- meta: false,
492
- shift: false,
493
- option: false,
494
- number: false,
495
- sequence: "\x1b[6~",
496
- raw: "\x1b[6~",
497
- code: "[6~",
498
- })
499
- })
500
-
501
- test("modifier combinations via keyInput events", async () => {
502
- const resultShiftUp = await triggerInput("\x1b[1;2A")
503
- expect(resultShiftUp).toMatchObject({
504
- eventType: "press",
505
- name: "up",
506
- ctrl: false,
507
- meta: false,
508
- shift: true,
509
- option: false,
510
- number: false,
511
- sequence: "\x1b[1;2A",
512
- raw: "\x1b[1;2A",
513
- code: "[A",
514
- })
515
-
516
- const resultMetaAltUp = await triggerInput("\x1b[1;4A")
517
- expect(resultMetaAltUp).toMatchObject({
518
- eventType: "press",
519
- name: "up",
520
- ctrl: false,
521
- meta: true,
522
- shift: true,
523
- option: true,
524
- number: false,
525
- sequence: "\x1b[1;4A",
526
- raw: "\x1b[1;4A",
527
- code: "[A",
528
- })
529
-
530
- const resultAllModsUp = await triggerInput("\x1b[1;8A")
531
- expect(resultAllModsUp).toMatchObject({
532
- eventType: "press",
533
- name: "up",
534
- ctrl: true,
535
- meta: true,
536
- shift: true,
537
- option: true,
538
- number: false,
539
- sequence: "\x1b[1;8A",
540
- raw: "\x1b[1;8A",
541
- code: "[A",
542
- })
543
- })
544
-
545
- test("delete key via keyInput events", async () => {
546
- const resultDelete = await triggerInput("\x1b[3~")
547
- expect(resultDelete).toMatchObject({
548
- eventType: "press",
549
- name: "delete",
550
- ctrl: false,
551
- meta: false,
552
- shift: false,
553
- option: false,
554
- number: false,
555
- sequence: "\x1b[3~",
556
- raw: "\x1b[3~",
557
- code: "[3~",
558
- })
559
- })
560
-
561
- test("Buffer input via keyInput events", async () => {
562
- // Test with Buffer input by emitting buffer data directly
563
- const result = await new Promise<KeyEvent>((resolve) => {
564
- const onKeypress = (parsedKey: KeyEvent) => {
565
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
566
- resolve(parsedKey)
567
- }
568
-
569
- currentRenderer.keyInput.on("keypress", onKeypress)
570
- currentRenderer.stdin.emit("data", Buffer.from("a"))
571
- })
572
-
573
- expect(result).toMatchObject({
574
- eventType: "press",
575
- name: "a",
576
- ctrl: false,
577
- meta: false,
578
- shift: false,
579
- option: false,
580
- number: false,
581
- sequence: "a",
582
- raw: "a",
583
- })
584
- })
585
-
586
- test("special characters via keyInput events", async () => {
587
- const resultExclamation = await triggerInput("!")
588
- expect(resultExclamation).toMatchObject({
589
- eventType: "press",
590
- name: "!",
591
- ctrl: false,
592
- meta: false,
593
- shift: false,
594
- option: false,
595
- number: false,
596
- sequence: "!",
597
- raw: "!",
598
- })
599
-
600
- const resultAt = await triggerInput("@")
601
- expect(resultAt).toMatchObject({
602
- eventType: "press",
603
- name: "@",
604
- ctrl: false,
605
- meta: false,
606
- shift: false,
607
- option: false,
608
- number: false,
609
- sequence: "@",
610
- raw: "@",
611
- })
612
- })
613
-
614
- test("meta space and escape combinations via keyInput events", async () => {
615
- const resultMetaSpace = await triggerInput("\x1b ")
616
- expect(resultMetaSpace).toMatchObject({
617
- eventType: "press",
618
- name: "space",
619
- ctrl: false,
620
- meta: true,
621
- shift: false,
622
- option: false,
623
- number: false,
624
- sequence: "\x1b ",
625
- raw: "\x1b ",
626
- })
627
-
628
- const resultDoubleEscape = await triggerInput("\x1b\x1b")
629
- expect(resultDoubleEscape).toMatchObject({
630
- eventType: "press",
631
- name: "escape",
632
- ctrl: false,
633
- meta: true,
634
- shift: false,
635
- option: false,
636
- number: false,
637
- sequence: "\x1b\x1b",
638
- raw: "\x1b\x1b",
639
- })
640
- })
641
-
642
- // ===== KITTY KEYBOARD PROTOCOL INTEGRATION TESTS =====
643
-
644
- test("Kitty keyboard basic key via keyInput events", async () => {
645
- const result = await triggerKittyInput("\x1b[97u")
646
- expect(result).toMatchObject({
647
- eventType: "press",
648
- name: "a",
649
- ctrl: false,
650
- meta: false,
651
- shift: false,
652
- option: false,
653
- number: false,
654
- sequence: "a",
655
- raw: "\x1b[97u",
656
- super: false,
657
- hyper: false,
658
- capsLock: false,
659
- numLock: false,
660
- })
661
- })
662
-
663
- test("Kitty keyboard shift+a via keyInput events", async () => {
664
- const result = await triggerKittyInput("\x1b[97:65;2u")
665
- expect(result).toMatchObject({
666
- eventType: "press",
667
- name: "a",
668
- ctrl: false,
669
- meta: false,
670
- shift: true,
671
- option: false,
672
- number: false,
673
- sequence: "A",
674
- raw: "\x1b[97:65;2u",
675
- super: false,
676
- hyper: false,
677
- capsLock: false,
678
- numLock: false,
679
- })
680
- })
681
-
682
- test("Kitty keyboard ctrl+a via keyInput events", async () => {
683
- const result = await triggerKittyInput("\x1b[97;5u")
684
- expect(result).toMatchObject({
685
- eventType: "press",
686
- name: "a",
687
- ctrl: true,
688
- meta: false,
689
- shift: false,
690
- option: false,
691
- number: false,
692
- sequence: "a",
693
- raw: "\x1b[97;5u",
694
- super: false,
695
- hyper: false,
696
- capsLock: false,
697
- numLock: false,
698
- })
699
- })
700
-
701
- test("Kitty keyboard Ctrl+C with alternate base layout still exits the renderer", async () => {
702
- const clock = new ManualClock()
703
- const { renderer } = await createTestRenderer({ kittyKeyboard: true, clock })
704
-
705
- try {
706
- // Simulate Ctrl+C from a non-Latin IME. Kitty reports the produced
707
- // character (`ㅊ`) plus the base-layout key (`c`).
708
- const keypress = new Promise<KeyEvent>((resolve) => {
709
- renderer.keyInput.once("keypress", resolve)
710
- })
711
-
712
- renderer.stdin.emit("data", Buffer.from("\x1b[12618::99;5u"))
713
- clock.advance(20)
714
-
715
- const event = await keypress
716
- expect(event).toMatchObject({
717
- name: "ㅊ",
718
- ctrl: true,
719
- baseCode: 99,
720
- })
721
-
722
- await new Promise<void>((resolve) => process.nextTick(resolve))
723
- expect(renderer.isDestroyed).toBe(true)
724
- } finally {
725
- if (!renderer.isDestroyed) {
726
- renderer.destroy()
727
- }
728
- }
729
- })
730
-
731
- test("Kitty keyboard alt+a via keyInput events", async () => {
732
- const result = await triggerKittyInput("\x1b[97;3u")
733
- expect(result).toMatchObject({
734
- eventType: "press",
735
- name: "a",
736
- ctrl: false,
737
- meta: true,
738
- shift: false,
739
- option: true,
740
- number: false,
741
- sequence: "a",
742
- raw: "\x1b[97;3u",
743
- super: false,
744
- hyper: false,
745
- capsLock: false,
746
- numLock: false,
747
- })
748
- })
749
-
750
- test("Kitty keyboard function key via keyInput events", async () => {
751
- const result = await triggerKittyInput("\x1b[57364u")
752
- expect(result).toMatchObject({
753
- eventType: "press",
754
- name: "f1",
755
- ctrl: false,
756
- meta: false,
757
- shift: false,
758
- option: false,
759
- number: false,
760
- sequence: "\x1b[57364u",
761
- raw: "\x1b[57364u",
762
- code: "[57364u",
763
- super: false,
764
- hyper: false,
765
- capsLock: false,
766
- numLock: false,
767
- })
768
- })
769
-
770
- test("Kitty keyboard arrow key via keyInput events", async () => {
771
- const result = await triggerKittyInput("\x1b[57352u")
772
- expect(result).toMatchObject({
773
- eventType: "press",
774
- name: "up",
775
- ctrl: false,
776
- meta: false,
777
- shift: false,
778
- option: false,
779
- number: false,
780
- sequence: "\x1b[57352u",
781
- raw: "\x1b[57352u",
782
- code: "[57352u",
783
- super: false,
784
- hyper: false,
785
- capsLock: false,
786
- numLock: false,
787
- })
788
- })
789
-
790
- test("Kitty keyboard shift+space via keyInput events", async () => {
791
- const result = await triggerKittyInput("\x1b[32;2u")
792
- expect(result).toMatchObject({
793
- eventType: "press",
794
- name: "space",
795
- ctrl: false,
796
- meta: false,
797
- shift: true,
798
- option: false,
799
- number: false,
800
- sequence: " ",
801
- raw: "\x1b[32;2u",
802
- super: false,
803
- hyper: false,
804
- capsLock: false,
805
- numLock: false,
806
- })
807
- })
808
-
809
- test("Kitty keyboard ctrl+space via keyInput events", async () => {
810
- const result = await triggerKittyInput("\x1b[32;5u")
811
- expect(result).toMatchObject({
812
- eventType: "press",
813
- name: "space",
814
- ctrl: true,
815
- meta: false,
816
- shift: false,
817
- option: false,
818
- number: false,
819
- sequence: " ",
820
- raw: "\x1b[32;5u",
821
- super: false,
822
- hyper: false,
823
- capsLock: false,
824
- numLock: false,
825
- })
826
- })
827
-
828
- test("Kitty keyboard event types via keyInput events", async () => {
829
- // Press event (explicit)
830
- const pressExplicit = await triggerKittyInput("\x1b[97;1:1u")
831
- expect(pressExplicit).toMatchObject({
832
- eventType: "press",
833
- name: "a",
834
- ctrl: false,
835
- meta: false,
836
- shift: false,
837
- option: false,
838
- number: false,
839
- sequence: "a",
840
- raw: "\x1b[97;1:1u",
841
- super: false,
842
- hyper: false,
843
- capsLock: false,
844
- numLock: false,
845
- })
846
-
847
- // Press event (default when no event type specified)
848
- const pressDefault = await triggerKittyInput("\x1b[97u")
849
- expect(pressDefault).toMatchObject({
850
- eventType: "press",
851
- name: "a",
852
- ctrl: false,
853
- meta: false,
854
- shift: false,
855
- option: false,
856
- number: false,
857
- sequence: "a",
858
- raw: "\x1b[97u",
859
- super: false,
860
- hyper: false,
861
- capsLock: false,
862
- numLock: false,
863
- })
864
-
865
- // Press event (modifier without event type)
866
- const pressWithModifier = await triggerKittyInput("\x1b[97;5u") // Ctrl+a
867
- expect(pressWithModifier).toMatchObject({
868
- eventType: "press",
869
- name: "a",
870
- ctrl: true,
871
- meta: false,
872
- shift: false,
873
- option: false,
874
- number: false,
875
- sequence: "a",
876
- raw: "\x1b[97;5u",
877
- super: false,
878
- hyper: false,
879
- capsLock: false,
880
- numLock: false,
881
- })
882
-
883
- // Repeat event (emitted as press with repeated=true)
884
- const repeat = await triggerKittyInput("\x1b[97;1:2u")
885
- expect(repeat).toMatchObject({
886
- eventType: "press",
887
- repeated: true,
888
- name: "a",
889
- ctrl: false,
890
- meta: false,
891
- shift: false,
892
- option: false,
893
- number: false,
894
- sequence: "a",
895
- raw: "\x1b[97;1:2u",
896
- super: false,
897
- hyper: false,
898
- capsLock: false,
899
- numLock: false,
900
- })
901
-
902
- // Release event
903
- const release = await triggerKittyInput("\x1b[97;1:3u")
904
- expect(release).toMatchObject({
905
- eventType: "release",
906
- name: "a",
907
- ctrl: false,
908
- meta: false,
909
- shift: false,
910
- option: false,
911
- number: false,
912
- sequence: "a",
913
- raw: "\x1b[97;1:3u",
914
- super: false,
915
- hyper: false,
916
- capsLock: false,
917
- numLock: false,
918
- })
919
-
920
- // Repeat event with modifier (emitted as press with repeated=true)
921
- const repeatWithCtrl = await triggerKittyInput("\x1b[97;5:2u")
922
- expect(repeatWithCtrl).toMatchObject({
923
- eventType: "press",
924
- repeated: true,
925
- name: "a",
926
- ctrl: true,
927
- meta: false,
928
- shift: false,
929
- option: false,
930
- number: false,
931
- sequence: "a",
932
- raw: "\x1b[97;5:2u",
933
- super: false,
934
- hyper: false,
935
- capsLock: false,
936
- numLock: false,
937
- })
938
-
939
- // Release event with modifier
940
- const releaseWithShift = await triggerKittyInput("\x1b[97;2:3u")
941
- expect(releaseWithShift).toMatchObject({
942
- eventType: "release",
943
- name: "a",
944
- ctrl: false,
945
- meta: false,
946
- shift: true,
947
- option: false,
948
- number: false,
949
- sequence: "A",
950
- raw: "\x1b[97;2:3u",
951
- super: false,
952
- hyper: false,
953
- capsLock: false,
954
- numLock: false,
955
- })
956
- })
957
-
958
- test("Kitty keyboard with text via keyInput events", async () => {
959
- const result = await triggerKittyInput("\x1b[97;1;97u")
960
- expect(result).toMatchObject({
961
- eventType: "press",
962
- name: "a",
963
- ctrl: false,
964
- meta: false,
965
- shift: false,
966
- option: false,
967
- number: false,
968
- sequence: "a",
969
- raw: "\x1b[97;1;97u",
970
- super: false,
971
- hyper: false,
972
- capsLock: false,
973
- numLock: false,
974
- })
975
- })
976
-
977
- test("Kitty keyboard ctrl+shift+a via keyInput events", async () => {
978
- const result = await triggerKittyInput("\x1b[97;6u")
979
- expect(result).toMatchObject({
980
- eventType: "press",
981
- name: "a",
982
- ctrl: true,
983
- meta: false,
984
- shift: true,
985
- option: false,
986
- number: false,
987
- sequence: "A",
988
- raw: "\x1b[97;6u",
989
- super: false,
990
- hyper: false,
991
- capsLock: false,
992
- numLock: false,
993
- })
994
- })
995
-
996
- test("Kitty keyboard alt+shift+a via keyInput events", async () => {
997
- const result = await triggerKittyInput("\x1b[97;4u")
998
- expect(result).toMatchObject({
999
- eventType: "press",
1000
- name: "a",
1001
- ctrl: false,
1002
- meta: true,
1003
- shift: true,
1004
- option: true,
1005
- number: false,
1006
- sequence: "A",
1007
- raw: "\x1b[97;4u",
1008
- super: false,
1009
- hyper: false,
1010
- capsLock: false,
1011
- numLock: false,
1012
- })
1013
- })
1014
-
1015
- test("Kitty keyboard super+a via keyInput events", async () => {
1016
- const result = await triggerKittyInput("\x1b[97;9u") // modifier 9 - 1 = 8 = super
1017
- expect(result).toMatchObject({
1018
- eventType: "press",
1019
- name: "a",
1020
- ctrl: false,
1021
- meta: false,
1022
- shift: false,
1023
- option: false,
1024
- number: false,
1025
- sequence: "a",
1026
- raw: "\x1b[97;9u",
1027
- super: true,
1028
- hyper: false,
1029
- capsLock: false,
1030
- numLock: false,
1031
- })
1032
- })
1033
-
1034
- test("Kitty keyboard hyper+a via keyInput events", async () => {
1035
- const result = await triggerKittyInput("\x1b[97;17u") // modifier 17 - 1 = 16 = hyper
1036
- expect(result).toMatchObject({
1037
- eventType: "press",
1038
- name: "a",
1039
- ctrl: false,
1040
- meta: false,
1041
- shift: false,
1042
- option: false,
1043
- number: false,
1044
- sequence: "a",
1045
- raw: "\x1b[97;17u",
1046
- super: false,
1047
- hyper: true,
1048
- capsLock: false,
1049
- numLock: false,
1050
- })
1051
- })
1052
-
1053
- test("Kitty keyboard caps lock via keyInput events", async () => {
1054
- const result = await triggerKittyInput("\x1b[97;65u") // modifier 65 - 1 = 64 = caps lock
1055
- expect(result).toMatchObject({
1056
- eventType: "press",
1057
- name: "a",
1058
- ctrl: false,
1059
- meta: false,
1060
- shift: false,
1061
- option: false,
1062
- number: false,
1063
- sequence: "a",
1064
- raw: "\x1b[97;65u",
1065
- super: false,
1066
- hyper: false,
1067
- capsLock: true,
1068
- numLock: false,
1069
- })
1070
- })
1071
-
1072
- test("Kitty keyboard num lock via keyInput events", async () => {
1073
- const result = await triggerKittyInput("\x1b[97;129u") // modifier 129 - 1 = 128 = num lock
1074
- expect(result).toMatchObject({
1075
- eventType: "press",
1076
- name: "a",
1077
- ctrl: false,
1078
- meta: false,
1079
- shift: false,
1080
- option: false,
1081
- number: false,
1082
- sequence: "a",
1083
- raw: "\x1b[97;129u",
1084
- super: false,
1085
- hyper: false,
1086
- capsLock: false,
1087
- numLock: true,
1088
- })
1089
- })
1090
-
1091
- test("Kitty keyboard unicode character via keyInput events", async () => {
1092
- const result = await triggerKittyInput("\x1b[233u") // é
1093
- expect(result).toMatchObject({
1094
- eventType: "press",
1095
- name: "é",
1096
- ctrl: false,
1097
- meta: false,
1098
- shift: false,
1099
- option: false,
1100
- number: false,
1101
- sequence: "é",
1102
- raw: "\x1b[233u",
1103
- super: false,
1104
- hyper: false,
1105
- capsLock: false,
1106
- numLock: false,
1107
- })
1108
- })
1109
-
1110
- test("Kitty keyboard emoji via keyInput events", async () => {
1111
- const result = await triggerKittyInput("\x1b[128512u") // 😀
1112
- expect(result).toMatchObject({
1113
- eventType: "press",
1114
- name: "😀",
1115
- ctrl: false,
1116
- meta: false,
1117
- shift: false,
1118
- option: false,
1119
- number: false,
1120
- sequence: "😀",
1121
- raw: "\x1b[128512u",
1122
- super: false,
1123
- hyper: false,
1124
- capsLock: false,
1125
- numLock: false,
1126
- })
1127
- })
1128
-
1129
- test("Kitty keyboard keypad keys via keyInput events", async () => {
1130
- const printableKeys = [
1131
- ["\x1b[57399u", "kp0", "0"],
1132
- ["\x1b[57400u", "kp1", "1"],
1133
- ["\x1b[57401u", "kp2", "2"],
1134
- ["\x1b[57402u", "kp3", "3"],
1135
- ["\x1b[57403u", "kp4", "4"],
1136
- ["\x1b[57404u", "kp5", "5"],
1137
- ["\x1b[57405u", "kp6", "6"],
1138
- ["\x1b[57406u", "kp7", "7"],
1139
- ["\x1b[57407u", "kp8", "8"],
1140
- ["\x1b[57408u", "kp9", "9"],
1141
- ["\x1b[57409u", "kpdecimal", "."],
1142
- ["\x1b[57410u", "kpdivide", "/"],
1143
- ["\x1b[57411u", "kpmultiply", "*"],
1144
- ["\x1b[57412u", "kpminus", "-"],
1145
- ["\x1b[57413u", "kpplus", "+"],
1146
- ["\x1b[57415u", "kpequal", "="],
1147
- ["\x1b[57416u", "kpseparator", ","],
1148
- ] as const
1149
-
1150
- for (const [sequence, name, text] of printableKeys) {
1151
- const key = await triggerKittyInput(sequence)
1152
- expect(key).toMatchObject({ name, sequence: text })
1153
- }
1154
-
1155
- const kpEnter = await triggerKittyInput("\x1b[57414u")
1156
- expect(kpEnter?.name).toBe("kpenter")
1157
- expect(kpEnter?.sequence).toBe("\x1b[57414u")
1158
-
1159
- const kpleft = await triggerKittyInput("\x1b[57417u")
1160
- expect(kpleft?.name).toBe("kpleft")
1161
- expect(kpleft?.sequence).toBe("\x1b[57417u")
1162
-
1163
- const explicitText = await triggerKittyInput("\x1b[57400;1;120u")
1164
- expect(explicitText).toMatchObject({ name: "kp1", sequence: "x" })
1165
- })
1166
-
1167
- test("Kitty keyboard media keys via keyInput events", async () => {
1168
- const play = await triggerKittyInput("\x1b[57428u")
1169
- expect(play?.name).toBe("mediaplay")
1170
-
1171
- const volumeUp = await triggerKittyInput("\x1b[57439u")
1172
- expect(volumeUp?.name).toBe("volumeup")
1173
- })
1174
-
1175
- test("Kitty keyboard modifier keys via keyInput events", async () => {
1176
- const leftShift = await triggerKittyInput("\x1b[57441u")
1177
- expect(leftShift?.name).toBe("leftshift")
1178
- expect(leftShift?.eventType).toBe("press")
1179
-
1180
- const rightCtrl = await triggerKittyInput("\x1b[57448u")
1181
- expect(rightCtrl?.name).toBe("rightctrl")
1182
- expect(rightCtrl?.eventType).toBe("press")
1183
- })
1184
-
1185
- test("Kitty keyboard function keys with event types via keyInput events", async () => {
1186
- // F1 press
1187
- const f1Press = await triggerKittyInput("\x1b[57364u")
1188
- expect(f1Press.name).toBe("f1")
1189
- expect(f1Press.eventType).toBe("press")
1190
- expect(f1Press.super ?? false).toBe(false)
1191
- expect(f1Press.hyper ?? false).toBe(false)
1192
- expect(f1Press.capsLock ?? false).toBe(false)
1193
- expect(f1Press.numLock ?? false).toBe(false)
1194
-
1195
- // F1 repeat (emitted as press with repeated=true)
1196
- const f1Repeat = await triggerKittyInput("\x1b[57364;1:2u")
1197
- expect(f1Repeat.name).toBe("f1")
1198
- expect(f1Repeat.eventType).toBe("press")
1199
- expect((f1Repeat as any).repeated).toBe(true)
1200
- expect(f1Repeat.super ?? false).toBe(false)
1201
- expect(f1Repeat.hyper ?? false).toBe(false)
1202
- expect(f1Repeat.capsLock ?? false).toBe(false)
1203
- expect(f1Repeat.numLock ?? false).toBe(false)
1204
-
1205
- // F1 release
1206
- const f1Release = await triggerKittyInput("\x1b[57364;1:3u")
1207
- expect(f1Release.name).toBe("f1")
1208
- expect(f1Release.eventType).toBe("release")
1209
- expect(f1Release.super ?? false).toBe(false)
1210
- expect(f1Release.hyper ?? false).toBe(false)
1211
- expect(f1Release.capsLock ?? false).toBe(false)
1212
- expect(f1Release.numLock ?? false).toBe(false)
1213
- })
1214
-
1215
- test("Kitty keyboard arrow keys with event types via keyInput events", async () => {
1216
- // Up arrow press
1217
- const upPress = await triggerKittyInput("\x1b[57352u")
1218
- expect(upPress.name).toBe("up")
1219
- expect(upPress.eventType).toBe("press")
1220
- expect(upPress.super ?? false).toBe(false)
1221
- expect(upPress.hyper ?? false).toBe(false)
1222
- expect(upPress.capsLock ?? false).toBe(false)
1223
- expect(upPress.numLock ?? false).toBe(false)
1224
-
1225
- // Up arrow repeat with Ctrl (emitted as press with repeated=true)
1226
- const upRepeatCtrl = await triggerKittyInput("\x1b[57352;5:2u")
1227
- expect(upRepeatCtrl.name).toBe("up")
1228
- expect(upRepeatCtrl.ctrl).toBe(true)
1229
- expect(upRepeatCtrl.eventType).toBe("press")
1230
- expect((upRepeatCtrl as any).repeated).toBe(true)
1231
- expect(upRepeatCtrl.super).toBe(false)
1232
- expect(upRepeatCtrl.hyper).toBe(false)
1233
- expect(upRepeatCtrl.capsLock).toBe(false)
1234
- expect(upRepeatCtrl.numLock).toBe(false)
1235
-
1236
- // Down arrow release
1237
- const downRelease = await triggerKittyInput("\x1b[57353;1:3u")
1238
- expect(downRelease.name).toBe("down")
1239
- expect(downRelease.eventType).toBe("release")
1240
- expect(downRelease.super).toBe(false)
1241
- expect(downRelease.hyper).toBe(false)
1242
- expect(downRelease.capsLock).toBe(false)
1243
- expect(downRelease.numLock).toBe(false)
1244
- })
1245
-
1246
- // ===== MISSING UNIT TEST CASES INTEGRATION TESTS =====
1247
-
1248
- test("high byte buffer handling via keyInput events", async () => {
1249
- // Test with Buffer input by emitting buffer data directly
1250
- const result = await new Promise<KeyEvent>((resolve) => {
1251
- const onKeypress = (parsedKey: KeyEvent) => {
1252
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
1253
- resolve(parsedKey)
1254
- }
1255
-
1256
- currentRenderer.keyInput.on("keypress", onKeypress)
1257
- // 128 + 32 = 160, should become \x1b + " "
1258
- currentRenderer.stdin.emit("data", Buffer.from([160]))
1259
- advanceCurrentClock()
1260
- })
1261
-
1262
- expect(result).toMatchObject({
1263
- eventType: "press",
1264
- name: "space",
1265
- ctrl: false,
1266
- meta: true,
1267
- shift: false,
1268
- option: false,
1269
- number: false,
1270
- sequence: "\x1b ",
1271
- raw: "\x1b ",
1272
- })
1273
- })
1274
-
1275
- test("high byte UTF-8 lead byte does not stall indefinitely", async () => {
1276
- const result = await new Promise<KeyEvent>((resolve, reject) => {
1277
- const timeout = setTimeout(() => {
1278
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
1279
- reject(new Error("timed out waiting for high-byte keypress"))
1280
- }, 300)
1281
-
1282
- const onKeypress = (parsedKey: KeyEvent) => {
1283
- clearTimeout(timeout)
1284
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
1285
- resolve(parsedKey)
1286
- }
1287
-
1288
- currentRenderer.keyInput.on("keypress", onKeypress)
1289
- // 128 + 105 = 233, should become \x1b + "i"
1290
- currentRenderer.stdin.emit("data", Buffer.from([233]))
1291
- advanceCurrentClock()
1292
- })
1293
-
1294
- expect(result).toMatchObject({
1295
- eventType: "press",
1296
- name: "i",
1297
- ctrl: false,
1298
- meta: true,
1299
- shift: false,
1300
- option: false,
1301
- number: false,
1302
- sequence: "\x1bi",
1303
- raw: "\x1bi",
1304
- })
1305
- })
1306
-
1307
- test("empty input via keyInput events", async () => {
1308
- const result = await triggerInput("")
1309
- expect(result).toMatchObject({
1310
- eventType: "press",
1311
- name: "",
1312
- ctrl: false,
1313
- meta: false,
1314
- shift: false,
1315
- option: false,
1316
- number: false,
1317
- sequence: "",
1318
- raw: "",
1319
- })
1320
- })
1321
-
1322
- test("rxvt style arrow keys with modifiers via keyInput events", async () => {
1323
- const resultShiftUp = await triggerInput("\x1b[a")
1324
- expect(resultShiftUp).toMatchObject({
1325
- eventType: "press",
1326
- name: "up",
1327
- ctrl: false,
1328
- meta: false,
1329
- shift: true,
1330
- option: false,
1331
- number: false,
1332
- sequence: "\x1b[a",
1333
- raw: "\x1b[a",
1334
- code: "[a",
1335
- })
1336
-
1337
- const resultShiftInsert = await triggerInput("\x1b[2$")
1338
- expect(resultShiftInsert).toMatchObject({
1339
- eventType: "press",
1340
- name: "insert",
1341
- ctrl: false,
1342
- meta: false,
1343
- shift: true,
1344
- option: false,
1345
- number: false,
1346
- sequence: "\x1b[2$",
1347
- raw: "\x1b[2$",
1348
- code: "[2$",
1349
- })
1350
- })
1351
-
1352
- test("ctrl modifier keys via keyInput events", async () => {
1353
- const resultCtrlUp = await triggerInput("\x1bOa")
1354
- expect(resultCtrlUp).toMatchObject({
1355
- eventType: "press",
1356
- name: "up",
1357
- ctrl: true,
1358
- meta: false,
1359
- shift: false,
1360
- option: false,
1361
- number: false,
1362
- sequence: "\x1bOa",
1363
- raw: "\x1bOa",
1364
- code: "Oa",
1365
- })
1366
-
1367
- const resultCtrlInsert = await triggerInput("\x1b[2^")
1368
- expect(resultCtrlInsert).toMatchObject({
1369
- eventType: "press",
1370
- name: "insert",
1371
- ctrl: true,
1372
- meta: false,
1373
- shift: false,
1374
- option: false,
1375
- number: false,
1376
- sequence: "\x1b[2^",
1377
- raw: "\x1b[2^",
1378
- code: "[2^",
1379
- })
1380
- })
1381
-
1382
- test("modifier bit calculations and meta/alt relationship via keyInput events", async () => {
1383
- // Super modifier is bit 8, so modifier value 9 = 8 + 1 (base)
1384
- const superOnly = await triggerInput("\x1b[1;9A")
1385
- expect(superOnly.name).toBe("up")
1386
- expect(superOnly.meta).toBe(false)
1387
- expect(superOnly.ctrl).toBe(false)
1388
- expect(superOnly.shift).toBe(false)
1389
- expect(superOnly.option).toBe(false)
1390
- expect((superOnly as any).super).toBe(true)
1391
- expect((superOnly as any).hyper).toBe(false)
1392
-
1393
- // Alt/Option modifier is bit 1 (value 2), so modifier value 3 = 2 + 1
1394
- const altOnly = await triggerInput("\x1b[1;3A")
1395
- expect(altOnly.name).toBe("up")
1396
- expect(altOnly.meta).toBe(true) // Alt sets meta flag (by design)
1397
- expect(altOnly.option).toBe(true)
1398
- expect(altOnly.ctrl).toBe(false)
1399
- expect(altOnly.shift).toBe(false)
1400
-
1401
- // Ctrl modifier is bit 2 (value 4), so modifier value 5 = 4 + 1
1402
- const ctrlOnly = await triggerInput("\x1b[1;5A")
1403
- expect(ctrlOnly.name).toBe("up")
1404
- expect(ctrlOnly.ctrl).toBe(true)
1405
- expect(ctrlOnly.meta).toBe(false)
1406
- expect(ctrlOnly.shift).toBe(false)
1407
- expect(ctrlOnly.option).toBe(false)
1408
-
1409
- // Shift modifier is bit 0 (value 1), so modifier value 2 = 1 + 1
1410
- const shiftOnly = await triggerInput("\x1b[1;2A")
1411
- expect(shiftOnly.name).toBe("up")
1412
- expect(shiftOnly.shift).toBe(true)
1413
- expect(shiftOnly.ctrl).toBe(false)
1414
- expect(shiftOnly.meta).toBe(false)
1415
- expect(shiftOnly.option).toBe(false)
1416
-
1417
- // Combined modifiers
1418
- // Ctrl+Super = 4 + 8 = 12, so modifier value 13 = 12 + 1
1419
- const ctrlSuper = await triggerInput("\x1b[1;13A")
1420
- expect(ctrlSuper.name).toBe("up")
1421
- expect(ctrlSuper.ctrl).toBe(true)
1422
- expect(ctrlSuper.meta).toBe(false)
1423
- expect(ctrlSuper.shift).toBe(false)
1424
- expect(ctrlSuper.option).toBe(false)
1425
- expect((ctrlSuper as any).super).toBe(true)
1426
- expect((ctrlSuper as any).hyper).toBe(false)
1427
-
1428
- // Shift+Alt = 1 + 2 = 3, so modifier value 4 = 3 + 1
1429
- const shiftAlt = await triggerInput("\x1b[1;4A")
1430
- expect(shiftAlt.name).toBe("up")
1431
- expect(shiftAlt.shift).toBe(true)
1432
- expect(shiftAlt.option).toBe(true)
1433
- expect(shiftAlt.meta).toBe(true) // Alt sets meta flag
1434
- expect(shiftAlt.ctrl).toBe(false)
1435
-
1436
- // All modifiers: Shift(1) + Alt(2) + Ctrl(4) + Meta(8) = 15, so modifier value 16 = 15 + 1
1437
- const allMods = await triggerInput("\x1b[1;16A")
1438
- expect(allMods.name).toBe("up")
1439
- expect(allMods.shift).toBe(true)
1440
- expect(allMods.option).toBe(true)
1441
- expect(allMods.ctrl).toBe(true)
1442
- expect(allMods.meta).toBe(true)
1443
- })
1444
-
1445
- test("modifier combinations with function keys via keyInput events", async () => {
1446
- // Ctrl+F1
1447
- const ctrlF1 = await triggerInput("\x1b[11;5~")
1448
- expect(ctrlF1.name).toBe("f1")
1449
- expect(ctrlF1.ctrl).toBe(true)
1450
- expect(ctrlF1.meta).toBe(false)
1451
- expect(ctrlF1.eventType).toBe("press")
1452
-
1453
- // Super+F1
1454
- const superF1 = await triggerInput("\x1b[11;9~")
1455
- expect(superF1.name).toBe("f1")
1456
- expect(superF1.meta).toBe(false)
1457
- expect(superF1.ctrl).toBe(false)
1458
- expect(superF1.super).toBe(true)
1459
- expect(superF1.hyper).toBe(false)
1460
- expect(superF1.eventType).toBe("press")
1461
-
1462
- // Shift+Ctrl+F1
1463
- const shiftCtrlF1 = await triggerInput("\x1b[11;6~")
1464
- expect(shiftCtrlF1.name).toBe("f1")
1465
- expect(shiftCtrlF1.shift).toBe(true)
1466
- expect(shiftCtrlF1.ctrl).toBe(true)
1467
- expect(shiftCtrlF1.meta).toBe(false)
1468
- expect(shiftCtrlF1.eventType).toBe("press")
1469
- })
1470
-
1471
- test("regular parsing always defaults to press event type via keyInput events", async () => {
1472
- // Test various regular key sequences to ensure they all default to "press"
1473
- const keys = [
1474
- "a",
1475
- "A",
1476
- "1",
1477
- "!",
1478
- "\t",
1479
- "\r",
1480
- "\n",
1481
- " ",
1482
- "\x1b",
1483
- "\x01", // Ctrl+A
1484
- "\x1ba", // Alt+A
1485
- "\x1b[A", // Up arrow
1486
- "\x1b[11~", // F1
1487
- "\x1b[1;2A", // Shift+Up
1488
- "\x1b[3~", // Delete
1489
- ]
1490
-
1491
- for (const keySeq of keys) {
1492
- const result = await triggerInput(keySeq)
1493
- expect(result.eventType).toBe("press")
1494
- }
1495
-
1496
- // Test with Buffer input too
1497
- const bufResult = await new Promise<KeyEvent>((resolve) => {
1498
- const onKeypress = (parsedKey: KeyEvent) => {
1499
- currentRenderer.keyInput.removeListener("keypress", onKeypress)
1500
- resolve(parsedKey)
1501
- }
1502
-
1503
- currentRenderer.keyInput.once("keypress", onKeypress)
1504
- currentRenderer.stdin.emit("data", Buffer.from("x"))
1505
- })
1506
- expect(bufResult.eventType).toBe("press")
1507
- })
1508
-
1509
- test("nonAlphanumericKeys export validation", async () => {
1510
- expect(Array.isArray(nonAlphanumericKeys)).toBe(true)
1511
- expect(nonAlphanumericKeys.length).toBeGreaterThan(0)
1512
- expect(nonAlphanumericKeys).toContain("up")
1513
- expect(nonAlphanumericKeys).toContain("down")
1514
- expect(nonAlphanumericKeys).toContain("f1")
1515
- expect(nonAlphanumericKeys).toContain("backspace")
1516
- expect(nonAlphanumericKeys).toContain("tab")
1517
- expect(nonAlphanumericKeys).toContain("left")
1518
- expect(nonAlphanumericKeys).toContain("right")
1519
- })
1520
-
1521
- test("ParsedKey type structure validation", async () => {
1522
- const key: ParsedKey = {
1523
- name: "test",
1524
- ctrl: false,
1525
- meta: false,
1526
- shift: false,
1527
- option: false,
1528
- sequence: "test",
1529
- raw: "test",
1530
- number: false,
1531
- eventType: "press",
1532
- source: "raw",
1533
- }
1534
-
1535
- expect(key).toHaveProperty("name")
1536
- expect(key).toHaveProperty("ctrl")
1537
- expect(key).toHaveProperty("meta")
1538
- expect(key).toHaveProperty("shift")
1539
- expect(key).toHaveProperty("option")
1540
- expect(key).toHaveProperty("sequence")
1541
- expect(key).toHaveProperty("raw")
1542
- expect(key).toHaveProperty("number")
1543
-
1544
- // Test that a key with code property works
1545
- const keyWithCode: ParsedKey = {
1546
- name: "up",
1547
- ctrl: false,
1548
- meta: false,
1549
- shift: false,
1550
- option: false,
1551
- sequence: "\x1b[A",
1552
- raw: "\x1b[A",
1553
- number: false,
1554
- code: "[A",
1555
- eventType: "press",
1556
- source: "raw",
1557
- }
1558
-
1559
- expect(keyWithCode).toHaveProperty("code")
1560
- expect(keyWithCode.code).toBe("[A")
1561
- })
1562
-
1563
- test("KeyEventType type validation", async () => {
1564
- // Test that KeyEventType only allows valid values
1565
- const validEventTypes: KeyEventType[] = ["press", "repeat", "release"]
1566
-
1567
- for (const eventType of validEventTypes) {
1568
- // This should compile without errors
1569
- const mockKey: ParsedKey = {
1570
- name: "test",
1571
- ctrl: false,
1572
- meta: false,
1573
- shift: false,
1574
- option: false,
1575
- sequence: "test",
1576
- raw: "test",
1577
- number: false,
1578
- eventType: eventType,
1579
- source: "raw",
1580
- }
1581
- expect(mockKey.eventType).toBe(eventType)
1582
- }
1583
- })
1584
-
1585
- // ===== CAPABILITY RESPONSE HANDLING TESTS =====
1586
-
1587
- test("capability responses should not trigger keypress events", async () => {
1588
- const keypresses: KeyEvent[] = []
1589
- currentRenderer.keyInput.on("keypress", (event) => {
1590
- keypresses.push(event)
1591
- })
1592
-
1593
- // Send various capability responses - none should trigger keypresses
1594
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y")) // DECRPM
1595
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[1;2R")) // CPR
1596
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?62;c")) // DA1
1597
-
1598
- // Wait for stdin parser timeout
1599
- advanceCurrentClock()
1600
-
1601
- expect(keypresses).toHaveLength(0)
1602
- })
1603
-
1604
- test("malformed XTGETTCAP Ms replies do not reach application input handlers", async () => {
1605
- const received: string[] = []
1606
- currentRenderer.addInputHandler((sequence) => {
1607
- received.push(sequence)
1608
- return false
1609
- })
1610
-
1611
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP1+r4d73\x1b\\"))
1612
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP1+r4d73=\x1b\\"))
1613
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP1+r4d73=abc\x1b\\"))
1614
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP1+r4d73=zz\x1b\\"))
1615
- advanceCurrentClock()
1616
-
1617
- expect(received).toHaveLength(0)
1618
- })
1619
-
1620
- test("capability response followed by keypress", async () => {
1621
- const keypresses: KeyEvent[] = []
1622
- currentRenderer.keyInput.on("keypress", (event) => {
1623
- keypresses.push(event)
1624
- })
1625
-
1626
- // Send capability response followed by 'a'
1627
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$ya"))
1628
-
1629
- // Wait for processing
1630
- advanceCurrentClock()
1631
-
1632
- expect(keypresses).toHaveLength(1)
1633
- expect(keypresses[0].name).toBe("a")
1634
- })
1635
-
1636
- test("partial SGR mouse stays pending on timeout, completes when rest arrives", async () => {
1637
- const keypresses: KeyEvent[] = []
1638
- currentRenderer.keyInput.on("keypress", (event) => {
1639
- keypresses.push(event)
1640
- })
1641
-
1642
- // Incomplete SGR mouse sequence; stays pending (not flushed on timeout).
1643
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[<35;20"))
1644
-
1645
- // Wait past native stdin parser timeout (20ms)
1646
- advanceCurrentClock()
1647
- expect(keypresses).toHaveLength(0)
1648
-
1649
- // Completing the mouse sequence should not trigger keypress either
1650
- currentRenderer.stdin.emit("data", Buffer.from(";5m"))
1651
- advanceCurrentClock()
1652
- expect(keypresses).toHaveLength(0)
1653
-
1654
- // Normal key input still works after
1655
- currentRenderer.stdin.emit("data", Buffer.from("x"))
1656
- advanceCurrentClock()
1657
-
1658
- expect(keypresses).toHaveLength(1)
1659
- expect(keypresses[0].name).toBe("x")
1660
- })
1661
-
1662
- test("partial OSC flushed on timeout should not block later text", async () => {
1663
- const keypresses: KeyEvent[] = []
1664
- currentRenderer.keyInput.on("keypress", (event) => {
1665
- keypresses.push(event)
1666
- })
1667
-
1668
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]52;c;"))
1669
- advanceCurrentClock()
1670
- expect(keypresses).toHaveLength(0)
1671
-
1672
- currentRenderer.stdin.emit("data", Buffer.from("abc"))
1673
- advanceCurrentClock()
1674
-
1675
- expect(keypresses).toHaveLength(3)
1676
- expect(keypresses.map((event) => event.name)).toEqual(["a", "b", "c"])
1677
- })
1678
-
1679
- test("partial OSC flushed on timeout should not block later escape sequences", async () => {
1680
- const keypresses: KeyEvent[] = []
1681
- currentRenderer.keyInput.on("keypress", (event) => {
1682
- keypresses.push(event)
1683
- })
1684
-
1685
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]52;c;"))
1686
- advanceCurrentClock()
1687
- expect(keypresses).toHaveLength(0)
1688
-
1689
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[A"))
1690
- advanceCurrentClock()
1691
-
1692
- expect(keypresses).toHaveLength(1)
1693
- expect(keypresses[0].name).toBe("up")
1694
- })
1695
-
1696
- test("incomplete mouse input resets the timeout when more bytes arrive", async () => {
1697
- const keypresses: KeyEvent[] = []
1698
- currentRenderer.keyInput.on("keypress", (event) => {
1699
- keypresses.push(event)
1700
- })
1701
-
1702
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[<35;20;"))
1703
- advanceCurrentClock(19)
1704
- currentRenderer.stdin.emit("data", Buffer.from("5"))
1705
- advanceCurrentClock(19)
1706
-
1707
- expect(keypresses).toHaveLength(0)
1708
-
1709
- currentRenderer.stdin.emit("data", Buffer.from("m"))
1710
- advanceCurrentClock()
1711
-
1712
- expect(keypresses).toHaveLength(0)
1713
- })
1714
-
1715
- test("chunked XTVersion response should not trigger keypresses", async () => {
1716
- const keypresses: KeyEvent[] = []
1717
- currentRenderer.keyInput.on("keypress", (event) => {
1718
- keypresses.push(event)
1719
- })
1720
-
1721
- // Send XTVersion in chunks (chunks arrive quickly, within stdin parser timeout)
1722
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kit"))
1723
- currentRenderer.stdin.emit("data", Buffer.from("ty(0.40"))
1724
- currentRenderer.stdin.emit("data", Buffer.from(".1)\x1b\\"))
1725
-
1726
- // Wait for stdin parser to process
1727
- advanceCurrentClock()
1728
-
1729
- expect(keypresses).toHaveLength(0)
1730
- })
1731
-
1732
- test("chunked XTVersion followed by keypress", async () => {
1733
- const keypresses: KeyEvent[] = []
1734
- currentRenderer.keyInput.on("keypress", (event) => {
1735
- keypresses.push(event)
1736
- })
1737
-
1738
- // Send XTVersion in chunks followed by 'x'
1739
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|ghostty"))
1740
- currentRenderer.stdin.emit("data", Buffer.from(" 1.1.3\x1b\\x"))
1741
-
1742
- // Wait for processing
1743
- advanceCurrentClock()
1744
-
1745
- expect(keypresses).toHaveLength(1)
1746
- expect(keypresses[0].name).toBe("x")
1747
- })
1748
-
1749
- test("chunked Kitty graphics response should not trigger keypresses", async () => {
1750
- const keypresses: KeyEvent[] = []
1751
- currentRenderer.keyInput.on("keypress", (event) => {
1752
- keypresses.push(event)
1753
- })
1754
-
1755
- // Send Kitty graphics response in chunks (arriving quickly)
1756
- currentRenderer.stdin.emit("data", Buffer.from("\x1b_Gi=1;"))
1757
- currentRenderer.stdin.emit("data", Buffer.from("EINVAL:"))
1758
- currentRenderer.stdin.emit("data", Buffer.from("Zero width/height not allowed\x1b\\"))
1759
-
1760
- // Wait for processing
1761
- advanceCurrentClock()
1762
-
1763
- expect(keypresses).toHaveLength(0)
1764
- })
1765
-
1766
- test("multiple DECRPM responses in sequence", async () => {
1767
- const keypresses: KeyEvent[] = []
1768
- currentRenderer.keyInput.on("keypress", (event) => {
1769
- keypresses.push(event)
1770
- })
1771
-
1772
- // Simulate multiple DECRPM responses arriving together
1773
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y\x1b[?2027;0$y\x1b[?2031;2$y"))
1774
- advanceCurrentClock()
1775
-
1776
- expect(keypresses).toHaveLength(0)
1777
- })
1778
-
1779
- test("OSC 10/11 fallback sets initial theme mode once both colors arrive", () => {
1780
- const themeModes: string[] = []
1781
- currentRenderer.on("theme_mode", (mode) => {
1782
- themeModes.push(mode)
1783
- })
1784
-
1785
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1786
- advanceCurrentClock()
1787
-
1788
- expect(currentRenderer.themeMode).toBeNull()
1789
- expect(themeModes).toEqual([])
1790
-
1791
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1792
- advanceCurrentClock()
1793
-
1794
- expect(currentRenderer.themeMode).toBe("dark")
1795
- expect(themeModes).toEqual(["dark"])
1796
- })
1797
-
1798
- test("CSI 997 does not set theme mode directly and triggers an OSC refresh query", async () => {
1799
- const { renderer, queryThemeColorsCalls, clock } = await createThemeQueryRenderer()
1800
- const themeModes: string[] = []
1801
- renderer.on("theme_mode", (mode) => {
1802
- themeModes.push(mode)
1803
- })
1804
-
1805
- renderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1806
- advanceClock(clock)
1807
-
1808
- expect(renderer.themeMode).toBeNull()
1809
- expect(themeModes).toEqual([])
1810
- expect(queryThemeColorsCalls.count).toBe(1)
1811
-
1812
- renderer.destroy()
1813
- })
1814
-
1815
- test("CSI 997 does not query theme colors again while a refresh is already pending", async () => {
1816
- const { renderer, queryThemeColorsCalls, clock } = await createThemeQueryRenderer()
1817
-
1818
- renderer.stdin.emit("data", Buffer.from("\x1b[?997;1n"))
1819
- advanceClock(clock)
1820
-
1821
- expect(queryThemeColorsCalls.count).toBe(1)
1822
-
1823
- renderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1824
- advanceClock(clock)
1825
-
1826
- expect(queryThemeColorsCalls.count).toBe(1)
1827
-
1828
- renderer.destroy()
1829
- })
1830
-
1831
- test("conflicting CSI 997 before initial OSC replies does not override the OSC-derived mode", () => {
1832
- const themeModes: string[] = []
1833
- currentRenderer.on("theme_mode", (mode) => {
1834
- themeModes.push(mode)
1835
- })
1836
-
1837
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1838
- advanceCurrentClock()
1839
-
1840
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1841
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1842
- advanceCurrentClock()
1843
-
1844
- expect(currentRenderer.themeMode).toBe("dark")
1845
- expect(themeModes).toEqual(["dark"])
1846
- })
1847
-
1848
- test("CSI 997 refreshes theme mode only after fresh OSC 10 and 11 replies arrive", async () => {
1849
- const { renderer, queryThemeColorsCalls, clock } = await createThemeQueryRenderer()
1850
- const themeModes: string[] = []
1851
- renderer.on("theme_mode", (mode) => {
1852
- themeModes.push(mode)
1853
- })
1854
-
1855
- renderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1856
- renderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1857
- advanceClock(clock)
1858
-
1859
- expect(renderer.themeMode).toBe("dark")
1860
- expect(themeModes).toEqual(["dark"])
1861
-
1862
- renderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1863
- advanceClock(clock)
1864
-
1865
- expect(renderer.themeMode).toBe("dark")
1866
- expect(themeModes).toEqual(["dark"])
1867
- expect(queryThemeColorsCalls.count).toBe(1)
1868
-
1869
- renderer.stdin.emit("data", Buffer.from("\x1b]10;#000000\x07"))
1870
- advanceClock(clock)
1871
-
1872
- expect(renderer.themeMode).toBe("dark")
1873
- expect(themeModes).toEqual(["dark"])
1874
-
1875
- renderer.stdin.emit("data", Buffer.from("\x1b]11;#ffffff\x07"))
1876
- advanceClock(clock)
1877
-
1878
- expect(renderer.themeMode).toBe("light")
1879
- expect(themeModes).toEqual(["dark", "light"])
1880
-
1881
- renderer.destroy()
1882
- })
1883
-
1884
- test("CSI 997 refresh timeout restores background override state without changing theme mode", async () => {
1885
- const { renderer, queryThemeColorsCalls, clock } = await createThemeQueryRenderer()
1886
- const themeModes: string[] = []
1887
- renderer.on("theme_mode", (mode) => {
1888
- themeModes.push(mode)
1889
- })
1890
-
1891
- renderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1892
- renderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1893
- advanceClock(clock)
1894
-
1895
- renderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1896
- advanceClock(clock, 249)
1897
-
1898
- expect(renderer.themeMode).toBe("dark")
1899
- expect(themeModes).toEqual(["dark"])
1900
- expect(queryThemeColorsCalls.count).toBe(1)
1901
-
1902
- advanceClock(clock, 1)
1903
- await flushMicrotasks()
1904
-
1905
- expect(renderer.themeMode).toBe("dark")
1906
- expect(themeModes).toEqual(["dark"])
1907
-
1908
- renderer.destroy()
1909
- })
1910
-
1911
- test("CSI 997 refresh with the same OSC-derived mode does not emit twice", () => {
1912
- const themeModes: string[] = []
1913
- currentRenderer.on("theme_mode", (mode) => {
1914
- themeModes.push(mode)
1915
- })
1916
-
1917
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1918
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1919
- advanceCurrentClock()
1920
-
1921
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?997;1n"))
1922
- advanceCurrentClock()
1923
-
1924
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1925
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1926
- advanceCurrentClock()
1927
-
1928
- expect(currentRenderer.themeMode).toBe("dark")
1929
- expect(themeModes).toEqual(["dark"])
1930
- })
1931
-
1932
- test("multiple CSI 997 responses while a refresh is pending do not emit early", () => {
1933
- const themeModes: string[] = []
1934
- currentRenderer.on("theme_mode", (mode) => {
1935
- themeModes.push(mode)
1936
- })
1937
-
1938
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1939
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1940
- advanceCurrentClock()
1941
-
1942
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?997;1n"))
1943
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1944
- advanceCurrentClock()
1945
-
1946
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#000000\x07"))
1947
- advanceCurrentClock()
1948
-
1949
- expect(currentRenderer.themeMode).toBe("dark")
1950
- expect(themeModes).toEqual(["dark"])
1951
-
1952
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#ffffff\x07"))
1953
- advanceCurrentClock()
1954
-
1955
- expect(currentRenderer.themeMode).toBe("light")
1956
- expect(themeModes).toEqual(["dark", "light"])
1957
- })
1958
-
1959
- test("waitForThemeMode resolves when initial theme mode is set", async () => {
1960
- const themeModePromise = currentRenderer.waitForThemeMode(500)
1961
-
1962
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#ffffff\x07"))
1963
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#000000\x07"))
1964
- advanceCurrentClock()
1965
-
1966
- await expect(themeModePromise).resolves.toBe("dark")
1967
- })
1968
-
1969
- test("waitForThemeMode resolves immediately when theme mode is already set", async () => {
1970
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#000000\x07"))
1971
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#ffffff\x07"))
1972
- advanceCurrentClock()
1973
-
1974
- await expect(currentRenderer.waitForThemeMode()).resolves.toBe("light")
1975
- })
1976
-
1977
- test("waitForThemeMode ignores CSI 997 until OSC-derived mode is available", async () => {
1978
- let resolvedThemeMode: string | null | undefined
1979
-
1980
- currentRenderer.waitForThemeMode(500).then((mode) => {
1981
- resolvedThemeMode = mode
1982
- })
1983
-
1984
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?997;2n"))
1985
- advanceCurrentClock(249)
1986
- await flushMicrotasks()
1987
-
1988
- expect(resolvedThemeMode).toBeUndefined()
1989
-
1990
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]10;#000000\x07"))
1991
- currentRenderer.stdin.emit("data", Buffer.from("\x1b]11;#ffffff\x07"))
1992
- advanceCurrentClock(1)
1993
- await flushMicrotasks()
1994
-
1995
- expect(resolvedThemeMode).toBe("light")
1996
- })
1997
-
1998
- test("waitForThemeMode returns current theme mode after the default timeout", async () => {
1999
- let resolvedThemeMode: string | null | undefined
2000
-
2001
- currentRenderer.waitForThemeMode().then((mode) => {
2002
- resolvedThemeMode = mode
2003
- })
2004
-
2005
- advanceCurrentClock(999)
2006
- await flushMicrotasks()
2007
- expect(resolvedThemeMode).toBeUndefined()
2008
-
2009
- advanceCurrentClock(1)
2010
- await flushMicrotasks()
2011
- expect(resolvedThemeMode).toBeNull()
2012
- })
2013
-
2014
- test("waitForThemeMode resolves with current theme mode when renderer is destroyed", async () => {
2015
- const themeModePromise = currentRenderer.waitForThemeMode(500)
2016
-
2017
- currentRenderer.destroy()
2018
-
2019
- await expect(themeModePromise).resolves.toBeNull()
2020
- })
2021
-
2022
- test("pixel resolution response should not trigger keypress", async () => {
2023
- const keypresses: KeyEvent[] = []
2024
- currentRenderer.keyInput.on("keypress", (event) => {
2025
- keypresses.push(event)
2026
- })
2027
-
2028
- // Mark as waiting for resolution
2029
- // @ts-expect-error - accessing private property for testing
2030
- currentRenderer.waitingForPixelResolution = true
2031
-
2032
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;720;1280t"))
2033
- advanceCurrentClock()
2034
-
2035
- expect(keypresses).toHaveLength(0)
2036
- expect(currentRenderer.resolution).toEqual({ width: 1280, height: 720 })
2037
- })
2038
-
2039
- test("chunked pixel resolution response", async () => {
2040
- const keypresses: KeyEvent[] = []
2041
- currentRenderer.keyInput.on("keypress", (event) => {
2042
- keypresses.push(event)
2043
- })
2044
-
2045
- // @ts-expect-error - accessing private property for testing
2046
- currentRenderer.waitingForPixelResolution = true
2047
-
2048
- // Send pixel resolution in chunks (arriving quickly)
2049
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;72"))
2050
- currentRenderer.stdin.emit("data", Buffer.from("0;1280t"))
2051
-
2052
- // Wait for processing
2053
- advanceCurrentClock()
2054
-
2055
- expect(keypresses).toHaveLength(0)
2056
- expect(currentRenderer.resolution).toEqual({ width: 1280, height: 720 })
2057
- })
2058
-
2059
- test("kitty full capability response arriving in realistic chunks", async () => {
2060
- const keypresses: KeyEvent[] = []
2061
- currentRenderer.keyInput.on("keypress", (event) => {
2062
- keypresses.push(event)
2063
- })
2064
-
2065
- // Simulate how kitty might send its full response in a few chunks
2066
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y\x1b[?2027;0$y"))
2067
- advanceCurrentClock(1)
2068
-
2069
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?2031;2$y\x1b[?1004;1$y\x1b[1;2R\x1b[1;3R"))
2070
- advanceCurrentClock(1)
2071
-
2072
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kitty(0."))
2073
- advanceCurrentClock(1)
2074
-
2075
- currentRenderer.stdin.emit("data", Buffer.from("40.1)\x1b\\\x1b_Gi=1;OK\x1b\\"))
2076
- advanceCurrentClock(1)
2077
-
2078
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?62;c"))
2079
-
2080
- // Wait for processing
2081
- advanceCurrentClock()
2082
-
2083
- expect(keypresses).toHaveLength(0)
2084
- })
2085
-
2086
- test("capability response interleaved with user input", async () => {
2087
- const keypresses: KeyEvent[] = []
2088
- currentRenderer.keyInput.on("keypress", (event) => {
2089
- keypresses.push(event)
2090
- })
2091
-
2092
- // User types 'h'
2093
- currentRenderer.stdin.emit("data", Buffer.from("h"))
2094
-
2095
- // Capability response arrives
2096
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y"))
2097
-
2098
- // User types 'e'
2099
- currentRenderer.stdin.emit("data", Buffer.from("e"))
2100
-
2101
- // More capability responses
2102
- currentRenderer.stdin.emit("data", Buffer.from("\x1bP>|kitty(0.40.1)\x1b\\"))
2103
-
2104
- // User types 'llo'
2105
- currentRenderer.stdin.emit("data", Buffer.from("llo"))
2106
-
2107
- // Wait for processing
2108
- advanceCurrentClock()
2109
-
2110
- // Should only have user keypresses
2111
- expect(keypresses).toHaveLength(5)
2112
- expect(keypresses.map((k) => k.name)).toEqual(["h", "e", "l", "l", "o"])
2113
- })
2114
-
2115
- test("delayed capability responses should be processed", async () => {
2116
- const keypresses: KeyEvent[] = []
2117
- currentRenderer.keyInput.on("keypress", (event) => {
2118
- keypresses.push(event)
2119
- })
2120
-
2121
- // User input first
2122
- currentRenderer.stdin.emit("data", Buffer.from("abc"))
2123
-
2124
- // Late capability response (e.g., terminal was slow to respond)
2125
- advanceCurrentClock(50)
2126
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?2027;2$y"))
2127
-
2128
- // Wait for processing
2129
- advanceCurrentClock()
2130
-
2131
- // Should have user input but not capability
2132
- expect(keypresses).toHaveLength(3)
2133
- expect(keypresses.map((k) => k.name)).toEqual(["a", "b", "c"])
2134
- })
2135
-
2136
- test("delayed explicit-width CPR stays in response path while setup probe is active", async () => {
2137
- const keypresses: KeyEvent[] = []
2138
- currentRenderer.keyInput.on("keypress", (event) => {
2139
- keypresses.push(event)
2140
- })
2141
-
2142
- // @ts-expect-error - accessing private helper for test coverage
2143
- currentRenderer.updateStdinParserProtocolContext({ explicitWidthCprActive: true })
2144
-
2145
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[1;2"))
2146
- advanceCurrentClock()
2147
- currentRenderer.stdin.emit("data", Buffer.from("R"))
2148
- advanceCurrentClock()
2149
-
2150
- expect(keypresses).toHaveLength(0)
2151
- })
2152
-
2153
- test("delayed DECRPM stays in response path while capability probing is active", async () => {
2154
- const keypresses: KeyEvent[] = []
2155
- currentRenderer.keyInput.on("keypress", (event) => {
2156
- keypresses.push(event)
2157
- })
2158
-
2159
- // @ts-expect-error - accessing private helper for test coverage
2160
- currentRenderer.updateStdinParserProtocolContext({ privateCapabilityRepliesActive: true })
2161
-
2162
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$"))
2163
- advanceCurrentClock()
2164
- currentRenderer.stdin.emit("data", Buffer.from("y"))
2165
- advanceCurrentClock()
2166
-
2167
- expect(keypresses).toHaveLength(0)
2168
- })
2169
-
2170
- test("delayed pixel resolution response stays in response path while query is active", async () => {
2171
- const keypresses: KeyEvent[] = []
2172
- currentRenderer.keyInput.on("keypress", (event) => {
2173
- keypresses.push(event)
2174
- })
2175
-
2176
- // @ts-expect-error - accessing private property for testing
2177
- currentRenderer.waitingForPixelResolution = true
2178
- // @ts-expect-error - accessing private helper for test coverage
2179
- currentRenderer.updateStdinParserProtocolContext({ pixelResolutionQueryActive: true })
2180
-
2181
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[4;1080;192"))
2182
- advanceCurrentClock()
2183
- currentRenderer.stdin.emit("data", Buffer.from("0t"))
2184
- advanceCurrentClock()
2185
-
2186
- expect(keypresses).toHaveLength(0)
2187
- expect(currentRenderer.resolution).toEqual({ width: 1920, height: 1080 })
2188
- })
2189
-
2190
- test("vscode minimal capability response", async () => {
2191
- const keypresses: KeyEvent[] = []
2192
- currentRenderer.keyInput.on("keypress", (event) => {
2193
- keypresses.push(event)
2194
- })
2195
-
2196
- // VSCode often sends just one DECRPM
2197
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[?1016;2$y"))
2198
- advanceCurrentClock()
2199
-
2200
- expect(keypresses).toHaveLength(0)
2201
- })
2202
-
2203
- test("alacritty capability response sequence", async () => {
2204
- const keypresses: KeyEvent[] = []
2205
- currentRenderer.keyInput.on("keypress", (event) => {
2206
- keypresses.push(event)
2207
- })
2208
-
2209
- // Simulate alacritty's response pattern
2210
- const alacrittyResponse =
2211
- "\x1b[?1016;0$y\x1b[?2027;0$y\x1b[?2031;0$y\x1b[?1004;2$y\x1b[?2004;2$y\x1b[?2026;2$y\x1b[1;1R\x1b[1;1R\x1b[?0u\x1b[?6c"
2212
- currentRenderer.stdin.emit("data", Buffer.from(alacrittyResponse))
2213
- advanceCurrentClock()
2214
-
2215
- expect(keypresses).toHaveLength(0)
2216
- })
2217
-
2218
- test("focus and blur events", async () => {
2219
- const events: string[] = []
2220
-
2221
- currentRenderer.on("focus", () => {
2222
- events.push("focus")
2223
- })
2224
-
2225
- currentRenderer.on("blur", () => {
2226
- events.push("blur")
2227
- })
2228
-
2229
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[I"))
2230
- advanceCurrentClock()
2231
-
2232
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[O"))
2233
- advanceCurrentClock()
2234
-
2235
- expect(events).toEqual(["focus", "blur"])
2236
- })
2237
-
2238
- test("focus events should not trigger keypress", async () => {
2239
- const keypresses: KeyEvent[] = []
2240
- const focusEvents: string[] = []
2241
-
2242
- currentRenderer.keyInput.on("keypress", (event) => {
2243
- keypresses.push(event)
2244
- })
2245
-
2246
- currentRenderer.on("focus", () => {
2247
- focusEvents.push("focus")
2248
- })
2249
-
2250
- currentRenderer.on("blur", () => {
2251
- focusEvents.push("blur")
2252
- })
2253
-
2254
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[I"))
2255
- advanceCurrentClock()
2256
- currentRenderer.stdin.emit("data", Buffer.from("\x1b[O"))
2257
- advanceCurrentClock()
2258
-
2259
- expect(focusEvents).toEqual(["focus", "blur"])
2260
- expect(keypresses).toHaveLength(0)
2261
- })
2262
-
2263
- describe("stdin routing", () => {
2264
- test("mouse then key in one chunk", async () => {
2265
- const { renderer, renderOnce, clock } = await createRoutingRenderer()
2266
- try {
2267
- const target = new MouseTarget(renderer, {
2268
- id: "target-mouse-then-key",
2269
- position: "absolute",
2270
- left: 0,
2271
- top: 0,
2272
- width: renderer.width,
2273
- height: renderer.height,
2274
- })
2275
- renderer.root.add(target)
2276
- await renderOnce()
2277
-
2278
- const keys: string[] = []
2279
- let scrollCount = 0
2280
-
2281
- renderer.keyInput.on("keypress", (event) => {
2282
- keys.push(event.name)
2283
- })
2284
-
2285
- target.onMouseScroll = () => {
2286
- scrollCount++
2287
- }
2288
-
2289
- renderer.stdin.emit("data", Buffer.from("\x1b[<64;10;5Mx"))
2290
- advanceClock(clock)
2291
-
2292
- expect(scrollCount).toBe(1)
2293
- expect(keys).toEqual(["x"])
2294
- } finally {
2295
- renderer.destroy()
2296
- }
2297
- })
2298
-
2299
- test("key then mouse in one chunk", async () => {
2300
- const { renderer, renderOnce, clock } = await createRoutingRenderer()
2301
- try {
2302
- const target = new MouseTarget(renderer, {
2303
- id: "target-key-then-mouse",
2304
- position: "absolute",
2305
- left: 0,
2306
- top: 0,
2307
- width: renderer.width,
2308
- height: renderer.height,
2309
- })
2310
- renderer.root.add(target)
2311
- await renderOnce()
2312
-
2313
- const keys: string[] = []
2314
- let scrollCount = 0
2315
-
2316
- renderer.keyInput.on("keypress", (event) => {
2317
- keys.push(event.name)
2318
- })
2319
-
2320
- target.onMouseScroll = () => {
2321
- scrollCount++
2322
- }
2323
-
2324
- renderer.stdin.emit("data", Buffer.from("x\x1b[<64;10;5M"))
2325
- advanceClock(clock)
2326
-
2327
- expect(keys).toEqual(["x"])
2328
- expect(scrollCount).toBe(1)
2329
- } finally {
2330
- renderer.destroy()
2331
- }
2332
- })
2333
-
2334
- test("focus and key mixed in one chunk", async () => {
2335
- const { renderer, clock } = await createRoutingRenderer()
2336
- try {
2337
- const events: string[] = []
2338
- const keys: string[] = []
2339
-
2340
- renderer.on("focus", () => {
2341
- events.push("focus")
2342
- })
2343
-
2344
- renderer.keyInput.on("keypress", (event) => {
2345
- keys.push(event.name)
2346
- })
2347
-
2348
- renderer.stdin.emit("data", Buffer.from("\x1b[Ix"))
2349
- advanceClock(clock)
2350
-
2351
- expect(events).toEqual(["focus"])
2352
- expect(keys).toEqual(["x"])
2353
- } finally {
2354
- renderer.destroy()
2355
- }
2356
- })
2357
-
2358
- test("focus and mouse mixed in one chunk", async () => {
2359
- const { renderer, renderOnce, clock } = await createRoutingRenderer()
2360
- try {
2361
- const events: string[] = []
2362
- let scrollCount = 0
2363
-
2364
- const target = new MouseTarget(renderer, {
2365
- id: "target-focus-then-mouse",
2366
- position: "absolute",
2367
- left: 0,
2368
- top: 0,
2369
- width: renderer.width,
2370
- height: renderer.height,
2371
- })
2372
- renderer.root.add(target)
2373
- await renderOnce()
2374
-
2375
- renderer.on("focus", () => {
2376
- events.push("focus")
2377
- })
2378
-
2379
- target.onMouseScroll = () => {
2380
- scrollCount++
2381
- }
2382
-
2383
- renderer.stdin.emit("data", Buffer.from("\x1b[I\x1b[<64;10;5M"))
2384
- advanceClock(clock)
2385
-
2386
- expect(events).toEqual(["focus"])
2387
- expect(scrollCount).toBe(1)
2388
- } finally {
2389
- renderer.destroy()
2390
- }
2391
- })
2392
-
2393
- test("mouse state resets when mouse mode toggles", async () => {
2394
- const { renderer, renderOnce, clock } = await createRoutingRenderer()
2395
- try {
2396
- const target = new MouseTarget(renderer, {
2397
- id: "target-mouse-toggle-reset",
2398
- position: "absolute",
2399
- left: 0,
2400
- top: 0,
2401
- width: renderer.width,
2402
- height: renderer.height,
2403
- })
2404
- renderer.root.add(target)
2405
- await renderOnce()
2406
-
2407
- let moveCount = 0
2408
- let dragCount = 0
2409
- target.onMouseMove = () => {
2410
- moveCount++
2411
- }
2412
- target.onMouseDrag = () => {
2413
- dragCount++
2414
- }
2415
-
2416
- renderer.stdin.emit("data", Buffer.from("\x1b[<0;1;1M"))
2417
- advanceClock(clock)
2418
-
2419
- renderer.useMouse = false
2420
- renderer.useMouse = true
2421
-
2422
- renderer.stdin.emit("data", Buffer.from("\x1b[<32;2;2M"))
2423
- advanceClock(clock)
2424
-
2425
- expect(moveCount).toBe(1)
2426
- expect(dragCount).toBe(0)
2427
- } finally {
2428
- renderer.destroy()
2429
- }
2430
- })
2431
-
2432
- test("mouse state resets on resize", async () => {
2433
- const { renderer, renderOnce, resize, clock } = await createRoutingRenderer()
2434
- try {
2435
- const target = new MouseTarget(renderer, {
2436
- id: "target-resize-reset",
2437
- position: "absolute",
2438
- left: 0,
2439
- top: 0,
2440
- width: renderer.width,
2441
- height: renderer.height,
2442
- })
2443
- renderer.root.add(target)
2444
- await renderOnce()
2445
-
2446
- let moveCount = 0
2447
- let dragCount = 0
2448
- target.onMouseMove = () => {
2449
- moveCount++
2450
- }
2451
- target.onMouseDrag = () => {
2452
- dragCount++
2453
- }
2454
-
2455
- renderer.stdin.emit("data", Buffer.from("\x1b[<0;1;1M"))
2456
- advanceClock(clock)
2457
-
2458
- resize(41, 20)
2459
- await renderOnce()
2460
-
2461
- renderer.stdin.emit("data", Buffer.from("\x1b[<32;2;2M"))
2462
- advanceClock(clock)
2463
-
2464
- expect(moveCount).toBe(1)
2465
- expect(dragCount).toBe(0)
2466
- } finally {
2467
- renderer.destroy()
2468
- }
2469
- })
2470
-
2471
- test("suspend resets parser state before resume", async () => {
2472
- const { renderer, clock } = await createRoutingRenderer()
2473
-
2474
- try {
2475
- const events: Array<{ name: string; meta: boolean }> = []
2476
- renderer.keyInput.on("keypress", (event) => {
2477
- events.push({ name: event.name, meta: event.meta })
2478
- })
2479
-
2480
- renderer.stdin.emit("data", Buffer.from("\x1b["))
2481
- advanceClock(clock, 5)
2482
-
2483
- renderer.suspend()
2484
- renderer.resume()
2485
-
2486
- renderer.stdin.emit("data", Buffer.from("x"))
2487
- advanceClock(clock)
2488
-
2489
- expect(events).toEqual([{ name: "x", meta: false }])
2490
- } finally {
2491
- renderer.destroy()
2492
- }
2493
- })
2494
-
2495
- test("streams large paste bodies without dropping them and resumes afterward", async () => {
2496
- const { renderer, clock } = await createRoutingRenderer({
2497
- stdinParserMaxBufferBytes: 64 * 1024,
2498
- })
2499
-
2500
- try {
2501
- const keys: string[] = []
2502
- const pastes: string[] = []
2503
- renderer.keyInput.on("keypress", (event) => {
2504
- keys.push(event.name)
2505
- })
2506
- renderer.keyInput.on("paste", (event) => {
2507
- pastes.push(decodePasteBytes(event.bytes))
2508
- })
2509
-
2510
- const largeChunk = Buffer.alloc(16 * 1024, "x")
2511
- const expectedPaste = largeChunk.toString().repeat(5) + "z"
2512
-
2513
- expect(() => {
2514
- renderer.stdin.emit("data", Buffer.from("\x1b[200~"))
2515
- for (let i = 0; i < 5; i++) {
2516
- renderer.stdin.emit("data", largeChunk)
2517
- }
2518
- renderer.stdin.emit("data", Buffer.from("z"))
2519
- renderer.stdin.emit("data", Buffer.from("\x1b[20"))
2520
- renderer.stdin.emit("data", Buffer.from("1~"))
2521
- renderer.stdin.emit("data", Buffer.from("q"))
2522
- }).not.toThrow()
2523
-
2524
- advanceClock(clock)
2525
-
2526
- expect(keys).toEqual(["q"])
2527
- expect(pastes).toEqual([expectedPaste])
2528
- } finally {
2529
- renderer.destroy()
2530
- }
2531
- })
2532
-
2533
- test("emits paste event for large bracketed paste under configured limit", async () => {
2534
- const { renderer, clock } = await createRoutingRenderer({
2535
- stdinParserMaxBufferBytes: 512 * 1024,
2536
- })
2537
-
2538
- try {
2539
- const payloadSize = 256 * 1024
2540
- let pasteCount = 0
2541
- let pastedBytes = 0
2542
-
2543
- renderer.keyInput.on("paste", (event) => {
2544
- pasteCount += 1
2545
- pastedBytes += event.bytes.length
2546
- })
2547
-
2548
- const chunk = Buffer.alloc(payloadSize, "x")
2549
- const stream = Buffer.concat([Buffer.from("\x1b[200~"), chunk, Buffer.from("\x1b[201~")])
2550
- renderer.stdin.emit("data", stream)
2551
- advanceClock(clock)
2552
-
2553
- expect(pasteCount).toBe(1)
2554
- expect(pastedBytes).toBe(payloadSize)
2555
- } finally {
2556
- renderer.destroy()
2557
- }
2558
- })
2559
-
2560
- test("emits one paste event for one bracketed paste", async () => {
2561
- const { renderer, clock } = await createRoutingRenderer()
2562
-
2563
- try {
2564
- const payload = "x".repeat(70_000)
2565
- const pastes: string[] = []
2566
- renderer.keyInput.on("paste", (event) => {
2567
- pastes.push(decodePasteBytes(event.bytes))
2568
- })
2569
-
2570
- renderer.stdin.emit("data", Buffer.from(`\x1b[200~${payload}\x1b[201~`))
2571
- advanceClock(clock)
2572
-
2573
- expect(pastes).toEqual([payload])
2574
- } finally {
2575
- renderer.destroy()
2576
- }
2577
- })
2578
-
2579
- test("emits empty paste for empty bracketed paste", async () => {
2580
- const { renderer, clock } = await createRoutingRenderer()
2581
-
2582
- try {
2583
- const pastes: string[] = []
2584
- renderer.keyInput.on("paste", (event) => {
2585
- pastes.push(decodePasteBytes(event.bytes))
2586
- })
2587
-
2588
- renderer.stdin.emit("data", Buffer.from("\x1b[200~\x1b[201~"))
2589
- advanceClock(clock)
2590
-
2591
- expect(pastes).toEqual([""])
2592
- } finally {
2593
- renderer.destroy()
2594
- }
2595
- })
2596
-
2597
- test("preserves UTF-8 across bracketed paste chunk boundaries", async () => {
2598
- const { renderer, clock } = await createRoutingRenderer()
2599
-
2600
- try {
2601
- const payload = "a".repeat(4095) + "é"
2602
- const pastes: string[] = []
2603
- renderer.keyInput.on("paste", (event) => {
2604
- pastes.push(decodePasteBytes(event.bytes))
2605
- })
2606
-
2607
- renderer.stdin.emit("data", Buffer.from(`\x1b[200~${payload}\x1b[201~`))
2608
- advanceClock(clock)
2609
-
2610
- expect(pastes.join("")).toBe(payload)
2611
- } finally {
2612
- renderer.destroy()
2613
- }
2614
- })
2615
- })