@fairyhunter13/opentui-core 0.1.114 → 0.1.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/3d/SpriteResourceManager.d.ts +74 -0
  2. package/3d/SpriteUtils.d.ts +13 -0
  3. package/3d/TextureUtils.d.ts +24 -0
  4. package/3d/ThreeRenderable.d.ts +40 -0
  5. package/3d/WGPURenderer.d.ts +61 -0
  6. package/3d/animation/ExplodingSpriteEffect.d.ts +71 -0
  7. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +76 -0
  8. package/3d/animation/SpriteAnimator.d.ts +124 -0
  9. package/3d/animation/SpriteParticleGenerator.d.ts +62 -0
  10. package/3d/canvas.d.ts +44 -0
  11. package/3d/index.d.ts +12 -0
  12. package/3d/physics/PlanckPhysicsAdapter.d.ts +19 -0
  13. package/3d/physics/RapierPhysicsAdapter.d.ts +19 -0
  14. package/3d/physics/physics-interface.d.ts +27 -0
  15. package/3d.d.ts +2 -0
  16. package/3d.js +34041 -0
  17. package/3d.js.map +155 -0
  18. package/LICENSE +21 -0
  19. package/NativeSpanFeed.d.ts +41 -0
  20. package/Renderable.d.ts +334 -0
  21. package/animation/Timeline.d.ts +126 -0
  22. package/ansi.d.ts +13 -0
  23. package/buffer.d.ts +111 -0
  24. package/console.d.ts +144 -0
  25. package/edit-buffer.d.ts +98 -0
  26. package/editor-view.d.ts +73 -0
  27. package/index-j4m38kjn.js +411 -0
  28. package/index-j4m38kjn.js.map +10 -0
  29. package/index-tse8gzh0.js +20614 -0
  30. package/index-tse8gzh0.js.map +67 -0
  31. package/index-vv2jcd4r.js +12299 -0
  32. package/index-vv2jcd4r.js.map +42 -0
  33. package/index.d.ts +23 -0
  34. package/index.js +478 -0
  35. package/index.js.map +9 -0
  36. package/lib/KeyHandler.d.ts +61 -0
  37. package/lib/RGBA.d.ts +25 -0
  38. package/lib/ascii.font.d.ts +508 -0
  39. package/lib/border.d.ts +51 -0
  40. package/lib/bunfs.d.ts +7 -0
  41. package/lib/clipboard.d.ts +17 -0
  42. package/lib/clock.d.ts +15 -0
  43. package/lib/data-paths.d.ts +26 -0
  44. package/lib/debounce.d.ts +42 -0
  45. package/lib/detect-links.d.ts +6 -0
  46. package/lib/env.d.ts +42 -0
  47. package/lib/extmarks-history.d.ts +17 -0
  48. package/lib/extmarks.d.ts +89 -0
  49. package/lib/hast-styled-text.d.ts +17 -0
  50. package/lib/index.d.ts +21 -0
  51. package/lib/keymapping.d.ts +25 -0
  52. package/lib/objects-in-viewport.d.ts +24 -0
  53. package/lib/output.capture.d.ts +24 -0
  54. package/lib/parse.keypress-kitty.d.ts +2 -0
  55. package/lib/parse.keypress.d.ts +26 -0
  56. package/lib/parse.mouse.d.ts +30 -0
  57. package/lib/paste.d.ts +7 -0
  58. package/lib/queue.d.ts +15 -0
  59. package/lib/renderable.validations.d.ts +12 -0
  60. package/lib/scroll-acceleration.d.ts +43 -0
  61. package/lib/selection.d.ts +63 -0
  62. package/lib/singleton.d.ts +7 -0
  63. package/lib/stdin-parser.d.ts +87 -0
  64. package/lib/styled-text.d.ts +63 -0
  65. package/lib/terminal-capability-detection.d.ts +30 -0
  66. package/lib/terminal-palette.d.ts +50 -0
  67. package/lib/tree-sitter/assets/update.d.ts +11 -0
  68. package/lib/tree-sitter/client.d.ts +47 -0
  69. package/lib/tree-sitter/default-parsers.d.ts +2 -0
  70. package/lib/tree-sitter/download-utils.d.ts +21 -0
  71. package/lib/tree-sitter/index.d.ts +8 -0
  72. package/lib/tree-sitter/parser.worker.d.ts +1 -0
  73. package/lib/tree-sitter/parsers-config.d.ts +53 -0
  74. package/lib/tree-sitter/resolve-ft.d.ts +5 -0
  75. package/lib/tree-sitter/types.d.ts +82 -0
  76. package/lib/tree-sitter-styled-text.d.ts +14 -0
  77. package/lib/validate-dir-name.d.ts +1 -0
  78. package/lib/yoga.options.d.ts +32 -0
  79. package/package.json +50 -62
  80. package/parser.worker.js +899 -0
  81. package/parser.worker.js.map +12 -0
  82. package/plugins/core-slot.d.ts +72 -0
  83. package/plugins/registry.d.ts +42 -0
  84. package/plugins/types.d.ts +34 -0
  85. package/post/effects.d.ts +147 -0
  86. package/post/filters.d.ts +65 -0
  87. package/post/matrices.d.ts +20 -0
  88. package/renderables/ASCIIFont.d.ts +52 -0
  89. package/renderables/Box.d.ts +81 -0
  90. package/renderables/Code.d.ts +78 -0
  91. package/renderables/Diff.d.ts +142 -0
  92. package/renderables/EditBufferRenderable.d.ts +237 -0
  93. package/renderables/FrameBuffer.d.ts +16 -0
  94. package/renderables/Input.d.ts +67 -0
  95. package/renderables/LineNumberRenderable.d.ts +78 -0
  96. package/renderables/Markdown.d.ts +185 -0
  97. package/renderables/ScrollBar.d.ts +77 -0
  98. package/renderables/ScrollBox.d.ts +124 -0
  99. package/renderables/Select.d.ts +115 -0
  100. package/renderables/Slider.d.ts +47 -0
  101. package/renderables/TabSelect.d.ts +96 -0
  102. package/renderables/Text.d.ts +36 -0
  103. package/renderables/TextBufferRenderable.d.ts +105 -0
  104. package/renderables/TextNode.d.ts +91 -0
  105. package/renderables/TextTable.d.ts +140 -0
  106. package/renderables/Textarea.d.ts +63 -0
  107. package/renderables/TimeToFirstDraw.d.ts +24 -0
  108. package/renderables/__tests__/renderable-test-utils.d.ts +12 -0
  109. package/renderables/composition/VRenderable.d.ts +16 -0
  110. package/renderables/composition/constructs.d.ts +35 -0
  111. package/renderables/composition/vnode.d.ts +46 -0
  112. package/renderables/index.d.ts +23 -0
  113. package/renderables/markdown-parser.d.ts +10 -0
  114. package/renderer.d.ts +419 -0
  115. package/runtime-plugin-support.d.ts +3 -0
  116. package/runtime-plugin-support.js +29 -0
  117. package/runtime-plugin-support.js.map +10 -0
  118. package/runtime-plugin.d.ts +16 -0
  119. package/runtime-plugin.js +16 -0
  120. package/runtime-plugin.js.map +9 -0
  121. package/syntax-style.d.ts +54 -0
  122. package/testing/manual-clock.d.ts +17 -0
  123. package/testing/mock-keys.d.ts +81 -0
  124. package/testing/mock-mouse.d.ts +38 -0
  125. package/testing/mock-tree-sitter-client.d.ts +23 -0
  126. package/testing/spy.d.ts +7 -0
  127. package/testing/test-recorder.d.ts +61 -0
  128. package/testing/test-renderer.d.ts +23 -0
  129. package/testing.d.ts +6 -0
  130. package/testing.js +697 -0
  131. package/testing.js.map +15 -0
  132. package/text-buffer-view.d.ts +42 -0
  133. package/text-buffer.d.ts +67 -0
  134. package/types.d.ts +139 -0
  135. package/utils.d.ts +14 -0
  136. package/zig-structs.d.ts +155 -0
  137. package/zig.d.ts +353 -0
  138. package/dev/keypress-debug-renderer.ts +0 -148
  139. package/dev/keypress-debug.ts +0 -43
  140. package/dev/print-env-vars.ts +0 -32
  141. package/dev/test-tmux-graphics-334.sh +0 -68
  142. package/dev/thai-debug-test.ts +0 -68
  143. package/docs/development.md +0 -144
  144. package/scripts/build.ts +0 -400
  145. package/scripts/publish.ts +0 -60
  146. package/src/3d/SpriteResourceManager.ts +0 -286
  147. package/src/3d/SpriteUtils.ts +0 -70
  148. package/src/3d/TextureUtils.ts +0 -196
  149. package/src/3d/ThreeRenderable.ts +0 -197
  150. package/src/3d/WGPURenderer.ts +0 -294
  151. package/src/3d/animation/ExplodingSpriteEffect.ts +0 -513
  152. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +0 -429
  153. package/src/3d/animation/SpriteAnimator.ts +0 -633
  154. package/src/3d/animation/SpriteParticleGenerator.ts +0 -435
  155. package/src/3d/canvas.ts +0 -464
  156. package/src/3d/index.ts +0 -12
  157. package/src/3d/physics/PlanckPhysicsAdapter.ts +0 -72
  158. package/src/3d/physics/RapierPhysicsAdapter.ts +0 -66
  159. package/src/3d/physics/physics-interface.ts +0 -31
  160. package/src/3d/shaders/supersampling.wgsl +0 -201
  161. package/src/3d.ts +0 -3
  162. package/src/NativeSpanFeed.ts +0 -300
  163. package/src/Renderable.ts +0 -1704
  164. package/src/__snapshots__/buffer.test.ts.snap +0 -28
  165. package/src/animation/Timeline.test.ts +0 -2709
  166. package/src/animation/Timeline.ts +0 -598
  167. package/src/ansi.ts +0 -18
  168. package/src/benchmark/attenuation-benchmark.ts +0 -81
  169. package/src/benchmark/colormatrix-benchmark.ts +0 -128
  170. package/src/benchmark/gain-benchmark.ts +0 -80
  171. package/src/benchmark/latest-all-bench-run.json +0 -707
  172. package/src/benchmark/latest-async-bench-run.json +0 -336
  173. package/src/benchmark/latest-default-bench-run.json +0 -657
  174. package/src/benchmark/latest-large-bench-run.json +0 -707
  175. package/src/benchmark/latest-quick-bench-run.json +0 -207
  176. package/src/benchmark/markdown-benchmark.ts +0 -1796
  177. package/src/benchmark/native-span-feed-async-benchmark.ts +0 -355
  178. package/src/benchmark/native-span-feed-benchmark.md +0 -56
  179. package/src/benchmark/native-span-feed-benchmark.ts +0 -596
  180. package/src/benchmark/native-span-feed-compare.ts +0 -280
  181. package/src/benchmark/renderer-benchmark.ts +0 -754
  182. package/src/benchmark/text-table-benchmark.ts +0 -948
  183. package/src/buffer.test.ts +0 -291
  184. package/src/buffer.ts +0 -554
  185. package/src/console.test.ts +0 -612
  186. package/src/console.ts +0 -1254
  187. package/src/edit-buffer.test.ts +0 -1769
  188. package/src/edit-buffer.ts +0 -411
  189. package/src/editor-view.test.ts +0 -1032
  190. package/src/editor-view.ts +0 -284
  191. package/src/examples/ascii-font-selection-demo.ts +0 -245
  192. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  193. package/src/examples/assets/concrete.png +0 -0
  194. package/src/examples/assets/crate.png +0 -0
  195. package/src/examples/assets/crate_emissive.png +0 -0
  196. package/src/examples/assets/forrest_background.png +0 -0
  197. package/src/examples/assets/hast-example.json +0 -1018
  198. package/src/examples/assets/heart.png +0 -0
  199. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  200. package/src/examples/assets/main_char_idle.png +0 -0
  201. package/src/examples/assets/main_char_jump_end.png +0 -0
  202. package/src/examples/assets/main_char_jump_landing.png +0 -0
  203. package/src/examples/assets/main_char_jump_start.png +0 -0
  204. package/src/examples/assets/main_char_run_loop.png +0 -0
  205. package/src/examples/assets/roughness_map.jpg +0 -0
  206. package/src/examples/build.ts +0 -115
  207. package/src/examples/code-demo.ts +0 -924
  208. package/src/examples/console-demo.ts +0 -358
  209. package/src/examples/core-plugin-slots-demo.ts +0 -759
  210. package/src/examples/diff-demo.ts +0 -701
  211. package/src/examples/draggable-three-demo.ts +0 -259
  212. package/src/examples/editor-demo.ts +0 -322
  213. package/src/examples/extmarks-demo.ts +0 -196
  214. package/src/examples/focus-restore-demo.ts +0 -310
  215. package/src/examples/fonts.ts +0 -245
  216. package/src/examples/fractal-shader-demo.ts +0 -268
  217. package/src/examples/framebuffer-demo.ts +0 -674
  218. package/src/examples/full-unicode-demo.ts +0 -241
  219. package/src/examples/golden-star-demo.ts +0 -933
  220. package/src/examples/grayscale-buffer-demo.ts +0 -249
  221. package/src/examples/hast-syntax-highlighting-demo.ts +0 -129
  222. package/src/examples/index.ts +0 -926
  223. package/src/examples/input-demo.ts +0 -377
  224. package/src/examples/input-select-layout-demo.ts +0 -425
  225. package/src/examples/install.sh +0 -143
  226. package/src/examples/keypress-debug-demo.ts +0 -452
  227. package/src/examples/lib/HexList.ts +0 -122
  228. package/src/examples/lib/PaletteGrid.ts +0 -125
  229. package/src/examples/lib/standalone-keys.ts +0 -25
  230. package/src/examples/lib/tab-controller.ts +0 -243
  231. package/src/examples/lights-phong-demo.ts +0 -290
  232. package/src/examples/link-demo.ts +0 -220
  233. package/src/examples/live-state-demo.ts +0 -480
  234. package/src/examples/markdown-demo.ts +0 -725
  235. package/src/examples/mouse-interaction-demo.ts +0 -428
  236. package/src/examples/nested-zindex-demo.ts +0 -357
  237. package/src/examples/opacity-example.ts +0 -235
  238. package/src/examples/opentui-demo.ts +0 -1057
  239. package/src/examples/physx-planck-2d-demo.ts +0 -623
  240. package/src/examples/physx-rapier-2d-demo.ts +0 -655
  241. package/src/examples/relative-positioning-demo.ts +0 -323
  242. package/src/examples/scroll-example.ts +0 -214
  243. package/src/examples/scrollbox-mouse-test.ts +0 -112
  244. package/src/examples/scrollbox-overlay-hit-test.ts +0 -206
  245. package/src/examples/select-demo.ts +0 -237
  246. package/src/examples/shader-cube-demo.ts +0 -1015
  247. package/src/examples/simple-layout-example.ts +0 -591
  248. package/src/examples/slider-demo.ts +0 -617
  249. package/src/examples/split-mode-demo.ts +0 -453
  250. package/src/examples/sprite-animation-demo.ts +0 -443
  251. package/src/examples/sprite-particle-generator-demo.ts +0 -486
  252. package/src/examples/static-sprite-demo.ts +0 -193
  253. package/src/examples/sticky-scroll-example.ts +0 -308
  254. package/src/examples/styled-text-demo.ts +0 -282
  255. package/src/examples/tab-select-demo.ts +0 -219
  256. package/src/examples/terminal-title.ts +0 -29
  257. package/src/examples/terminal.ts +0 -305
  258. package/src/examples/text-node-demo.ts +0 -416
  259. package/src/examples/text-selection-demo.ts +0 -377
  260. package/src/examples/text-table-demo.ts +0 -503
  261. package/src/examples/text-truncation-demo.ts +0 -481
  262. package/src/examples/text-wrap.ts +0 -757
  263. package/src/examples/texture-loading-demo.ts +0 -259
  264. package/src/examples/timeline-example.ts +0 -670
  265. package/src/examples/transparency-demo.ts +0 -400
  266. package/src/examples/vnode-composition-demo.ts +0 -404
  267. package/src/examples/wide-grapheme-overlay-demo.ts +0 -280
  268. package/src/index.ts +0 -24
  269. package/src/lib/KeyHandler.integration.test.ts +0 -292
  270. package/src/lib/KeyHandler.stopPropagation.test.ts +0 -289
  271. package/src/lib/KeyHandler.test.ts +0 -662
  272. package/src/lib/KeyHandler.ts +0 -222
  273. package/src/lib/RGBA.test.ts +0 -984
  274. package/src/lib/RGBA.ts +0 -204
  275. package/src/lib/ascii.font.ts +0 -330
  276. package/src/lib/border.test.ts +0 -83
  277. package/src/lib/border.ts +0 -170
  278. package/src/lib/bunfs.test.ts +0 -27
  279. package/src/lib/bunfs.ts +0 -18
  280. package/src/lib/clipboard.test.ts +0 -41
  281. package/src/lib/clipboard.ts +0 -47
  282. package/src/lib/clock.ts +0 -35
  283. package/src/lib/data-paths.test.ts +0 -133
  284. package/src/lib/data-paths.ts +0 -109
  285. package/src/lib/debounce.ts +0 -106
  286. package/src/lib/detect-links.test.ts +0 -98
  287. package/src/lib/detect-links.ts +0 -56
  288. package/src/lib/env.test.ts +0 -228
  289. package/src/lib/env.ts +0 -209
  290. package/src/lib/extmarks-history.ts +0 -51
  291. package/src/lib/extmarks-multiwidth.test.ts +0 -322
  292. package/src/lib/extmarks.test.ts +0 -3457
  293. package/src/lib/extmarks.ts +0 -843
  294. package/src/lib/fonts/block.json +0 -405
  295. package/src/lib/fonts/grid.json +0 -265
  296. package/src/lib/fonts/huge.json +0 -741
  297. package/src/lib/fonts/pallet.json +0 -314
  298. package/src/lib/fonts/shade.json +0 -591
  299. package/src/lib/fonts/slick.json +0 -321
  300. package/src/lib/fonts/tiny.json +0 -69
  301. package/src/lib/hast-styled-text.ts +0 -59
  302. package/src/lib/index.ts +0 -21
  303. package/src/lib/keymapping.test.ts +0 -317
  304. package/src/lib/keymapping.ts +0 -115
  305. package/src/lib/objects-in-viewport.test.ts +0 -787
  306. package/src/lib/objects-in-viewport.ts +0 -153
  307. package/src/lib/output.capture.ts +0 -58
  308. package/src/lib/parse.keypress-kitty.protocol.test.ts +0 -340
  309. package/src/lib/parse.keypress-kitty.test.ts +0 -663
  310. package/src/lib/parse.keypress-kitty.ts +0 -439
  311. package/src/lib/parse.keypress.test.ts +0 -1849
  312. package/src/lib/parse.keypress.ts +0 -397
  313. package/src/lib/parse.mouse.test.ts +0 -552
  314. package/src/lib/parse.mouse.ts +0 -232
  315. package/src/lib/paste.ts +0 -16
  316. package/src/lib/queue.ts +0 -65
  317. package/src/lib/renderable.validations.test.ts +0 -87
  318. package/src/lib/renderable.validations.ts +0 -83
  319. package/src/lib/scroll-acceleration.ts +0 -98
  320. package/src/lib/selection.ts +0 -240
  321. package/src/lib/singleton.ts +0 -28
  322. package/src/lib/stdin-parser.test.ts +0 -2290
  323. package/src/lib/stdin-parser.ts +0 -1810
  324. package/src/lib/styled-text.ts +0 -178
  325. package/src/lib/terminal-capability-detection.test.ts +0 -202
  326. package/src/lib/terminal-capability-detection.ts +0 -79
  327. package/src/lib/terminal-palette.test.ts +0 -878
  328. package/src/lib/terminal-palette.ts +0 -383
  329. package/src/lib/tree-sitter/assets/README.md +0 -118
  330. package/src/lib/tree-sitter/assets/update.ts +0 -334
  331. package/src/lib/tree-sitter/assets.d.ts +0 -9
  332. package/src/lib/tree-sitter/cache.test.ts +0 -273
  333. package/src/lib/tree-sitter/client.test.ts +0 -1165
  334. package/src/lib/tree-sitter/client.ts +0 -607
  335. package/src/lib/tree-sitter/default-parsers.ts +0 -86
  336. package/src/lib/tree-sitter/download-utils.ts +0 -148
  337. package/src/lib/tree-sitter/index.ts +0 -28
  338. package/src/lib/tree-sitter/parser.worker.ts +0 -1042
  339. package/src/lib/tree-sitter/parsers-config.ts +0 -81
  340. package/src/lib/tree-sitter/resolve-ft.test.ts +0 -55
  341. package/src/lib/tree-sitter/resolve-ft.ts +0 -189
  342. package/src/lib/tree-sitter/types.ts +0 -82
  343. package/src/lib/tree-sitter-styled-text.test.ts +0 -1253
  344. package/src/lib/tree-sitter-styled-text.ts +0 -306
  345. package/src/lib/validate-dir-name.ts +0 -55
  346. package/src/lib/yoga.options.test.ts +0 -628
  347. package/src/lib/yoga.options.ts +0 -346
  348. package/src/plugins/core-slot.ts +0 -579
  349. package/src/plugins/registry.ts +0 -402
  350. package/src/plugins/types.ts +0 -46
  351. package/src/post/effects.ts +0 -930
  352. package/src/post/filters.ts +0 -489
  353. package/src/post/matrices.ts +0 -288
  354. package/src/renderables/ASCIIFont.ts +0 -219
  355. package/src/renderables/Box.test.ts +0 -205
  356. package/src/renderables/Box.ts +0 -326
  357. package/src/renderables/Code.test.ts +0 -2062
  358. package/src/renderables/Code.ts +0 -357
  359. package/src/renderables/Diff.regression.test.ts +0 -226
  360. package/src/renderables/Diff.test.ts +0 -3101
  361. package/src/renderables/Diff.ts +0 -1211
  362. package/src/renderables/EditBufferRenderable.test.ts +0 -288
  363. package/src/renderables/EditBufferRenderable.ts +0 -1166
  364. package/src/renderables/FrameBuffer.ts +0 -47
  365. package/src/renderables/Input.test.ts +0 -1228
  366. package/src/renderables/Input.ts +0 -247
  367. package/src/renderables/LineNumberRenderable.ts +0 -724
  368. package/src/renderables/Markdown.ts +0 -1393
  369. package/src/renderables/ScrollBar.ts +0 -422
  370. package/src/renderables/ScrollBox.ts +0 -883
  371. package/src/renderables/Select.test.ts +0 -1033
  372. package/src/renderables/Select.ts +0 -524
  373. package/src/renderables/Slider.test.ts +0 -456
  374. package/src/renderables/Slider.ts +0 -342
  375. package/src/renderables/TabSelect.test.ts +0 -197
  376. package/src/renderables/TabSelect.ts +0 -455
  377. package/src/renderables/Text.selection-buffer.test.ts +0 -123
  378. package/src/renderables/Text.test.ts +0 -2660
  379. package/src/renderables/Text.ts +0 -147
  380. package/src/renderables/TextBufferRenderable.ts +0 -518
  381. package/src/renderables/TextNode.test.ts +0 -1058
  382. package/src/renderables/TextNode.ts +0 -325
  383. package/src/renderables/TextTable.test.ts +0 -1421
  384. package/src/renderables/TextTable.ts +0 -1344
  385. package/src/renderables/Textarea.ts +0 -430
  386. package/src/renderables/TimeToFirstDraw.ts +0 -89
  387. package/src/renderables/__snapshots__/Code.test.ts.snap +0 -13
  388. package/src/renderables/__snapshots__/Diff.test.ts.snap +0 -785
  389. package/src/renderables/__snapshots__/Text.test.ts.snap +0 -421
  390. package/src/renderables/__snapshots__/TextTable.test.ts.snap +0 -215
  391. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +0 -144
  392. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +0 -816
  393. package/src/renderables/__tests__/LineNumberRenderable.test.ts +0 -1865
  394. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +0 -85
  395. package/src/renderables/__tests__/Markdown.code-colors.test.ts +0 -242
  396. package/src/renderables/__tests__/Markdown.test.ts +0 -2518
  397. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +0 -87
  398. package/src/renderables/__tests__/Textarea.buffer.test.ts +0 -682
  399. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +0 -675
  400. package/src/renderables/__tests__/Textarea.editing.test.ts +0 -2041
  401. package/src/renderables/__tests__/Textarea.error-handling.test.ts +0 -35
  402. package/src/renderables/__tests__/Textarea.events.test.ts +0 -738
  403. package/src/renderables/__tests__/Textarea.highlights.test.ts +0 -590
  404. package/src/renderables/__tests__/Textarea.keybinding.test.ts +0 -3149
  405. package/src/renderables/__tests__/Textarea.paste.test.ts +0 -357
  406. package/src/renderables/__tests__/Textarea.rendering.test.ts +0 -1866
  407. package/src/renderables/__tests__/Textarea.scroll.test.ts +0 -733
  408. package/src/renderables/__tests__/Textarea.selection.test.ts +0 -1590
  409. package/src/renderables/__tests__/Textarea.stress.test.ts +0 -670
  410. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +0 -383
  411. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +0 -310
  412. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +0 -221
  413. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +0 -89
  414. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +0 -457
  415. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +0 -158
  416. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +0 -387
  417. package/src/renderables/__tests__/markdown-parser.test.ts +0 -217
  418. package/src/renderables/__tests__/renderable-test-utils.ts +0 -60
  419. package/src/renderables/composition/README.md +0 -8
  420. package/src/renderables/composition/VRenderable.ts +0 -32
  421. package/src/renderables/composition/constructs.ts +0 -127
  422. package/src/renderables/composition/vnode.ts +0 -289
  423. package/src/renderables/index.ts +0 -23
  424. package/src/renderables/markdown-parser.ts +0 -66
  425. package/src/renderer.ts +0 -2681
  426. package/src/runtime-plugin-support.ts +0 -39
  427. package/src/runtime-plugin.ts +0 -615
  428. package/src/syntax-style.test.ts +0 -841
  429. package/src/syntax-style.ts +0 -257
  430. package/src/testing/README.md +0 -210
  431. package/src/testing/capture-spans.test.ts +0 -194
  432. package/src/testing/integration.test.ts +0 -276
  433. package/src/testing/manual-clock.ts +0 -117
  434. package/src/testing/mock-keys.test.ts +0 -1378
  435. package/src/testing/mock-keys.ts +0 -457
  436. package/src/testing/mock-mouse.test.ts +0 -218
  437. package/src/testing/mock-mouse.ts +0 -247
  438. package/src/testing/mock-tree-sitter-client.ts +0 -73
  439. package/src/testing/spy.ts +0 -13
  440. package/src/testing/test-recorder.test.ts +0 -415
  441. package/src/testing/test-recorder.ts +0 -145
  442. package/src/testing/test-renderer.ts +0 -132
  443. package/src/testing.ts +0 -7
  444. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +0 -481
  445. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +0 -19
  446. package/src/tests/__snapshots__/scrollbox.test.ts.snap +0 -29
  447. package/src/tests/absolute-positioning.snapshot.test.ts +0 -638
  448. package/src/tests/allocator-stats.test.ts +0 -38
  449. package/src/tests/destroy-during-render.test.ts +0 -200
  450. package/src/tests/destroy-on-exit.fixture.ts +0 -36
  451. package/src/tests/destroy-on-exit.test.ts +0 -41
  452. package/src/tests/hover-cursor.test.ts +0 -98
  453. package/src/tests/native-span-feed-async.test.ts +0 -173
  454. package/src/tests/native-span-feed-close.test.ts +0 -120
  455. package/src/tests/native-span-feed-coverage.test.ts +0 -227
  456. package/src/tests/native-span-feed-edge-cases.test.ts +0 -352
  457. package/src/tests/native-span-feed-use-after-free.test.ts +0 -45
  458. package/src/tests/opacity.test.ts +0 -123
  459. package/src/tests/renderable.snapshot.test.ts +0 -524
  460. package/src/tests/renderable.test.ts +0 -1281
  461. package/src/tests/renderer.clock.test.ts +0 -158
  462. package/src/tests/renderer.console-startup.test.ts +0 -185
  463. package/src/tests/renderer.control.test.ts +0 -425
  464. package/src/tests/renderer.core-slot-binding.test.ts +0 -952
  465. package/src/tests/renderer.cursor.test.ts +0 -26
  466. package/src/tests/renderer.destroy-during-render.test.ts +0 -147
  467. package/src/tests/renderer.focus-restore.test.ts +0 -257
  468. package/src/tests/renderer.focus.test.ts +0 -294
  469. package/src/tests/renderer.idle.test.ts +0 -219
  470. package/src/tests/renderer.input.test.ts +0 -2237
  471. package/src/tests/renderer.kitty-flags.test.ts +0 -195
  472. package/src/tests/renderer.mouse.test.ts +0 -1274
  473. package/src/tests/renderer.palette.test.ts +0 -629
  474. package/src/tests/renderer.selection.test.ts +0 -49
  475. package/src/tests/renderer.slot-registry.test.ts +0 -684
  476. package/src/tests/renderer.useMouse.test.ts +0 -47
  477. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +0 -76
  478. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +0 -43
  479. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +0 -67
  480. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +0 -72
  481. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +0 -44
  482. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +0 -85
  483. package/src/tests/runtime-plugin-path-alias.fixture.ts +0 -43
  484. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +0 -65
  485. package/src/tests/runtime-plugin-support.fixture.ts +0 -11
  486. package/src/tests/runtime-plugin-support.test.ts +0 -19
  487. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +0 -30
  488. package/src/tests/runtime-plugin.fixture.ts +0 -40
  489. package/src/tests/runtime-plugin.test.ts +0 -354
  490. package/src/tests/scrollbox-culling-bug.test.ts +0 -114
  491. package/src/tests/scrollbox-hitgrid-resize.test.ts +0 -136
  492. package/src/tests/scrollbox-hitgrid.test.ts +0 -909
  493. package/src/tests/scrollbox.test.ts +0 -1530
  494. package/src/tests/wrap-resize-perf.test.ts +0 -276
  495. package/src/tests/yoga-setters.test.ts +0 -921
  496. package/src/text-buffer-view.test.ts +0 -705
  497. package/src/text-buffer-view.ts +0 -189
  498. package/src/text-buffer.test.ts +0 -347
  499. package/src/text-buffer.ts +0 -250
  500. package/src/types.ts +0 -161
  501. package/src/utils.ts +0 -88
  502. package/src/zig/ansi.zig +0 -268
  503. package/src/zig/bench/README.md +0 -50
  504. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +0 -887
  505. package/src/zig/bench/edit-buffer_bench.zig +0 -476
  506. package/src/zig/bench/native-span-feed_bench.zig +0 -100
  507. package/src/zig/bench/rope-markers_bench.zig +0 -713
  508. package/src/zig/bench/rope_bench.zig +0 -514
  509. package/src/zig/bench/styled-text_bench.zig +0 -470
  510. package/src/zig/bench/text-buffer-coords_bench.zig +0 -362
  511. package/src/zig/bench/text-buffer-view_bench.zig +0 -459
  512. package/src/zig/bench/text-chunk-graphemes_bench.zig +0 -273
  513. package/src/zig/bench/utf8_bench.zig +0 -799
  514. package/src/zig/bench-utils.zig +0 -431
  515. package/src/zig/bench.zig +0 -217
  516. package/src/zig/buffer-methods.zig +0 -211
  517. package/src/zig/buffer.zig +0 -2281
  518. package/src/zig/build.zig +0 -289
  519. package/src/zig/build.zig.zon +0 -16
  520. package/src/zig/edit-buffer.zig +0 -825
  521. package/src/zig/editor-view.zig +0 -802
  522. package/src/zig/event-bus.zig +0 -13
  523. package/src/zig/event-emitter.zig +0 -65
  524. package/src/zig/file-logger.zig +0 -92
  525. package/src/zig/grapheme.zig +0 -599
  526. package/src/zig/lib.zig +0 -1854
  527. package/src/zig/link.zig +0 -333
  528. package/src/zig/logger.zig +0 -43
  529. package/src/zig/mem-registry.zig +0 -125
  530. package/src/zig/native-span-feed-bench-lib.zig +0 -7
  531. package/src/zig/native-span-feed.zig +0 -708
  532. package/src/zig/renderer.zig +0 -1393
  533. package/src/zig/rope.zig +0 -1220
  534. package/src/zig/syntax-style.zig +0 -161
  535. package/src/zig/terminal.zig +0 -987
  536. package/src/zig/test.zig +0 -72
  537. package/src/zig/tests/README.md +0 -18
  538. package/src/zig/tests/buffer-methods_test.zig +0 -1109
  539. package/src/zig/tests/buffer_test.zig +0 -2557
  540. package/src/zig/tests/edit-buffer-history_test.zig +0 -271
  541. package/src/zig/tests/edit-buffer_test.zig +0 -1689
  542. package/src/zig/tests/editor-view_test.zig +0 -3299
  543. package/src/zig/tests/event-emitter_test.zig +0 -249
  544. package/src/zig/tests/grapheme_test.zig +0 -1304
  545. package/src/zig/tests/link_test.zig +0 -190
  546. package/src/zig/tests/mem-registry_test.zig +0 -473
  547. package/src/zig/tests/memory_leak_regression_test.zig +0 -159
  548. package/src/zig/tests/native-span-feed_test.zig +0 -1264
  549. package/src/zig/tests/renderer_test.zig +0 -1017
  550. package/src/zig/tests/rope-nested_test.zig +0 -712
  551. package/src/zig/tests/rope_fuzz_test.zig +0 -238
  552. package/src/zig/tests/rope_test.zig +0 -2362
  553. package/src/zig/tests/segment-merge.test.zig +0 -148
  554. package/src/zig/tests/syntax-style_test.zig +0 -557
  555. package/src/zig/tests/terminal_test.zig +0 -754
  556. package/src/zig/tests/text-buffer-drawing_test.zig +0 -3237
  557. package/src/zig/tests/text-buffer-highlights_test.zig +0 -666
  558. package/src/zig/tests/text-buffer-iterators_test.zig +0 -776
  559. package/src/zig/tests/text-buffer-segment_test.zig +0 -320
  560. package/src/zig/tests/text-buffer-selection_test.zig +0 -1035
  561. package/src/zig/tests/text-buffer-selection_viewport_test.zig +0 -358
  562. package/src/zig/tests/text-buffer-view_test.zig +0 -3649
  563. package/src/zig/tests/text-buffer_test.zig +0 -2191
  564. package/src/zig/tests/unicode-width-map.zon +0 -3909
  565. package/src/zig/tests/utf8_no_zwj_test.zig +0 -260
  566. package/src/zig/tests/utf8_test.zig +0 -4057
  567. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +0 -267
  568. package/src/zig/tests/utf8_wcwidth_test.zig +0 -357
  569. package/src/zig/tests/word-wrap-editing_test.zig +0 -498
  570. package/src/zig/tests/wrap-cache-perf_test.zig +0 -113
  571. package/src/zig/text-buffer-iterators.zig +0 -499
  572. package/src/zig/text-buffer-segment.zig +0 -404
  573. package/src/zig/text-buffer-view.zig +0 -1371
  574. package/src/zig/text-buffer.zig +0 -1180
  575. package/src/zig/utf8.zig +0 -1948
  576. package/src/zig/utils.zig +0 -9
  577. package/src/zig-structs.ts +0 -261
  578. package/src/zig.ts +0 -3884
  579. package/tsconfig.build.json +0 -24
  580. package/tsconfig.json +0 -27
  581. /package/{src/lib/tree-sitter/assets → assets}/javascript/highlights.scm +0 -0
  582. /package/{src/lib/tree-sitter/assets → assets}/javascript/tree-sitter-javascript.wasm +0 -0
  583. /package/{src/lib/tree-sitter/assets → assets}/markdown/highlights.scm +0 -0
  584. /package/{src/lib/tree-sitter/assets → assets}/markdown/injections.scm +0 -0
  585. /package/{src/lib/tree-sitter/assets → assets}/markdown/tree-sitter-markdown.wasm +0 -0
  586. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/highlights.scm +0 -0
  587. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  588. /package/{src/lib/tree-sitter/assets → assets}/typescript/highlights.scm +0 -0
  589. /package/{src/lib/tree-sitter/assets → assets}/typescript/tree-sitter-typescript.wasm +0 -0
  590. /package/{src/lib/tree-sitter/assets → assets}/zig/highlights.scm +0 -0
  591. /package/{src/lib/tree-sitter/assets → assets}/zig/tree-sitter-zig.wasm +0 -0
package/src/renderer.ts DELETED
@@ -1,2681 +0,0 @@
1
- import { ANSI } from "./ansi.js"
2
- import { Renderable, RootRenderable } from "./Renderable.js"
3
- import {
4
- DebugOverlayCorner,
5
- type CursorStyleOptions,
6
- type MousePointerStyle,
7
- type RenderContext,
8
- type ThemeMode,
9
- type ViewportBounds,
10
- type WidthMethod,
11
- } from "./types.js"
12
- import { RGBA, parseColor, type ColorInput } from "./lib/RGBA.js"
13
- import type { Pointer } from "bun:ffi"
14
- import { OptimizedBuffer } from "./buffer.js"
15
- import { resolveRenderLib, type RenderLib } from "./zig.js"
16
- import { TerminalConsole, type ConsoleOptions, capture } from "./console.js"
17
- import { type MouseEventType, type RawMouseEvent, type ScrollInfo } from "./lib/parse.mouse.js"
18
- import { Selection } from "./lib/selection.js"
19
- import { Clipboard, type ClipboardTarget } from "./lib/clipboard.js"
20
- import { EventEmitter } from "events"
21
- import { destroySingleton, hasSingleton, singleton } from "./lib/singleton.js"
22
- import { getObjectsInViewport } from "./lib/objects-in-viewport.js"
23
- import { KeyHandler, InternalKeyHandler } from "./lib/KeyHandler.js"
24
- import { isEditBufferRenderable, type EditBufferRenderable } from "./renderables/EditBufferRenderable.js"
25
- import { env, registerEnvVar } from "./lib/env.js"
26
- import { getTreeSitterClient } from "./lib/tree-sitter/index.js"
27
- import {
28
- createTerminalPalette,
29
- type TerminalPaletteDetector,
30
- type TerminalColors,
31
- type GetPaletteOptions,
32
- } from "./lib/terminal-palette.js"
33
- import {
34
- isCapabilityResponse,
35
- isPixelResolutionResponse,
36
- parsePixelResolution,
37
- } from "./lib/terminal-capability-detection.js"
38
- import { type Clock, type TimerHandle, SystemClock } from "./lib/clock.js"
39
- import { StdinParser, type StdinEvent, type StdinParserProtocolContext } from "./lib/stdin-parser.js"
40
-
41
- registerEnvVar({
42
- name: "OTUI_DUMP_CAPTURES",
43
- description: "Dump captured stdout and console caches when the renderer exit handler runs.",
44
- type: "boolean",
45
- default: false,
46
- })
47
-
48
- registerEnvVar({
49
- name: "OTUI_NO_NATIVE_RENDER",
50
- description:
51
- "Skip the Zig/native frame renderer. Useful for debugging the render loop; split-footer stdout flushing may still write ANSI.",
52
- type: "boolean",
53
- default: false,
54
- })
55
-
56
- registerEnvVar({
57
- name: "OTUI_USE_ALTERNATE_SCREEN",
58
- description: "When explicitly set, force screen mode selection: true=alternate-screen, false=main-screen.",
59
- type: "boolean",
60
- default: true,
61
- })
62
-
63
- registerEnvVar({
64
- name: "OTUI_OVERRIDE_STDOUT",
65
- description: "When explicitly set, force stdout routing: false=passthrough, true=capture in split-footer mode.",
66
- type: "boolean",
67
- default: true,
68
- })
69
-
70
- registerEnvVar({
71
- name: "OTUI_DEBUG",
72
- description: "Enable debug mode to capture all raw input for debugging purposes.",
73
- type: "boolean",
74
- default: false,
75
- })
76
-
77
- registerEnvVar({
78
- name: "OTUI_SHOW_STATS",
79
- description: "Show the debug overlay at startup.",
80
- type: "boolean",
81
- default: false,
82
- })
83
-
84
- export interface CliRendererConfig {
85
- // Read input from this stream. Defaults to process.stdin.
86
- stdin?: NodeJS.ReadStream
87
-
88
- // Use a custom stdout stream for size detection and stdout interception.
89
- // Native frame output still goes to the real TTY.
90
- stdout?: NodeJS.WriteStream
91
-
92
- // Tell the native renderer it is driving a remote terminal.
93
- remote?: boolean
94
-
95
- // Skip terminal setup. Useful in tests.
96
- testing?: boolean
97
-
98
- // Call renderer.destroy() when Ctrl+C is pressed. Defaults to true.
99
- exitOnCtrlC?: boolean
100
-
101
- // Clean up on these signals. Defaults to the common termination signals.
102
- exitSignals?: NodeJS.Signals[]
103
-
104
- // Forward these env var names to native terminal detection.
105
- forwardEnvKeys?: string[]
106
-
107
- // Wait this long before handling resize events. Defaults to 100 ms.
108
- debounceDelay?: number
109
-
110
- // Aim for this many frames per second in continuous mode. Defaults to 30.
111
- targetFps?: number
112
-
113
- // Cap immediate re-renders at this frame rate. Defaults to 60.
114
- maxFps?: number
115
-
116
- // Emit memory snapshots on this interval in ms. Set 0 to disable.
117
- memorySnapshotInterval?: number
118
-
119
- // Render from a separate thread when the platform supports it.
120
- useThread?: boolean
121
-
122
- // Collect frame timing stats for the debug overlay.
123
- gatherStats?: boolean
124
-
125
- // Keep this many timing samples. Defaults to 300.
126
- maxStatSamples?: number
127
-
128
- // Pass options to the built-in console overlay.
129
- consoleOptions?: Omit<ConsoleOptions, "clock">
130
-
131
- // Run these hooks after each render pass.
132
- postProcessFns?: ((buffer: OptimizedBuffer, deltaTime: number) => void)[]
133
-
134
- // Track mouse move events. Defaults to true.
135
- enableMouseMovement?: boolean
136
-
137
- // Enable mouse input. Defaults to true.
138
- useMouse?: boolean
139
-
140
- // Focus the nearest focusable renderable on left click. Defaults to true.
141
- autoFocus?: boolean
142
-
143
- // Choose where the renderer owns terminal space. Defaults to "alternate-screen".
144
- screenMode?: ScreenMode
145
-
146
- // Set the requested footer height for "split-footer". Defaults to 12.
147
- footerHeight?: number
148
-
149
- // Choose what happens to writes that go through `stdout.write`.
150
- externalOutputMode?: ExternalOutputMode
151
-
152
- // Choose what the built-in console overlay does.
153
- consoleMode?: ConsoleMode
154
-
155
- // Set Kitty keyboard protocol flags, or null to disable them.
156
- useKittyKeyboard?: KittyKeyboardOptions | null
157
-
158
- // Fill the render buffer with this background color. Default transparent.
159
- backgroundColor?: ColorInput
160
-
161
- // Open the console overlay on uncaught errors. Defaults to true in development.
162
- openConsoleOnError?: boolean
163
-
164
- // Run these input handlers before the built-in handlers.
165
- prependInputHandlers?: ((sequence: string) => boolean)[]
166
-
167
- // Cap the stdin parser buffer size in bytes. Defaults to 64 MB.
168
- stdinParserMaxBufferBytes?: number
169
-
170
- // Use a custom clock for timers and tests.
171
- clock?: Clock
172
-
173
- // Run after destroy() finishes cleanup.
174
- onDestroy?: () => void
175
- }
176
-
177
- // Controls how the renderer uses terminal space:
178
- //
179
- // - "alternate-screen": Use the terminal's alternate screen buffer.
180
- //
181
- // - "main-screen": Render on the main screen.
182
- //
183
- // - "split-footer": Keep the renderer in a reserved footer on the main screen.
184
- export type ScreenMode = "alternate-screen" | "main-screen" | "split-footer"
185
-
186
- // Controls writes that go through the configured `stdout.write`.
187
- //
188
- // - "capture-stdout": Queue stdout and replay it above the split footer.
189
- // Only valid with "split-footer".
190
- //
191
- // - "passthrough": Leave stdout alone.
192
- export type ExternalOutputMode = "capture-stdout" | "passthrough"
193
-
194
- // Controls the built-in console overlay:
195
- //
196
- // - "console-overlay": Capture `console.*` output and show the overlay.
197
- //
198
- // - "disabled": Hide the overlay. `OTUI_USE_CONSOLE` controls global console
199
- // capture.
200
- export type ConsoleMode = "console-overlay" | "disabled"
201
-
202
- export type PixelResolution = {
203
- width: number
204
- height: number
205
- }
206
-
207
- const DEFAULT_FOOTER_HEIGHT = 12
208
-
209
- function normalizeFooterHeight(footerHeight: number | undefined): number {
210
- if (footerHeight === undefined) {
211
- return DEFAULT_FOOTER_HEIGHT
212
- }
213
-
214
- if (!Number.isFinite(footerHeight)) {
215
- throw new Error("footerHeight must be a finite number")
216
- }
217
-
218
- const normalizedFooterHeight = Math.trunc(footerHeight)
219
- if (normalizedFooterHeight <= 0) {
220
- throw new Error("footerHeight must be greater than 0")
221
- }
222
-
223
- return normalizedFooterHeight
224
- }
225
-
226
- function resolveModes(config: CliRendererConfig): {
227
- screenMode: ScreenMode
228
- footerHeight: number
229
- externalOutputMode: ExternalOutputMode
230
- } {
231
- let screenMode = config.screenMode ?? "alternate-screen"
232
- if (process.env.OTUI_USE_ALTERNATE_SCREEN !== undefined) {
233
- screenMode = env.OTUI_USE_ALTERNATE_SCREEN ? "alternate-screen" : "main-screen"
234
- }
235
-
236
- const footerHeight =
237
- screenMode === "split-footer" ? normalizeFooterHeight(config.footerHeight) : DEFAULT_FOOTER_HEIGHT
238
-
239
- let externalOutputMode =
240
- config.externalOutputMode ?? (screenMode === "split-footer" ? "capture-stdout" : "passthrough")
241
- if (process.env.OTUI_OVERRIDE_STDOUT !== undefined) {
242
- externalOutputMode = env.OTUI_OVERRIDE_STDOUT && screenMode === "split-footer" ? "capture-stdout" : "passthrough"
243
- }
244
-
245
- if (externalOutputMode === "capture-stdout" && screenMode !== "split-footer") {
246
- throw new Error('externalOutputMode "capture-stdout" requires screenMode "split-footer"')
247
- }
248
-
249
- return {
250
- screenMode,
251
- footerHeight,
252
- externalOutputMode,
253
- }
254
- }
255
-
256
- const DEFAULT_FORWARDED_ENV_KEYS = [
257
- "TMUX",
258
- "TERM",
259
- "OPENTUI_GRAPHICS",
260
- "TERM_PROGRAM",
261
- "TERM_PROGRAM_VERSION",
262
- "ALACRITTY_SOCKET",
263
- "ALACRITTY_LOG",
264
- "COLORTERM",
265
- "TERMUX_VERSION",
266
- "VHS_RECORD",
267
- "OPENTUI_FORCE_WCWIDTH",
268
- "OPENTUI_FORCE_UNICODE",
269
- "OPENTUI_FORCE_NOZWJ",
270
- "OPENTUI_FORCE_EXPLICIT_WIDTH",
271
- "WT_SESSION",
272
- "STY",
273
- "WSL_DISTRO_NAME",
274
- "WSL_INTEROP",
275
- ] as const
276
-
277
- // Kitty keyboard protocol flags
278
- // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
279
- const KITTY_FLAG_DISAMBIGUATE = 0b1 // Report disambiguated escape codes
280
- const KITTY_FLAG_EVENT_TYPES = 0b10 // Report event types (press/repeat/release)
281
- const KITTY_FLAG_ALTERNATE_KEYS = 0b100 // Report alternate keys (e.g., numpad vs regular)
282
- const KITTY_FLAG_ALL_KEYS_AS_ESCAPES = 0b1000 // Report all keys as escape codes
283
- const KITTY_FLAG_REPORT_TEXT = 0b10000 // Report text associated with key events
284
-
285
- const DEFAULT_STDIN_PARSER_MAX_BUFFER_BYTES = 64 * 1024 * 1024
286
-
287
- /**
288
- * Kitty Keyboard Protocol configuration options
289
- * See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
290
- */
291
- export interface KittyKeyboardOptions {
292
- /** Disambiguate escape codes (fixes ESC timing, alt+key ambiguity, ctrl+c as event). Default: true */
293
- disambiguate?: boolean
294
- /** Report alternate keys (numpad, shifted, base layout) for cross-keyboard shortcuts. Default: true */
295
- alternateKeys?: boolean
296
- /** Report event types (press/repeat/release). Default: false */
297
- events?: boolean
298
- /** Report all keys as escape codes. Default: false */
299
- allKeysAsEscapes?: boolean
300
- /** Report text associated with key events. Default: false */
301
- reportText?: boolean
302
- }
303
-
304
- /**
305
- * Build kitty keyboard protocol flags based on configuration
306
- * @param config Kitty keyboard configuration object (null/undefined = disabled)
307
- * @returns The combined flags value (0 = disabled, >0 = enabled)
308
- * @internal Exported for testing
309
- */
310
- export function buildKittyKeyboardFlags(config: KittyKeyboardOptions | null | undefined): number {
311
- if (!config) {
312
- return 0
313
- }
314
-
315
- let flags = 0
316
-
317
- // Default: disambiguate + alternate keys (both default to true)
318
- // - Disambiguate (0b1): Fixes ESC timing issues, alt+key ambiguity, makes ctrl+c a key event
319
- // - Alternate keys (0b100): Reports shifted/base-layout keys for cross-keyboard shortcuts
320
-
321
- // disambiguate defaults to true unless explicitly set to false
322
- if (config.disambiguate !== false) {
323
- flags |= KITTY_FLAG_DISAMBIGUATE
324
- }
325
-
326
- // alternateKeys defaults to true unless explicitly set to false
327
- if (config.alternateKeys !== false) {
328
- flags |= KITTY_FLAG_ALTERNATE_KEYS
329
- }
330
-
331
- // Optional flags (default to false, only enabled when explicitly true)
332
- if (config.events === true) {
333
- flags |= KITTY_FLAG_EVENT_TYPES
334
- }
335
-
336
- if (config.allKeysAsEscapes === true) {
337
- flags |= KITTY_FLAG_ALL_KEYS_AS_ESCAPES
338
- }
339
-
340
- if (config.reportText === true) {
341
- flags |= KITTY_FLAG_REPORT_TEXT
342
- }
343
-
344
- return flags
345
- }
346
-
347
- export class MouseEvent {
348
- public readonly type: MouseEventType
349
- public readonly button: number
350
- public readonly x: number
351
- public readonly y: number
352
- public readonly source?: Renderable
353
- public readonly modifiers: {
354
- shift: boolean
355
- alt: boolean
356
- ctrl: boolean
357
- }
358
- public readonly scroll?: ScrollInfo
359
- public readonly target: Renderable | null
360
- public readonly isDragging?: boolean
361
- private _propagationStopped: boolean = false
362
- private _defaultPrevented: boolean = false
363
-
364
- public get propagationStopped(): boolean {
365
- return this._propagationStopped
366
- }
367
-
368
- public get defaultPrevented(): boolean {
369
- return this._defaultPrevented
370
- }
371
-
372
- constructor(target: Renderable | null, attributes: RawMouseEvent & { source?: Renderable; isDragging?: boolean }) {
373
- this.target = target
374
- this.type = attributes.type
375
- this.button = attributes.button
376
- this.x = attributes.x
377
- this.y = attributes.y
378
- this.modifiers = attributes.modifiers
379
- this.scroll = attributes.scroll
380
- this.source = attributes.source
381
- this.isDragging = attributes.isDragging
382
- }
383
-
384
- public stopPropagation(): void {
385
- this._propagationStopped = true
386
- }
387
-
388
- public preventDefault(): void {
389
- this._defaultPrevented = true
390
- }
391
- }
392
-
393
- export enum MouseButton {
394
- LEFT = 0,
395
- MIDDLE = 1,
396
- RIGHT = 2,
397
- WHEEL_UP = 4,
398
- WHEEL_DOWN = 5,
399
- }
400
-
401
- const rendererTracker = singleton("RendererTracker", () => {
402
- const renderers = new Set<CliRenderer>()
403
- return {
404
- addRenderer: (renderer: CliRenderer) => {
405
- renderers.add(renderer)
406
- },
407
- removeRenderer: (renderer: CliRenderer) => {
408
- renderers.delete(renderer)
409
- if (renderers.size === 0) {
410
- process.stdin.pause()
411
-
412
- if (hasSingleton("tree-sitter-client")) {
413
- getTreeSitterClient().destroy()
414
- destroySingleton("tree-sitter-client")
415
- }
416
- }
417
- },
418
- }
419
- })
420
-
421
- export async function createCliRenderer(config: CliRendererConfig = {}): Promise<CliRenderer> {
422
- if (process.argv.includes("--delay-start")) {
423
- await new Promise((resolve) => setTimeout(resolve, 5000))
424
- }
425
- const stdin = config.stdin || process.stdin
426
- const stdout = config.stdout || process.stdout
427
- const { screenMode, footerHeight } = resolveModes(config)
428
-
429
- const width = stdout.columns || 80
430
- const height = stdout.rows || 24
431
- const renderHeight = screenMode === "split-footer" ? footerHeight : height
432
-
433
- const ziglib = resolveRenderLib()
434
- const rendererPtr = ziglib.createRenderer(width, renderHeight, {
435
- remote: config.remote ?? false,
436
- testing: config.testing ?? false,
437
- })
438
- if (!rendererPtr) {
439
- throw new Error("Failed to create renderer")
440
- }
441
- if (config.useThread === undefined) {
442
- config.useThread = true
443
- }
444
-
445
- // Disable threading on linux because there currently is currently an issue
446
- // might be just a missing dependency for the build or something, but threads crash on linux
447
- if (process.platform === "linux") {
448
- config.useThread = false
449
- }
450
- ziglib.setUseThread(rendererPtr, config.useThread)
451
-
452
- const kittyConfig = config.useKittyKeyboard ?? {}
453
- const kittyFlags = buildKittyKeyboardFlags(kittyConfig)
454
-
455
- ziglib.setKittyKeyboardFlags(rendererPtr, kittyFlags)
456
-
457
- const renderer = new CliRenderer(ziglib, rendererPtr, stdin, stdout, width, height, config)
458
- if (!config.testing) {
459
- await renderer.setupTerminal()
460
- }
461
- return renderer
462
- }
463
-
464
- export enum CliRenderEvents {
465
- RESIZE = "resize",
466
- FOCUS = "focus",
467
- BLUR = "blur",
468
- FOCUSED_EDITOR = "focused_editor",
469
- THEME_MODE = "theme_mode",
470
- CAPABILITIES = "capabilities",
471
- SELECTION = "selection",
472
- DEBUG_OVERLAY_TOGGLE = "debugOverlay:toggle",
473
- DESTROY = "destroy",
474
- MEMORY_SNAPSHOT = "memory:snapshot",
475
- }
476
-
477
- export enum RendererControlState {
478
- IDLE = "idle",
479
- AUTO_STARTED = "auto_started",
480
- EXPLICIT_STARTED = "explicit_started",
481
- EXPLICIT_PAUSED = "explicit_paused",
482
- EXPLICIT_SUSPENDED = "explicit_suspended",
483
- EXPLICIT_STOPPED = "explicit_stopped",
484
- }
485
-
486
- export class CliRenderer extends EventEmitter implements RenderContext {
487
- private static animationFrameId = 0
488
- private lib: RenderLib
489
- public rendererPtr: Pointer
490
- public stdin: NodeJS.ReadStream
491
- private stdout: NodeJS.WriteStream
492
- private exitOnCtrlC: boolean
493
- private exitSignals: NodeJS.Signals[]
494
- private _exitListenersAdded: boolean = false
495
- private _isDestroyed: boolean = false
496
- private _destroyPending: boolean = false
497
- private _destroyFinalized: boolean = false
498
- private _destroyCleanupPrepared: boolean = false
499
- public nextRenderBuffer: OptimizedBuffer
500
- public currentRenderBuffer: OptimizedBuffer
501
- private _isRunning: boolean = false
502
- private _targetFps: number = 30
503
- private _maxFps: number = 60
504
- private automaticMemorySnapshot: boolean = false
505
- private memorySnapshotInterval: number
506
- private memorySnapshotTimer: TimerHandle | null = null
507
- private lastMemorySnapshot: { heapUsed: number; heapTotal: number; arrayBuffers: number } = {
508
- heapUsed: 0,
509
- heapTotal: 0,
510
- arrayBuffers: 0,
511
- }
512
- public readonly root: RootRenderable
513
- public width: number
514
- public height: number
515
- private _useThread: boolean = false
516
- private gatherStats: boolean = false
517
- private frameTimes: number[] = []
518
- private maxStatSamples: number = 300
519
- private postProcessFns: ((buffer: OptimizedBuffer, deltaTime: number) => void)[] = []
520
- private backgroundColor: RGBA = RGBA.fromInts(0, 0, 0, 0)
521
- private waitingForPixelResolution: boolean = false
522
- private readonly clock: Clock
523
-
524
- private rendering: boolean = false
525
- private renderingNative: boolean = false
526
- private renderTimeout: TimerHandle | null = null
527
- private lastTime: number = 0
528
- private frameCount: number = 0
529
- private lastFpsTime: number = 0
530
- private currentFps: number = 0
531
- private targetFrameTime: number = 1000 / this._targetFps
532
- private minTargetFrameTime: number = 1000 / this._maxFps
533
- private immediateRerenderRequested: boolean = false
534
- private updateScheduled: boolean = false
535
-
536
- private liveRequestCounter: number = 0
537
- private _controlState: RendererControlState = RendererControlState.IDLE
538
-
539
- private frameCallbacks: ((deltaTime: number) => Promise<void>)[] = []
540
- private renderStats: {
541
- frameCount: number
542
- fps: number
543
- renderTime?: number
544
- frameCallbackTime: number
545
- } = {
546
- frameCount: 0,
547
- fps: 0,
548
- renderTime: 0,
549
- frameCallbackTime: 0,
550
- }
551
- public debugOverlay = {
552
- enabled: env.OTUI_SHOW_STATS,
553
- corner: DebugOverlayCorner.bottomRight,
554
- }
555
-
556
- private _console: TerminalConsole
557
- private _resolution: PixelResolution | null = null
558
- private _keyHandler: InternalKeyHandler
559
- private stdinParser: StdinParser | null = null
560
- private readonly oscSubscribers = new Set<(sequence: string) => void>()
561
- private hasLoggedStdinParserError = false
562
-
563
- private animationRequest: Map<number, FrameRequestCallback> = new Map()
564
-
565
- private resizeTimeoutId: TimerHandle | null = null
566
- private capabilityTimeoutId: TimerHandle | null = null
567
- private resizeDebounceDelay: number = 100
568
-
569
- private enableMouseMovement: boolean = false
570
- private _useMouse: boolean = true
571
- private autoFocus: boolean = true
572
- private _screenMode: ScreenMode = "alternate-screen"
573
- private _footerHeight: number = DEFAULT_FOOTER_HEIGHT
574
- private _externalOutputMode: ExternalOutputMode = "passthrough"
575
- private _suspendedMouseEnabled: boolean = false
576
- private _previousControlState: RendererControlState = RendererControlState.IDLE
577
- private capturedRenderable?: Renderable
578
- private lastOverRenderableNum: number = 0
579
- private lastOverRenderable?: Renderable
580
-
581
- private currentSelection: Selection | null = null
582
- private selectionContainers: Renderable[] = []
583
- private clipboard: Clipboard
584
-
585
- private _splitHeight: number = 0
586
- private renderOffset: number = 0
587
-
588
- private _terminalWidth: number = 0
589
- private _terminalHeight: number = 0
590
- private _terminalIsSetup: boolean = false
591
-
592
- private realStdoutWrite: (chunk: any, encoding?: any, callback?: any) => boolean
593
- private captureCallback: () => void = () => {
594
- if (this._splitHeight > 0) {
595
- this.requestRender()
596
- }
597
- }
598
-
599
- private _useConsole: boolean = true
600
- private sigwinchHandler: () => void = (() => {
601
- const width = this.stdout.columns || 80
602
- const height = this.stdout.rows || 24
603
- this.handleResize(width, height)
604
- }).bind(this)
605
- private _capabilities: any | null = null
606
- private _latestPointer: { x: number; y: number } = { x: 0, y: 0 }
607
- private _hasPointer: boolean = false
608
- private _lastPointerModifiers: RawMouseEvent["modifiers"] = { shift: false, alt: false, ctrl: false }
609
- private _currentMousePointerStyle: MousePointerStyle | undefined = undefined
610
-
611
- private _currentFocusedRenderable: Renderable | null = null
612
- private lifecyclePasses: Set<Renderable> = new Set()
613
- private _openConsoleOnError: boolean = true
614
- private _paletteDetector: TerminalPaletteDetector | null = null
615
- private _cachedPalette: TerminalColors | null = null
616
- private _paletteDetectionPromise: Promise<TerminalColors> | null = null
617
- private _onDestroy?: () => void
618
- private _themeMode: ThemeMode | null = null
619
- private _terminalFocusState: boolean | null = null
620
-
621
- private sequenceHandlers: ((sequence: string) => boolean)[] = []
622
- private prependedInputHandlers: ((sequence: string) => boolean)[] = []
623
- private shouldRestoreModesOnNextFocus: boolean = false
624
-
625
- private idleResolvers: (() => void)[] = []
626
-
627
- private _debugInputs: Array<{ timestamp: string; sequence: string }> = []
628
- private _debugModeEnabled: boolean = env.OTUI_DEBUG
629
-
630
- private handleError: (error: Error) => void = ((error: Error) => {
631
- console.error(error)
632
-
633
- if (this._openConsoleOnError) {
634
- this.console.show()
635
- }
636
- }).bind(this)
637
-
638
- private dumpOutputCache(optionalMessage: string = ""): void {
639
- const cachedLogs = this.console.getCachedLogs()
640
- const capturedOutput = capture.claimOutput()
641
-
642
- if (capturedOutput.length > 0 || cachedLogs.length > 0) {
643
- this.realStdoutWrite.call(this.stdout, optionalMessage)
644
- }
645
-
646
- if (cachedLogs.length > 0) {
647
- this.realStdoutWrite.call(this.stdout, "Console cache:\n")
648
- this.realStdoutWrite.call(this.stdout, cachedLogs)
649
- }
650
-
651
- if (capturedOutput.length > 0) {
652
- this.realStdoutWrite.call(this.stdout, "\nCaptured output:\n")
653
- this.realStdoutWrite.call(this.stdout, capturedOutput + "\n")
654
- }
655
-
656
- this.realStdoutWrite.call(this.stdout, ANSI.reset)
657
- }
658
-
659
- private exitHandler: () => void = (() => {
660
- this.destroy()
661
- if (env.OTUI_DUMP_CAPTURES) {
662
- Bun.sleep(100).then(() => {
663
- this.dumpOutputCache("=== CAPTURED OUTPUT ===\n")
664
- })
665
- }
666
- }).bind(this)
667
-
668
- private warningHandler: (warning: any) => void = ((warning: any) => {
669
- console.warn(JSON.stringify(warning.message, null, 2))
670
- }).bind(this)
671
-
672
- public get controlState(): RendererControlState {
673
- return this._controlState
674
- }
675
-
676
- constructor(
677
- lib: RenderLib,
678
- rendererPtr: Pointer,
679
- stdin: NodeJS.ReadStream,
680
- stdout: NodeJS.WriteStream,
681
- width: number,
682
- height: number,
683
- config: CliRendererConfig = {},
684
- ) {
685
- super()
686
-
687
- rendererTracker.addRenderer(this)
688
-
689
- this.stdin = stdin
690
- this.stdout = stdout
691
- this.realStdoutWrite = stdout.write
692
- this.lib = lib
693
- this._terminalWidth = stdout.columns ?? width
694
- this._terminalHeight = stdout.rows ?? height
695
- this.width = width
696
- this.height = height
697
- this._useThread = config.useThread === undefined ? false : config.useThread
698
- const { screenMode, footerHeight, externalOutputMode } = resolveModes(config)
699
-
700
- this._footerHeight = footerHeight
701
- this._screenMode = screenMode
702
-
703
- this.rendererPtr = rendererPtr
704
-
705
- const forwardEnvKeys = config.forwardEnvKeys ?? [...DEFAULT_FORWARDED_ENV_KEYS]
706
- for (const key of forwardEnvKeys) {
707
- const value = process.env[key]
708
- if (value === undefined) continue
709
- this.lib.setTerminalEnvVar(this.rendererPtr, key, value)
710
- }
711
-
712
- this.exitOnCtrlC = config.exitOnCtrlC === undefined ? true : config.exitOnCtrlC
713
- this.exitSignals = config.exitSignals || [
714
- "SIGINT", // Ctrl+C
715
- "SIGTERM", // Termination signal
716
- "SIGQUIT", // Ctrl+\
717
- "SIGABRT", // Abort signal
718
- "SIGHUP", // Hangup (terminal closed)
719
- "SIGBREAK", // Ctrl+Break on Windows
720
- "SIGPIPE", // Broken pipe
721
- "SIGBUS", // Bus error
722
- ]
723
-
724
- this.clipboard = new Clipboard(this.lib, this.rendererPtr)
725
- this.resizeDebounceDelay = config.debounceDelay || 100
726
- this.targetFps = config.targetFps || 30
727
- this.maxFps = config.maxFps || 60
728
- this.clock = config.clock ?? new SystemClock()
729
- this.memorySnapshotInterval = config.memorySnapshotInterval ?? 0
730
- this.gatherStats = config.gatherStats || false
731
- this.maxStatSamples = config.maxStatSamples || 300
732
- this.enableMouseMovement = config.enableMouseMovement ?? true
733
- this._useMouse = config.useMouse ?? true
734
- this.autoFocus = config.autoFocus ?? true
735
- this.nextRenderBuffer = this.lib.getNextBuffer(this.rendererPtr)
736
- this.currentRenderBuffer = this.lib.getCurrentBuffer(this.rendererPtr)
737
- this.postProcessFns = config.postProcessFns || []
738
- this.prependedInputHandlers = config.prependInputHandlers || []
739
-
740
- this.root = new RootRenderable(this)
741
-
742
- if (this.memorySnapshotInterval > 0) {
743
- this.startMemorySnapshotTimer()
744
- }
745
-
746
- // Handle terminal resize
747
- process.on("SIGWINCH", this.sigwinchHandler)
748
-
749
- process.on("warning", this.warningHandler)
750
-
751
- process.on("uncaughtException", this.handleError)
752
- process.on("unhandledRejection", this.handleError)
753
- process.on("beforeExit", this.exitHandler)
754
-
755
- const kittyConfig = config.useKittyKeyboard ?? {}
756
- const useKittyForParsing = kittyConfig !== null
757
- this._keyHandler = new InternalKeyHandler()
758
- this._keyHandler.on("keypress", (event) => {
759
- if (this.exitOnCtrlC && event.name === "c" && event.ctrl) {
760
- process.nextTick(() => {
761
- this.destroy()
762
- })
763
- return
764
- }
765
- })
766
-
767
- this.addExitListeners()
768
-
769
- const stdinParserMaxBufferBytes = config.stdinParserMaxBufferBytes ?? DEFAULT_STDIN_PARSER_MAX_BUFFER_BYTES
770
- this.stdinParser = new StdinParser({
771
- timeoutMs: 20,
772
- maxPendingBytes: stdinParserMaxBufferBytes,
773
- armTimeouts: true,
774
- onTimeoutFlush: () => {
775
- this.drainStdinParser()
776
- },
777
- useKittyKeyboard: useKittyForParsing,
778
- protocolContext: {
779
- kittyKeyboardEnabled: useKittyForParsing,
780
- privateCapabilityRepliesActive: false,
781
- pixelResolutionQueryActive: false,
782
- explicitWidthCprActive: false,
783
- },
784
- clock: this.clock,
785
- })
786
-
787
- this._console = new TerminalConsole(this, {
788
- ...(config.consoleOptions ?? {}),
789
- clock: this.clock,
790
- })
791
- this.consoleMode = config.consoleMode ?? "console-overlay"
792
- this.applyScreenMode(screenMode, false, false)
793
- this.externalOutputMode = externalOutputMode
794
- this._openConsoleOnError = config.openConsoleOnError ?? process.env.NODE_ENV !== "production"
795
- this._onDestroy = config.onDestroy
796
-
797
- global.requestAnimationFrame = (callback: FrameRequestCallback) => {
798
- const id = CliRenderer.animationFrameId++
799
- this.animationRequest.set(id, callback)
800
- this.requestLive()
801
- return id
802
- }
803
- global.cancelAnimationFrame = (handle: number) => {
804
- this.animationRequest.delete(handle)
805
- }
806
-
807
- const window = global.window
808
- if (!window) {
809
- global.window = {} as Window & typeof globalThis
810
- }
811
- global.window.requestAnimationFrame = requestAnimationFrame
812
-
813
- // Prevents output from being written to the terminal, useful for debugging
814
- if (env.OTUI_NO_NATIVE_RENDER) {
815
- this.renderNative = () => {
816
- if (this._splitHeight > 0) {
817
- this.flushStdoutCache(this._splitHeight)
818
- }
819
- }
820
- }
821
-
822
- this.setupInput()
823
- }
824
-
825
- private addExitListeners(): void {
826
- if (this._exitListenersAdded || this.exitSignals.length === 0) return
827
-
828
- this.exitSignals.forEach((signal) => {
829
- process.addListener(signal, this.exitHandler)
830
- })
831
-
832
- this._exitListenersAdded = true
833
- }
834
-
835
- private removeExitListeners(): void {
836
- if (!this._exitListenersAdded || this.exitSignals.length === 0) return
837
-
838
- this.exitSignals.forEach((signal) => {
839
- process.removeListener(signal, this.exitHandler)
840
- })
841
-
842
- this._exitListenersAdded = false
843
- }
844
-
845
- public get isDestroyed(): boolean {
846
- return this._isDestroyed
847
- }
848
-
849
- public registerLifecyclePass(renderable: Renderable) {
850
- this.lifecyclePasses.add(renderable)
851
- }
852
-
853
- public unregisterLifecyclePass(renderable: Renderable) {
854
- this.lifecyclePasses.delete(renderable)
855
- }
856
-
857
- public getLifecyclePasses() {
858
- return this.lifecyclePasses
859
- }
860
-
861
- public get currentFocusedRenderable(): Renderable | null {
862
- return this._currentFocusedRenderable
863
- }
864
-
865
- public get currentFocusedEditor(): EditBufferRenderable | null {
866
- if (!this._currentFocusedRenderable) return null
867
- if (!isEditBufferRenderable(this._currentFocusedRenderable)) return null
868
- return this._currentFocusedRenderable
869
- }
870
-
871
- private normalizeClockTime(now: number, fallback: number): number {
872
- if (Number.isFinite(now)) {
873
- return now
874
- }
875
-
876
- return Number.isFinite(fallback) ? fallback : 0
877
- }
878
-
879
- private getElapsedMs(now: number, then: number): number {
880
- if (!Number.isFinite(now) || !Number.isFinite(then)) {
881
- return 0
882
- }
883
-
884
- return Math.max(now - then, 0)
885
- }
886
-
887
- public focusRenderable(renderable: Renderable) {
888
- if (this._currentFocusedRenderable === renderable) return
889
-
890
- const prev = this.currentFocusedEditor
891
-
892
- if (this._currentFocusedRenderable) {
893
- this._currentFocusedRenderable.blur()
894
- }
895
-
896
- this._currentFocusedRenderable = renderable
897
-
898
- const next = this.currentFocusedEditor
899
- if (prev !== next) {
900
- this.emit(CliRenderEvents.FOCUSED_EDITOR, next, prev)
901
- }
902
- }
903
-
904
- private setCapturedRenderable(renderable: Renderable | undefined): void {
905
- if (this.capturedRenderable === renderable) {
906
- return
907
- }
908
- this.capturedRenderable = renderable
909
- }
910
-
911
- public addToHitGrid(x: number, y: number, width: number, height: number, id: number) {
912
- if (id !== this.capturedRenderable?.num) {
913
- this.lib.addToHitGrid(this.rendererPtr, x, y, width, height, id)
914
- }
915
- }
916
-
917
- public pushHitGridScissorRect(x: number, y: number, width: number, height: number): void {
918
- this.lib.hitGridPushScissorRect(this.rendererPtr, x, y, width, height)
919
- }
920
-
921
- public popHitGridScissorRect(): void {
922
- this.lib.hitGridPopScissorRect(this.rendererPtr)
923
- }
924
-
925
- public clearHitGridScissorRects(): void {
926
- this.lib.hitGridClearScissorRects(this.rendererPtr)
927
- }
928
-
929
- public get widthMethod(): WidthMethod {
930
- const caps = this.capabilities
931
- return caps?.unicode === "wcwidth" ? "wcwidth" : "unicode"
932
- }
933
-
934
- private writeOut(chunk: any, encoding?: any, callback?: any): boolean {
935
- if (this.rendererPtr && this._useThread) {
936
- const data = typeof chunk === "string" ? chunk : (chunk?.toString() ?? "")
937
- this.lib.writeOut(this.rendererPtr, data)
938
- if (typeof callback === "function") {
939
- process.nextTick(callback)
940
- }
941
- return true
942
- }
943
-
944
- return this.realStdoutWrite.call(this.stdout, chunk, encoding, callback)
945
- }
946
-
947
- public requestRender() {
948
- if (this._controlState === RendererControlState.EXPLICIT_SUSPENDED) {
949
- return
950
- }
951
-
952
- if (this._isRunning) {
953
- return
954
- }
955
-
956
- // NOTE: Using a frame callback that causes a re-render while already rendering
957
- // leads to a continuous loop of renders.
958
- if (this.rendering) {
959
- this.immediateRerenderRequested = true
960
- return
961
- }
962
-
963
- if (!this.updateScheduled && !this.renderTimeout) {
964
- this.updateScheduled = true
965
- const now = this.normalizeClockTime(this.clock.now(), this.lastTime)
966
- const elapsed = this.getElapsedMs(now, this.lastTime)
967
- const delay = Math.max(this.minTargetFrameTime - elapsed, 0)
968
-
969
- if (delay === 0) {
970
- process.nextTick(() => this.activateFrame())
971
- return
972
- }
973
-
974
- this.clock.setTimeout(() => this.activateFrame(), delay)
975
- }
976
- }
977
-
978
- private async activateFrame() {
979
- if (!this.updateScheduled) {
980
- this.resolveIdleIfNeeded()
981
- return
982
- }
983
-
984
- try {
985
- await this.loop()
986
- } finally {
987
- this.updateScheduled = false
988
- this.resolveIdleIfNeeded()
989
- }
990
- }
991
-
992
- public get consoleMode(): ConsoleMode {
993
- return this._useConsole ? "console-overlay" : "disabled"
994
- }
995
-
996
- public set consoleMode(mode: ConsoleMode) {
997
- this._useConsole = mode === "console-overlay"
998
- if (this._useConsole) {
999
- this.console.activate()
1000
- } else {
1001
- this.console.deactivate()
1002
- }
1003
- }
1004
-
1005
- public get isRunning(): boolean {
1006
- return this._isRunning
1007
- }
1008
-
1009
- private isIdleNow(): boolean {
1010
- return (
1011
- !this._isRunning &&
1012
- !this.rendering &&
1013
- !this.renderTimeout &&
1014
- !this.updateScheduled &&
1015
- !this.immediateRerenderRequested
1016
- )
1017
- }
1018
-
1019
- private resolveIdleIfNeeded(): void {
1020
- if (!this.isIdleNow()) return
1021
- const resolvers = this.idleResolvers.splice(0)
1022
- for (const resolve of resolvers) {
1023
- resolve()
1024
- }
1025
- }
1026
-
1027
- public idle(): Promise<void> {
1028
- if (this._isDestroyed) return Promise.resolve()
1029
- if (this.isIdleNow()) return Promise.resolve()
1030
- return new Promise<void>((resolve) => {
1031
- this.idleResolvers.push(resolve)
1032
- })
1033
- }
1034
-
1035
- public get resolution(): PixelResolution | null {
1036
- return this._resolution
1037
- }
1038
-
1039
- public get console(): TerminalConsole {
1040
- return this._console
1041
- }
1042
-
1043
- public get keyInput(): KeyHandler {
1044
- return this._keyHandler
1045
- }
1046
-
1047
- public get _internalKeyInput(): InternalKeyHandler {
1048
- return this._keyHandler
1049
- }
1050
-
1051
- public get terminalWidth(): number {
1052
- return this._terminalWidth
1053
- }
1054
-
1055
- public get terminalHeight(): number {
1056
- return this._terminalHeight
1057
- }
1058
-
1059
- public get useThread(): boolean {
1060
- return this._useThread
1061
- }
1062
-
1063
- public get targetFps(): number {
1064
- return this._targetFps
1065
- }
1066
-
1067
- public set targetFps(targetFps: number) {
1068
- this._targetFps = targetFps
1069
- this.targetFrameTime = 1000 / this._targetFps
1070
- }
1071
-
1072
- public get maxFps(): number {
1073
- return this._maxFps
1074
- }
1075
-
1076
- public set maxFps(maxFps: number) {
1077
- this._maxFps = maxFps
1078
- this.minTargetFrameTime = 1000 / this._maxFps
1079
- }
1080
-
1081
- public get useMouse(): boolean {
1082
- return this._useMouse
1083
- }
1084
-
1085
- public set useMouse(useMouse: boolean) {
1086
- if (this._useMouse === useMouse) return // No change needed
1087
-
1088
- this._useMouse = useMouse
1089
-
1090
- if (useMouse) {
1091
- this.enableMouse()
1092
- } else {
1093
- this.disableMouse()
1094
- }
1095
- }
1096
-
1097
- public get screenMode(): ScreenMode {
1098
- return this._screenMode
1099
- }
1100
-
1101
- public set screenMode(mode: ScreenMode) {
1102
- if (this.externalOutputMode === "capture-stdout" && mode !== "split-footer") {
1103
- throw new Error('externalOutputMode "capture-stdout" requires screenMode "split-footer"')
1104
- }
1105
-
1106
- this.applyScreenMode(mode)
1107
- }
1108
-
1109
- public get footerHeight(): number {
1110
- return this._footerHeight
1111
- }
1112
-
1113
- public set footerHeight(footerHeight: number) {
1114
- const normalizedFooterHeight = normalizeFooterHeight(footerHeight)
1115
- if (normalizedFooterHeight === this._footerHeight) {
1116
- return
1117
- }
1118
-
1119
- this._footerHeight = normalizedFooterHeight
1120
- if (this.screenMode === "split-footer") {
1121
- this.applyScreenMode("split-footer")
1122
- }
1123
- }
1124
-
1125
- public get externalOutputMode(): ExternalOutputMode {
1126
- return this._externalOutputMode
1127
- }
1128
-
1129
- public set externalOutputMode(mode: ExternalOutputMode) {
1130
- if (mode === "capture-stdout" && this.screenMode !== "split-footer") {
1131
- throw new Error('externalOutputMode "capture-stdout" requires screenMode "split-footer"')
1132
- }
1133
-
1134
- this._externalOutputMode = mode
1135
- this.stdout.write = mode === "capture-stdout" ? this.interceptStdoutWrite : this.realStdoutWrite
1136
- }
1137
-
1138
- public get liveRequestCount(): number {
1139
- return this.liveRequestCounter
1140
- }
1141
-
1142
- public get currentControlState(): string {
1143
- return this._controlState
1144
- }
1145
-
1146
- public get capabilities(): any | null {
1147
- return this._capabilities
1148
- }
1149
-
1150
- public get themeMode(): ThemeMode | null {
1151
- return this._themeMode
1152
- }
1153
-
1154
- public getDebugInputs(): Array<{ timestamp: string; sequence: string }> {
1155
- return [...this._debugInputs]
1156
- }
1157
-
1158
- public get useKittyKeyboard(): boolean {
1159
- return this.lib.getKittyKeyboardFlags(this.rendererPtr) > 0
1160
- }
1161
-
1162
- public set useKittyKeyboard(use: boolean) {
1163
- const flags = use ? KITTY_FLAG_DISAMBIGUATE | KITTY_FLAG_ALTERNATE_KEYS : 0
1164
- this.lib.setKittyKeyboardFlags(this.rendererPtr, flags)
1165
- }
1166
-
1167
- private interceptStdoutWrite = (chunk: any, encoding?: any, callback?: any): boolean => {
1168
- const text = chunk.toString()
1169
-
1170
- capture.write("stdout", text)
1171
- if (this._splitHeight > 0) {
1172
- this.requestRender()
1173
- }
1174
-
1175
- if (typeof callback === "function") {
1176
- process.nextTick(callback)
1177
- }
1178
-
1179
- return true
1180
- }
1181
-
1182
- private applyScreenMode(screenMode: ScreenMode, emitResize: boolean = true, requestRender: boolean = true): void {
1183
- const prevScreenMode = this._screenMode
1184
- const prevSplitHeight = this._splitHeight
1185
- const nextSplitHeight = screenMode === "split-footer" ? this._footerHeight : 0
1186
-
1187
- if (prevScreenMode === screenMode && prevSplitHeight === nextSplitHeight) {
1188
- return
1189
- }
1190
-
1191
- const prevUseAlternateScreen = prevScreenMode === "alternate-screen"
1192
- const nextUseAlternateScreen = screenMode === "alternate-screen"
1193
- const terminalScreenModeChanged = this._terminalIsSetup && prevUseAlternateScreen !== nextUseAlternateScreen
1194
- const leavingSplitFooter = prevSplitHeight > 0 && nextSplitHeight === 0
1195
-
1196
- if (this._terminalIsSetup && leavingSplitFooter) {
1197
- this.flushStdoutCache(this._terminalHeight, true)
1198
- }
1199
-
1200
- if (this._terminalIsSetup && !terminalScreenModeChanged) {
1201
- if (prevSplitHeight === 0 && nextSplitHeight > 0) {
1202
- const freedLines = this._terminalHeight - nextSplitHeight
1203
- const scrollDown = ANSI.scrollDown(freedLines)
1204
- this.writeOut(scrollDown)
1205
- } else if (prevSplitHeight > nextSplitHeight && nextSplitHeight > 0) {
1206
- const freedLines = prevSplitHeight - nextSplitHeight
1207
- const scrollDown = ANSI.scrollDown(freedLines)
1208
- this.writeOut(scrollDown)
1209
- } else if (prevSplitHeight < nextSplitHeight && prevSplitHeight > 0) {
1210
- const additionalLines = nextSplitHeight - prevSplitHeight
1211
- const scrollUp = ANSI.scrollUp(additionalLines)
1212
- this.writeOut(scrollUp)
1213
- }
1214
- }
1215
-
1216
- if (prevSplitHeight === 0 && nextSplitHeight > 0) {
1217
- capture.on("write", this.captureCallback)
1218
- } else if (prevSplitHeight > 0 && nextSplitHeight === 0) {
1219
- capture.off("write", this.captureCallback)
1220
- }
1221
-
1222
- this._screenMode = screenMode
1223
- this._splitHeight = nextSplitHeight
1224
- this.renderOffset = nextSplitHeight > 0 ? this._terminalHeight - nextSplitHeight : 0
1225
- this.width = this._terminalWidth
1226
- this.height = nextSplitHeight > 0 ? nextSplitHeight : this._terminalHeight
1227
-
1228
- this.lib.setRenderOffset(this.rendererPtr, this.renderOffset)
1229
- this.lib.resizeRenderer(this.rendererPtr, this.width, this.height)
1230
- this.nextRenderBuffer = this.lib.getNextBuffer(this.rendererPtr)
1231
- this.currentRenderBuffer = this.lib.getCurrentBuffer(this.rendererPtr)
1232
-
1233
- this._console.resize(this.width, this.height)
1234
- this.root.resize(this.width, this.height)
1235
-
1236
- if (terminalScreenModeChanged) {
1237
- this.lib.suspendRenderer(this.rendererPtr)
1238
- this.lib.setupTerminal(this.rendererPtr, nextUseAlternateScreen)
1239
-
1240
- if (this._useMouse) {
1241
- this.enableMouse()
1242
- }
1243
- }
1244
-
1245
- if (emitResize) {
1246
- this.emit(CliRenderEvents.RESIZE, this.width, this.height)
1247
- }
1248
-
1249
- if (requestRender) {
1250
- this.requestRender()
1251
- }
1252
- }
1253
-
1254
- // TODO: Move this to native
1255
- private flushStdoutCache(space: number, force: boolean = false): boolean {
1256
- if (capture.size === 0 && !force) return false
1257
-
1258
- const output = capture.claimOutput()
1259
-
1260
- const rendererStartLine = this._terminalHeight - this._splitHeight
1261
- const flush = ANSI.moveCursorAndClear(rendererStartLine, 1)
1262
-
1263
- const outputLine = this._terminalHeight - this._splitHeight
1264
- const move = ANSI.moveCursor(outputLine, 1)
1265
-
1266
- let clear = ""
1267
- if (space > 0) {
1268
- const backgroundColor = this.backgroundColor.toInts()
1269
- const newlines = " ".repeat(this.width) + "\n".repeat(space)
1270
- // Check if background is transparent (alpha = 0)
1271
- if (backgroundColor[3] === 0) {
1272
- clear = newlines
1273
- } else {
1274
- clear =
1275
- ANSI.setRgbBackground(backgroundColor[0], backgroundColor[1], backgroundColor[2]) +
1276
- newlines +
1277
- ANSI.resetBackground
1278
- }
1279
- }
1280
-
1281
- this.writeOut(flush + move + output + clear)
1282
-
1283
- return true
1284
- }
1285
-
1286
- private enableMouse(): void {
1287
- this._useMouse = true
1288
- this.lib.enableMouse(this.rendererPtr, this.enableMouseMovement)
1289
- }
1290
-
1291
- private disableMouse(): void {
1292
- this._useMouse = false
1293
- this.setCapturedRenderable(undefined)
1294
- this.stdinParser?.resetMouseState()
1295
- this.lib.disableMouse(this.rendererPtr)
1296
- }
1297
-
1298
- public enableKittyKeyboard(flags: number = 0b00011): void {
1299
- this.lib.enableKittyKeyboard(this.rendererPtr, flags)
1300
- this.updateStdinParserProtocolContext({ kittyKeyboardEnabled: true })
1301
- }
1302
-
1303
- public disableKittyKeyboard(): void {
1304
- this.lib.disableKittyKeyboard(this.rendererPtr)
1305
- this.updateStdinParserProtocolContext({ kittyKeyboardEnabled: false }, true)
1306
- }
1307
-
1308
- public set useThread(useThread: boolean) {
1309
- this._useThread = useThread
1310
- this.lib.setUseThread(this.rendererPtr, useThread)
1311
- }
1312
-
1313
- // TODO: All input management may move to native when zig finally has async io support again,
1314
- // without rolling a full event loop
1315
- public async setupTerminal(): Promise<void> {
1316
- if (this._terminalIsSetup) return
1317
- this._terminalIsSetup = true
1318
-
1319
- this.updateStdinParserProtocolContext({
1320
- privateCapabilityRepliesActive: true,
1321
- explicitWidthCprActive: true,
1322
- })
1323
- this.lib.setupTerminal(this.rendererPtr, this._screenMode === "alternate-screen")
1324
- this._capabilities = this.lib.getTerminalCapabilities(this.rendererPtr)
1325
-
1326
- if (this.debugOverlay.enabled) {
1327
- this.lib.setDebugOverlay(this.rendererPtr, true, this.debugOverlay.corner)
1328
- if (!this.memorySnapshotInterval) {
1329
- this.memorySnapshotInterval = 3000
1330
- this.startMemorySnapshotTimer()
1331
- this.automaticMemorySnapshot = true
1332
- }
1333
- }
1334
-
1335
- this.capabilityTimeoutId = this.clock.setTimeout(() => {
1336
- this.capabilityTimeoutId = null
1337
- this.removeInputHandler(this.capabilityHandler)
1338
- this.updateStdinParserProtocolContext(
1339
- {
1340
- privateCapabilityRepliesActive: false,
1341
- explicitWidthCprActive: false,
1342
- },
1343
- true,
1344
- )
1345
- }, 5000)
1346
-
1347
- if (this._useMouse) {
1348
- this.enableMouse()
1349
- }
1350
-
1351
- this.queryPixelResolution()
1352
- }
1353
-
1354
- private stdinListener: (chunk: Buffer | string) => void = ((chunk: Buffer | string) => {
1355
- const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
1356
- if (!this.stdinParser) return
1357
-
1358
- try {
1359
- this.stdinParser.push(data)
1360
- this.drainStdinParser()
1361
- } catch (error) {
1362
- this.handleStdinParserFailure(error)
1363
- }
1364
- }).bind(this)
1365
-
1366
- public addInputHandler(handler: (sequence: string) => boolean): void {
1367
- this.sequenceHandlers.push(handler)
1368
- }
1369
-
1370
- public prependInputHandler(handler: (sequence: string) => boolean): void {
1371
- this.sequenceHandlers.unshift(handler)
1372
- }
1373
-
1374
- public removeInputHandler(handler: (sequence: string) => boolean): void {
1375
- this.sequenceHandlers = this.sequenceHandlers.filter((candidate) => candidate !== handler)
1376
- }
1377
-
1378
- private updateStdinParserProtocolContext(patch: Partial<StdinParserProtocolContext>, drain = false): void {
1379
- if (!this.stdinParser) return
1380
- this.stdinParser.updateProtocolContext(patch)
1381
- if (drain) this.drainStdinParser()
1382
- }
1383
-
1384
- public subscribeOsc(handler: (sequence: string) => void): () => void {
1385
- this.oscSubscribers.add(handler)
1386
- return () => {
1387
- this.oscSubscribers.delete(handler)
1388
- }
1389
- }
1390
-
1391
- private capabilityHandler: (sequence: string) => boolean = ((sequence: string) => {
1392
- if (isCapabilityResponse(sequence)) {
1393
- this.lib.processCapabilityResponse(this.rendererPtr, sequence)
1394
- this._capabilities = this.lib.getTerminalCapabilities(this.rendererPtr)
1395
- this.emit(CliRenderEvents.CAPABILITIES, this._capabilities)
1396
- return true
1397
- }
1398
- return false
1399
- }).bind(this)
1400
-
1401
- private focusHandler: (sequence: string) => boolean = ((sequence: string) => {
1402
- if (sequence === "\x1b[I") {
1403
- // When the terminal regains focus, some terminal emulators (notably
1404
- // Windows Terminal / ConPTY) may have stripped DEC private modes like
1405
- // mouse tracking, bracketed paste, and focus tracking itself while the
1406
- // window was unfocused.
1407
- if (this.shouldRestoreModesOnNextFocus) {
1408
- this.lib.restoreTerminalModes(this.rendererPtr)
1409
- this.shouldRestoreModesOnNextFocus = false
1410
- }
1411
- if (this._terminalFocusState !== true) {
1412
- this._terminalFocusState = true
1413
- this.emit(CliRenderEvents.FOCUS)
1414
- }
1415
- return true
1416
- }
1417
- if (sequence === "\x1b[O") {
1418
- this.shouldRestoreModesOnNextFocus = true
1419
- if (this._terminalFocusState !== false) {
1420
- this._terminalFocusState = false
1421
- this.emit(CliRenderEvents.BLUR)
1422
- }
1423
- return true
1424
- }
1425
- return false
1426
- }).bind(this)
1427
-
1428
- private themeModeHandler: (sequence: string) => boolean = ((sequence: string) => {
1429
- if (sequence === "\x1b[?997;1n") {
1430
- if (this._themeMode !== "dark") {
1431
- this._themeMode = "dark"
1432
- this.emit(CliRenderEvents.THEME_MODE, "dark")
1433
- }
1434
- return true
1435
- }
1436
- if (sequence === "\x1b[?997;2n") {
1437
- if (this._themeMode !== "light") {
1438
- this._themeMode = "light"
1439
- this.emit(CliRenderEvents.THEME_MODE, "light")
1440
- }
1441
- return true
1442
- }
1443
- return false
1444
- }).bind(this)
1445
-
1446
- private dispatchSequenceHandlers(sequence: string): boolean {
1447
- if (this._debugModeEnabled) {
1448
- this._debugInputs.push({
1449
- timestamp: new Date().toISOString(),
1450
- sequence,
1451
- })
1452
- }
1453
-
1454
- for (const handler of this.sequenceHandlers) {
1455
- if (handler(sequence)) {
1456
- return true
1457
- }
1458
- }
1459
-
1460
- return false
1461
- }
1462
-
1463
- private drainStdinParser(): void {
1464
- if (!this.stdinParser) return
1465
-
1466
- this.stdinParser.drain((event) => {
1467
- this.handleStdinEvent(event)
1468
- })
1469
- }
1470
-
1471
- private handleStdinEvent(event: StdinEvent): void {
1472
- switch (event.type) {
1473
- case "key":
1474
- if (this.dispatchSequenceHandlers(event.raw)) {
1475
- return
1476
- }
1477
-
1478
- this._keyHandler.processParsedKey(event.key)
1479
- return
1480
- case "mouse":
1481
- if (this._useMouse && this.processSingleMouseEvent(event.event)) {
1482
- return
1483
- }
1484
-
1485
- this.dispatchSequenceHandlers(event.raw)
1486
- return
1487
- case "paste":
1488
- this._keyHandler.processPaste(event.bytes, event.metadata)
1489
- return
1490
- case "response":
1491
- if (event.protocol === "osc") {
1492
- for (const subscriber of this.oscSubscribers) {
1493
- subscriber(event.sequence)
1494
- }
1495
- }
1496
-
1497
- this.dispatchSequenceHandlers(event.sequence)
1498
- return
1499
- }
1500
- }
1501
-
1502
- private handleStdinParserFailure(error: unknown): void {
1503
- if (!this.hasLoggedStdinParserError) {
1504
- this.hasLoggedStdinParserError = true
1505
- if (process.env.NODE_ENV !== "test") {
1506
- console.error("[stdin-parser-error] parser failure, resetting parser", error)
1507
- }
1508
- }
1509
-
1510
- try {
1511
- this.stdinParser?.reset()
1512
- } catch (resetError) {
1513
- console.error("stdin parser reset failed after parser error", resetError)
1514
- }
1515
- }
1516
-
1517
- private setupInput(): void {
1518
- for (const handler of this.prependedInputHandlers) {
1519
- this.addInputHandler(handler)
1520
- }
1521
-
1522
- this.addInputHandler((sequence: string) => {
1523
- if (isPixelResolutionResponse(sequence) && this.waitingForPixelResolution) {
1524
- const resolution = parsePixelResolution(sequence)
1525
- if (resolution) {
1526
- this._resolution = resolution
1527
- }
1528
- this.waitingForPixelResolution = false
1529
- this.updateStdinParserProtocolContext({ pixelResolutionQueryActive: false }, true)
1530
- return true
1531
- }
1532
- return false
1533
- })
1534
- this.addInputHandler(this.capabilityHandler)
1535
- this.addInputHandler(this.focusHandler)
1536
- this.addInputHandler(this.themeModeHandler)
1537
-
1538
- if (this.stdin.setRawMode) {
1539
- this.stdin.setRawMode(true)
1540
- }
1541
-
1542
- this.stdin.on("data", this.stdinListener)
1543
- this.stdin.resume()
1544
- }
1545
-
1546
- private dispatchMouseEvent(
1547
- target: Renderable,
1548
- attributes: RawMouseEvent & { source?: Renderable; isDragging?: boolean },
1549
- ): MouseEvent {
1550
- const event = new MouseEvent(target, attributes)
1551
- target.processMouseEvent(event)
1552
-
1553
- if (this.autoFocus && event.type === "down" && event.button === MouseButton.LEFT && !event.defaultPrevented) {
1554
- let current: Renderable | null = target
1555
- while (current) {
1556
- if (current.focusable) {
1557
- current.focus()
1558
- break
1559
- }
1560
- current = current.parent
1561
- }
1562
- }
1563
-
1564
- return event
1565
- }
1566
-
1567
- private processSingleMouseEvent(mouseEvent: RawMouseEvent): boolean {
1568
- if (this._splitHeight > 0) {
1569
- if (mouseEvent.y < this.renderOffset) {
1570
- return false
1571
- }
1572
- mouseEvent.y -= this.renderOffset
1573
- }
1574
-
1575
- this._latestPointer.x = mouseEvent.x
1576
- this._latestPointer.y = mouseEvent.y
1577
- this._hasPointer = true
1578
- this._lastPointerModifiers = mouseEvent.modifiers
1579
-
1580
- if (this._console.visible) {
1581
- const consoleBounds = this._console.bounds
1582
- if (
1583
- mouseEvent.x >= consoleBounds.x &&
1584
- mouseEvent.x < consoleBounds.x + consoleBounds.width &&
1585
- mouseEvent.y >= consoleBounds.y &&
1586
- mouseEvent.y < consoleBounds.y + consoleBounds.height
1587
- ) {
1588
- const event = new MouseEvent(null, mouseEvent)
1589
- const handled = this._console.handleMouse(event)
1590
- if (handled) return true
1591
- }
1592
- }
1593
-
1594
- if (mouseEvent.type === "scroll") {
1595
- const maybeRenderableId = this.hitTest(mouseEvent.x, mouseEvent.y)
1596
- const maybeRenderable = Renderable.renderablesByNumber.get(maybeRenderableId)
1597
- const fallbackTarget =
1598
- this._currentFocusedRenderable &&
1599
- !this._currentFocusedRenderable.isDestroyed &&
1600
- this._currentFocusedRenderable.focused
1601
- ? this._currentFocusedRenderable
1602
- : null
1603
- const scrollTarget = maybeRenderable ?? fallbackTarget
1604
-
1605
- if (scrollTarget) {
1606
- const event = new MouseEvent(scrollTarget, mouseEvent)
1607
- scrollTarget.processMouseEvent(event)
1608
- }
1609
- return true
1610
- }
1611
-
1612
- const maybeRenderableId = this.hitTest(mouseEvent.x, mouseEvent.y)
1613
- const sameElement = maybeRenderableId === this.lastOverRenderableNum
1614
- this.lastOverRenderableNum = maybeRenderableId
1615
- const maybeRenderable = Renderable.renderablesByNumber.get(maybeRenderableId)
1616
-
1617
- if (
1618
- mouseEvent.type === "down" &&
1619
- mouseEvent.button === MouseButton.LEFT &&
1620
- !this.currentSelection?.isDragging &&
1621
- !mouseEvent.modifiers.ctrl
1622
- ) {
1623
- const canStartSelection = Boolean(
1624
- maybeRenderable &&
1625
- maybeRenderable.selectable &&
1626
- !maybeRenderable.isDestroyed &&
1627
- maybeRenderable.shouldStartSelection(mouseEvent.x, mouseEvent.y),
1628
- )
1629
-
1630
- if (canStartSelection && maybeRenderable) {
1631
- this.startSelection(maybeRenderable, mouseEvent.x, mouseEvent.y)
1632
- this.dispatchMouseEvent(maybeRenderable, mouseEvent)
1633
- return true
1634
- }
1635
- }
1636
-
1637
- if (mouseEvent.type === "drag" && this.currentSelection?.isDragging) {
1638
- this.updateSelection(maybeRenderable, mouseEvent.x, mouseEvent.y)
1639
-
1640
- if (maybeRenderable) {
1641
- const event = new MouseEvent(maybeRenderable, { ...mouseEvent, isDragging: true })
1642
- maybeRenderable.processMouseEvent(event)
1643
- }
1644
-
1645
- return true
1646
- }
1647
-
1648
- if (mouseEvent.type === "up" && this.currentSelection?.isDragging) {
1649
- if (maybeRenderable) {
1650
- const event = new MouseEvent(maybeRenderable, { ...mouseEvent, isDragging: true })
1651
- maybeRenderable.processMouseEvent(event)
1652
- }
1653
-
1654
- this.finishSelection()
1655
- return true
1656
- }
1657
-
1658
- if (mouseEvent.type === "down" && mouseEvent.button === MouseButton.LEFT && this.currentSelection) {
1659
- if (mouseEvent.modifiers.ctrl) {
1660
- this.currentSelection.isDragging = true
1661
- this.updateSelection(maybeRenderable, mouseEvent.x, mouseEvent.y)
1662
- return true
1663
- }
1664
- }
1665
-
1666
- if (!sameElement && (mouseEvent.type === "drag" || mouseEvent.type === "move")) {
1667
- if (
1668
- this.lastOverRenderable &&
1669
- this.lastOverRenderable !== this.capturedRenderable &&
1670
- !this.lastOverRenderable.isDestroyed
1671
- ) {
1672
- const event = new MouseEvent(this.lastOverRenderable, { ...mouseEvent, type: "out" })
1673
- this.lastOverRenderable.processMouseEvent(event)
1674
- }
1675
- this.lastOverRenderable = maybeRenderable
1676
- if (maybeRenderable) {
1677
- const event = new MouseEvent(maybeRenderable, {
1678
- ...mouseEvent,
1679
- type: "over",
1680
- source: this.capturedRenderable,
1681
- })
1682
- maybeRenderable.processMouseEvent(event)
1683
- }
1684
- }
1685
-
1686
- if (this.capturedRenderable && mouseEvent.type !== "up") {
1687
- const event = new MouseEvent(this.capturedRenderable, mouseEvent)
1688
- this.capturedRenderable.processMouseEvent(event)
1689
- return true
1690
- }
1691
-
1692
- if (this.capturedRenderable && mouseEvent.type === "up") {
1693
- const event = new MouseEvent(this.capturedRenderable, { ...mouseEvent, type: "drag-end" })
1694
- this.capturedRenderable.processMouseEvent(event)
1695
- this.capturedRenderable.processMouseEvent(new MouseEvent(this.capturedRenderable, mouseEvent))
1696
- if (maybeRenderable) {
1697
- const event = new MouseEvent(maybeRenderable, {
1698
- ...mouseEvent,
1699
- type: "drop",
1700
- source: this.capturedRenderable,
1701
- })
1702
- maybeRenderable.processMouseEvent(event)
1703
- }
1704
- this.lastOverRenderable = this.capturedRenderable
1705
- this.lastOverRenderableNum = this.capturedRenderable.num
1706
- this.setCapturedRenderable(undefined)
1707
- // Dropping the renderable needs to push another frame when the renderer is not live
1708
- // to update the hit grid, otherwise capturedRenderable won't be in the hit grid and will not receive mouse events
1709
- this.requestRender()
1710
- }
1711
-
1712
- let event: MouseEvent | undefined
1713
- if (maybeRenderable) {
1714
- if (mouseEvent.type === "drag" && mouseEvent.button === MouseButton.LEFT) {
1715
- this.setCapturedRenderable(maybeRenderable)
1716
- } else {
1717
- this.setCapturedRenderable(undefined)
1718
- }
1719
- event = this.dispatchMouseEvent(maybeRenderable, mouseEvent)
1720
- } else {
1721
- this.setCapturedRenderable(undefined)
1722
- this.lastOverRenderable = undefined
1723
- }
1724
-
1725
- if (!event?.defaultPrevented && mouseEvent.type === "down" && this.currentSelection) {
1726
- this.clearSelection()
1727
- }
1728
-
1729
- return true
1730
- }
1731
-
1732
- /**
1733
- * Recheck hover state after hit grid changes.
1734
- * Called after render when native code detects the hit grid changed.
1735
- * Fires out/over events if the element under the cursor changed.
1736
- */
1737
- private recheckHoverState(): void {
1738
- if (this._isDestroyed || !this._hasPointer) return
1739
- if (this.capturedRenderable) return
1740
-
1741
- const hitId = this.hitTest(this._latestPointer.x, this._latestPointer.y)
1742
- const hitRenderable = Renderable.renderablesByNumber.get(hitId)
1743
- const lastOver = this.lastOverRenderable
1744
-
1745
- // No change
1746
- if (lastOver?.num === hitId) {
1747
- this.lastOverRenderableNum = hitId
1748
- return
1749
- }
1750
-
1751
- const baseEvent: RawMouseEvent = {
1752
- type: "move",
1753
- button: 0,
1754
- x: this._latestPointer.x,
1755
- y: this._latestPointer.y,
1756
- modifiers: this._lastPointerModifiers,
1757
- }
1758
-
1759
- // Fire out on old element
1760
- if (lastOver && !lastOver.isDestroyed) {
1761
- const event = new MouseEvent(lastOver, { ...baseEvent, type: "out" })
1762
- lastOver.processMouseEvent(event)
1763
- }
1764
-
1765
- this.lastOverRenderable = hitRenderable
1766
- this.lastOverRenderableNum = hitId
1767
-
1768
- // Fire over on new element
1769
- if (hitRenderable) {
1770
- const event = new MouseEvent(hitRenderable, {
1771
- ...baseEvent,
1772
- type: "over",
1773
- })
1774
- hitRenderable.processMouseEvent(event)
1775
- }
1776
- }
1777
- public setMousePointer(style: MousePointerStyle): void {
1778
- this._currentMousePointerStyle = style
1779
- this.lib.setCursorStyleOptions(this.rendererPtr, { cursor: style })
1780
- }
1781
-
1782
- public hitTest(x: number, y: number): number {
1783
- return this.lib.checkHit(this.rendererPtr, x, y)
1784
- }
1785
-
1786
- private takeMemorySnapshot(): void {
1787
- if (this._isDestroyed) return
1788
-
1789
- const memoryUsage = process.memoryUsage()
1790
- this.lastMemorySnapshot = {
1791
- heapUsed: memoryUsage.heapUsed,
1792
- heapTotal: memoryUsage.heapTotal,
1793
- arrayBuffers: memoryUsage.arrayBuffers,
1794
- }
1795
-
1796
- this.lib.updateMemoryStats(
1797
- this.rendererPtr,
1798
- this.lastMemorySnapshot.heapUsed,
1799
- this.lastMemorySnapshot.heapTotal,
1800
- this.lastMemorySnapshot.arrayBuffers,
1801
- )
1802
-
1803
- this.emit(CliRenderEvents.MEMORY_SNAPSHOT, this.lastMemorySnapshot)
1804
- }
1805
-
1806
- private startMemorySnapshotTimer(): void {
1807
- this.stopMemorySnapshotTimer()
1808
-
1809
- this.memorySnapshotTimer = this.clock.setInterval(() => {
1810
- this.takeMemorySnapshot()
1811
- }, this.memorySnapshotInterval)
1812
- }
1813
-
1814
- private stopMemorySnapshotTimer(): void {
1815
- if (this.memorySnapshotTimer) {
1816
- this.clock.clearInterval(this.memorySnapshotTimer)
1817
- this.memorySnapshotTimer = null
1818
- }
1819
- }
1820
-
1821
- public setMemorySnapshotInterval(interval: number): void {
1822
- this.memorySnapshotInterval = interval
1823
-
1824
- if (this._isRunning && interval > 0) {
1825
- this.startMemorySnapshotTimer()
1826
- } else if (interval <= 0 && this.memorySnapshotTimer) {
1827
- this.clock.clearInterval(this.memorySnapshotTimer)
1828
- this.memorySnapshotTimer = null
1829
- }
1830
- }
1831
-
1832
- private handleResize(width: number, height: number): void {
1833
- if (this._isDestroyed) return
1834
- if (this._splitHeight > 0) {
1835
- this.processResize(width, height)
1836
- return
1837
- }
1838
-
1839
- if (this.resizeTimeoutId !== null) {
1840
- this.clock.clearTimeout(this.resizeTimeoutId)
1841
- this.resizeTimeoutId = null
1842
- }
1843
-
1844
- this.resizeTimeoutId = this.clock.setTimeout(() => {
1845
- this.resizeTimeoutId = null
1846
- this.processResize(width, height)
1847
- }, this.resizeDebounceDelay)
1848
- }
1849
-
1850
- private queryPixelResolution() {
1851
- this.waitingForPixelResolution = true
1852
- this.updateStdinParserProtocolContext({ pixelResolutionQueryActive: true })
1853
- this.lib.queryPixelResolution(this.rendererPtr)
1854
- }
1855
-
1856
- private processResize(width: number, height: number): void {
1857
- if (width === this._terminalWidth && height === this._terminalHeight) return
1858
-
1859
- const prevWidth = this._terminalWidth
1860
-
1861
- this._terminalWidth = width
1862
- this._terminalHeight = height
1863
- this.queryPixelResolution()
1864
-
1865
- this.setCapturedRenderable(undefined)
1866
- this.stdinParser?.resetMouseState()
1867
-
1868
- if (this._splitHeight > 0) {
1869
- // TODO: Handle resizing split mode properly
1870
- if (width < prevWidth) {
1871
- const start = this._terminalHeight - this._splitHeight * 2
1872
- const flush = ANSI.moveCursorAndClear(start, 1)
1873
- this.writeOut(flush)
1874
- }
1875
- this.renderOffset = height - this._splitHeight
1876
- this.width = width
1877
- this.height = this._splitHeight
1878
- this.currentRenderBuffer.clear(this.backgroundColor)
1879
- this.lib.setRenderOffset(this.rendererPtr, this.renderOffset)
1880
- } else {
1881
- this.width = width
1882
- this.height = height
1883
- }
1884
-
1885
- this.lib.resizeRenderer(this.rendererPtr, this.width, this.height)
1886
- this.nextRenderBuffer = this.lib.getNextBuffer(this.rendererPtr)
1887
- this.currentRenderBuffer = this.lib.getCurrentBuffer(this.rendererPtr)
1888
- this._console.resize(this.width, this.height)
1889
- this.root.resize(this.width, this.height)
1890
- this.emit(CliRenderEvents.RESIZE, this.width, this.height)
1891
- this.requestRender()
1892
- }
1893
-
1894
- public setBackgroundColor(color: ColorInput): void {
1895
- const parsedColor = parseColor(color)
1896
- this.lib.setBackgroundColor(this.rendererPtr, parsedColor as RGBA)
1897
- this.backgroundColor = parsedColor as RGBA
1898
- this.nextRenderBuffer.clear(parsedColor as RGBA)
1899
- this.requestRender()
1900
- }
1901
-
1902
- public toggleDebugOverlay(): void {
1903
- const willBeEnabled = !this.debugOverlay.enabled
1904
-
1905
- if (willBeEnabled && !this.memorySnapshotInterval) {
1906
- this.memorySnapshotInterval = 3000
1907
- this.startMemorySnapshotTimer()
1908
- this.automaticMemorySnapshot = true
1909
- } else if (!willBeEnabled && this.automaticMemorySnapshot) {
1910
- this.stopMemorySnapshotTimer()
1911
- this.memorySnapshotInterval = 0
1912
- this.automaticMemorySnapshot = false
1913
- }
1914
-
1915
- this.debugOverlay.enabled = !this.debugOverlay.enabled
1916
- this.lib.setDebugOverlay(this.rendererPtr, this.debugOverlay.enabled, this.debugOverlay.corner)
1917
- this.emit(CliRenderEvents.DEBUG_OVERLAY_TOGGLE, this.debugOverlay.enabled)
1918
- this.requestRender()
1919
- }
1920
-
1921
- public configureDebugOverlay(options: { enabled?: boolean; corner?: DebugOverlayCorner }): void {
1922
- this.debugOverlay.enabled = options.enabled ?? this.debugOverlay.enabled
1923
- this.debugOverlay.corner = options.corner ?? this.debugOverlay.corner
1924
- this.lib.setDebugOverlay(this.rendererPtr, this.debugOverlay.enabled, this.debugOverlay.corner)
1925
- this.requestRender()
1926
- }
1927
-
1928
- public setTerminalTitle(title: string): void {
1929
- this.lib.setTerminalTitle(this.rendererPtr, title)
1930
- }
1931
-
1932
- public copyToClipboardOSC52(text: string, target?: ClipboardTarget): boolean {
1933
- return this.clipboard.copyToClipboardOSC52(text, target)
1934
- }
1935
-
1936
- public clearClipboardOSC52(target?: ClipboardTarget): boolean {
1937
- return this.clipboard.clearClipboardOSC52(target)
1938
- }
1939
-
1940
- public isOsc52Supported(): boolean {
1941
- return this._capabilities?.osc52 ?? this.clipboard.isOsc52Supported()
1942
- }
1943
-
1944
- public dumpHitGrid(): void {
1945
- this.lib.dumpHitGrid(this.rendererPtr)
1946
- }
1947
-
1948
- public dumpBuffers(timestamp?: number): void {
1949
- this.lib.dumpBuffers(this.rendererPtr, timestamp)
1950
- }
1951
-
1952
- public dumpStdoutBuffer(timestamp?: number): void {
1953
- this.lib.dumpStdoutBuffer(this.rendererPtr, timestamp)
1954
- }
1955
-
1956
- public static setCursorPosition(renderer: CliRenderer, x: number, y: number, visible: boolean = true): void {
1957
- const lib = resolveRenderLib()
1958
- lib.setCursorPosition(renderer.rendererPtr, x, y, visible)
1959
- }
1960
-
1961
- public static setCursorStyle(renderer: CliRenderer, options: CursorStyleOptions): void {
1962
- const lib = resolveRenderLib()
1963
- lib.setCursorStyleOptions(renderer.rendererPtr, options)
1964
- if (options.cursor !== undefined) {
1965
- renderer._currentMousePointerStyle = options.cursor
1966
- }
1967
- }
1968
-
1969
- public static setCursorColor(renderer: CliRenderer, color: RGBA): void {
1970
- const lib = resolveRenderLib()
1971
- lib.setCursorColor(renderer.rendererPtr, color)
1972
- }
1973
-
1974
- public setCursorPosition(x: number, y: number, visible: boolean = true): void {
1975
- this.lib.setCursorPosition(this.rendererPtr, x, y, visible)
1976
- }
1977
-
1978
- public setCursorStyle(options: CursorStyleOptions): void {
1979
- this.lib.setCursorStyleOptions(this.rendererPtr, options)
1980
- if (options.cursor !== undefined) {
1981
- this._currentMousePointerStyle = options.cursor
1982
- }
1983
- }
1984
-
1985
- public setCursorColor(color: RGBA): void {
1986
- this.lib.setCursorColor(this.rendererPtr, color)
1987
- }
1988
-
1989
- public getCursorState() {
1990
- return this.lib.getCursorState(this.rendererPtr)
1991
- }
1992
-
1993
- public addPostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void {
1994
- this.postProcessFns.push(processFn)
1995
- }
1996
-
1997
- public removePostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void {
1998
- this.postProcessFns = this.postProcessFns.filter((fn) => fn !== processFn)
1999
- }
2000
-
2001
- public clearPostProcessFns(): void {
2002
- this.postProcessFns = []
2003
- }
2004
-
2005
- public setFrameCallback(callback: (deltaTime: number) => Promise<void>): void {
2006
- this.frameCallbacks.push(callback)
2007
- }
2008
-
2009
- public removeFrameCallback(callback: (deltaTime: number) => Promise<void>): void {
2010
- this.frameCallbacks = this.frameCallbacks.filter((cb) => cb !== callback)
2011
- }
2012
-
2013
- public clearFrameCallbacks(): void {
2014
- this.frameCallbacks = []
2015
- }
2016
-
2017
- public requestLive(): void {
2018
- this.liveRequestCounter++
2019
-
2020
- if (this._controlState === RendererControlState.IDLE && this.liveRequestCounter > 0) {
2021
- this._controlState = RendererControlState.AUTO_STARTED
2022
- this.internalStart()
2023
- }
2024
- }
2025
-
2026
- public dropLive(): void {
2027
- this.liveRequestCounter = Math.max(0, this.liveRequestCounter - 1)
2028
-
2029
- if (this._controlState === RendererControlState.AUTO_STARTED && this.liveRequestCounter === 0) {
2030
- this._controlState = RendererControlState.IDLE
2031
- this.internalPause()
2032
- }
2033
- }
2034
-
2035
- public start(): void {
2036
- this._controlState = RendererControlState.EXPLICIT_STARTED
2037
- this.internalStart()
2038
- }
2039
-
2040
- public auto(): void {
2041
- this._controlState = this._isRunning ? RendererControlState.AUTO_STARTED : RendererControlState.IDLE
2042
- }
2043
-
2044
- private internalStart(): void {
2045
- if (!this._isRunning && !this._isDestroyed) {
2046
- this._isRunning = true
2047
-
2048
- // Invalidate any queued idle one-shot frame.
2049
- // start()/live/resume transition to the continuous loop, so queued
2050
- // activateFrame callbacks must no-op via !updateScheduled.
2051
- this.updateScheduled = false
2052
-
2053
- if (this.memorySnapshotInterval > 0) {
2054
- this.startMemorySnapshotTimer()
2055
- }
2056
-
2057
- this.startRenderLoop()
2058
- }
2059
- }
2060
-
2061
- public pause(): void {
2062
- this._controlState = RendererControlState.EXPLICIT_PAUSED
2063
- this.internalPause()
2064
- }
2065
-
2066
- public suspend(): void {
2067
- this._previousControlState = this._controlState
2068
-
2069
- this._controlState = RendererControlState.EXPLICIT_SUSPENDED
2070
- this.internalPause()
2071
-
2072
- this._suspendedMouseEnabled = this._useMouse
2073
-
2074
- this.disableMouse()
2075
- this.removeExitListeners()
2076
- this.waitingForPixelResolution = false
2077
- this.updateStdinParserProtocolContext({
2078
- privateCapabilityRepliesActive: false,
2079
- pixelResolutionQueryActive: false,
2080
- explicitWidthCprActive: false,
2081
- })
2082
- this.stdinParser?.reset()
2083
- this.stdin.removeListener("data", this.stdinListener)
2084
- this.lib.suspendRenderer(this.rendererPtr)
2085
-
2086
- if (this.stdin.setRawMode) {
2087
- this.stdin.setRawMode(false)
2088
- }
2089
-
2090
- this.stdin.pause()
2091
- }
2092
-
2093
- public resume(): void {
2094
- if (this.stdin.setRawMode) {
2095
- this.stdin.setRawMode(true)
2096
- }
2097
-
2098
- // Drain any input buffered during suspension before registering the
2099
- // listener. Adding a "data" listener can auto-resume a Readable, so the
2100
- // drain must come first while the stream is still paused and read()
2101
- // pulls from the internal buffer rather than being a flowing-mode no-op.
2102
- while (this.stdin.read() !== null) {}
2103
- this.stdin.on("data", this.stdinListener)
2104
- this.stdin.resume()
2105
- this.addExitListeners()
2106
-
2107
- this.lib.resumeRenderer(this.rendererPtr)
2108
-
2109
- if (this._suspendedMouseEnabled) {
2110
- this.enableMouse()
2111
- }
2112
-
2113
- this.currentRenderBuffer.clear(this.backgroundColor)
2114
- this._controlState = this._previousControlState
2115
-
2116
- if (
2117
- this._previousControlState === RendererControlState.AUTO_STARTED ||
2118
- this._previousControlState === RendererControlState.EXPLICIT_STARTED
2119
- ) {
2120
- this.internalStart()
2121
- } else {
2122
- this.requestRender()
2123
- }
2124
- }
2125
-
2126
- private internalPause(): void {
2127
- this._isRunning = false
2128
-
2129
- if (this.renderTimeout) {
2130
- this.clock.clearTimeout(this.renderTimeout)
2131
- this.renderTimeout = null
2132
- }
2133
-
2134
- if (!this.rendering) {
2135
- this.resolveIdleIfNeeded()
2136
- }
2137
- }
2138
-
2139
- public stop(): void {
2140
- this._controlState = RendererControlState.EXPLICIT_STOPPED
2141
- this.internalStop()
2142
- }
2143
-
2144
- private internalStop(): void {
2145
- if (this.isRunning && !this._isDestroyed) {
2146
- this._isRunning = false
2147
-
2148
- if (this.memorySnapshotTimer) {
2149
- this.clock.clearInterval(this.memorySnapshotTimer)
2150
- this.memorySnapshotTimer = null
2151
- }
2152
-
2153
- if (this.renderTimeout) {
2154
- this.clock.clearTimeout(this.renderTimeout)
2155
- this.renderTimeout = null
2156
- }
2157
-
2158
- // If we're currently rendering, the frame will resolve idle when it completes
2159
- // Otherwise, resolve immediately
2160
- if (!this.rendering) {
2161
- this.resolveIdleIfNeeded()
2162
- }
2163
- }
2164
- }
2165
-
2166
- public destroy(): void {
2167
- if (this._isDestroyed) return
2168
- this._isDestroyed = true
2169
- this._destroyPending = true
2170
-
2171
- if (this.rendering) {
2172
- // Restore terminal/input state immediately, but defer full native teardown until the frame unwinds.
2173
- this.prepareDestroyDuringRender()
2174
- return
2175
- }
2176
-
2177
- this.finalizeDestroy()
2178
- }
2179
-
2180
- private cleanupBeforeDestroy(): void {
2181
- if (this._destroyCleanupPrepared) return
2182
- this._destroyCleanupPrepared = true
2183
-
2184
- process.removeListener("SIGWINCH", this.sigwinchHandler)
2185
- process.removeListener("uncaughtException", this.handleError)
2186
- process.removeListener("unhandledRejection", this.handleError)
2187
- process.removeListener("warning", this.warningHandler)
2188
- process.removeListener("beforeExit", this.exitHandler)
2189
- capture.removeListener("write", this.captureCallback)
2190
- this.removeExitListeners()
2191
-
2192
- if (this.resizeTimeoutId !== null) {
2193
- this.clock.clearTimeout(this.resizeTimeoutId)
2194
- this.resizeTimeoutId = null
2195
- }
2196
-
2197
- if (this.capabilityTimeoutId !== null) {
2198
- this.clock.clearTimeout(this.capabilityTimeoutId)
2199
- this.capabilityTimeoutId = null
2200
- }
2201
-
2202
- if (this.memorySnapshotTimer) {
2203
- this.clock.clearInterval(this.memorySnapshotTimer)
2204
- this.memorySnapshotTimer = null
2205
- }
2206
-
2207
- if (this.renderTimeout) {
2208
- this.clock.clearTimeout(this.renderTimeout)
2209
- this.renderTimeout = null
2210
- }
2211
-
2212
- this._isRunning = false
2213
- this.waitingForPixelResolution = false
2214
- this.updateStdinParserProtocolContext(
2215
- {
2216
- privateCapabilityRepliesActive: false,
2217
- pixelResolutionQueryActive: false,
2218
- explicitWidthCprActive: false,
2219
- },
2220
- true,
2221
- )
2222
- this.setCapturedRenderable(undefined)
2223
-
2224
- this.stdin.removeListener("data", this.stdinListener)
2225
- if (this.stdin.setRawMode) {
2226
- this.stdin.setRawMode(false)
2227
- }
2228
-
2229
- this.externalOutputMode = "passthrough"
2230
-
2231
- if (this._splitHeight > 0) {
2232
- this.flushStdoutCache(this._splitHeight, true)
2233
- }
2234
- }
2235
-
2236
- private prepareDestroyDuringRender(): void {
2237
- this.cleanupBeforeDestroy()
2238
- this.lib.suspendRenderer(this.rendererPtr)
2239
- }
2240
-
2241
- private finalizeDestroy(): void {
2242
- if (this._destroyFinalized) return
2243
-
2244
- this._destroyFinalized = true
2245
- this._destroyPending = false
2246
-
2247
- this.cleanupBeforeDestroy()
2248
-
2249
- // Clean up palette detector
2250
- if (this._paletteDetector) {
2251
- this._paletteDetector.cleanup()
2252
- this._paletteDetector = null
2253
- }
2254
- this._paletteDetectionPromise = null
2255
- this._cachedPalette = null
2256
-
2257
- this.emit(CliRenderEvents.DESTROY)
2258
-
2259
- try {
2260
- this.root.destroyRecursively()
2261
- } catch (e) {
2262
- console.error("Error destroying root renderable:", e instanceof Error ? e.stack : String(e))
2263
- }
2264
-
2265
- this.stdinParser?.destroy()
2266
- this.stdinParser = null
2267
- this.oscSubscribers.clear()
2268
- this._console.destroy()
2269
-
2270
- this.lib.destroyRenderer(this.rendererPtr)
2271
- rendererTracker.removeRenderer(this)
2272
-
2273
- if (this._onDestroy) {
2274
- try {
2275
- this._onDestroy()
2276
- } catch (e) {
2277
- console.error("Error in onDestroy callback:", e instanceof Error ? e.stack : String(e))
2278
- }
2279
- }
2280
-
2281
- // Resolve any pending idle() calls
2282
- this.resolveIdleIfNeeded()
2283
- }
2284
-
2285
- private startRenderLoop(): void {
2286
- if (!this._isRunning) return
2287
-
2288
- this.lastTime = this.normalizeClockTime(this.clock.now(), 0)
2289
- this.frameCount = 0
2290
- this.lastFpsTime = this.lastTime
2291
- this.currentFps = 0
2292
-
2293
- this.loop()
2294
- }
2295
-
2296
- private async loop(): Promise<void> {
2297
- if (this.rendering || this._isDestroyed) return
2298
- this.renderTimeout = null
2299
-
2300
- this.rendering = true
2301
- if (this.renderTimeout) {
2302
- this.clock.clearTimeout(this.renderTimeout)
2303
- this.renderTimeout = null
2304
- }
2305
- try {
2306
- const now = this.normalizeClockTime(this.clock.now(), this.lastTime)
2307
- const elapsed = this.getElapsedMs(now, this.lastTime)
2308
-
2309
- const deltaTime = elapsed
2310
- this.lastTime = now
2311
-
2312
- this.frameCount++
2313
- if (this.getElapsedMs(now, this.lastFpsTime) >= 1000) {
2314
- this.currentFps = this.frameCount
2315
- this.frameCount = 0
2316
- this.lastFpsTime = now
2317
- }
2318
-
2319
- this.renderStats.frameCount++
2320
- this.renderStats.fps = this.currentFps
2321
- const overallStart = performance.now()
2322
-
2323
- const frameRequests = Array.from(this.animationRequest.values())
2324
- this.animationRequest.clear()
2325
- const animationRequestStart = performance.now()
2326
- for (const callback of frameRequests) {
2327
- callback(deltaTime)
2328
- this.dropLive()
2329
- }
2330
- const animationRequestEnd = performance.now()
2331
- const animationRequestTime = animationRequestEnd - animationRequestStart
2332
-
2333
- const start = performance.now()
2334
- for (const frameCallback of this.frameCallbacks) {
2335
- try {
2336
- await frameCallback(deltaTime)
2337
- } catch (error) {
2338
- console.error("Error in frame callback:", error)
2339
- }
2340
- }
2341
- const end = performance.now()
2342
- this.renderStats.frameCallbackTime = end - start
2343
-
2344
- this.root.render(this.nextRenderBuffer, deltaTime)
2345
-
2346
- for (const postProcessFn of this.postProcessFns) {
2347
- postProcessFn(this.nextRenderBuffer, deltaTime)
2348
- }
2349
-
2350
- this._console.renderToBuffer(this.nextRenderBuffer)
2351
-
2352
- // If destroy() was requested during this frame, skip native work and scheduling.
2353
- if (!this._isDestroyed) {
2354
- this.renderNative()
2355
-
2356
- // Check if hit grid changed and recheck hover state if needed
2357
- if (this._useMouse && this.lib.getHitGridDirty(this.rendererPtr)) {
2358
- this.recheckHoverState()
2359
- }
2360
-
2361
- const overallFrameTime = performance.now() - overallStart
2362
-
2363
- // TODO: Add animationRequestTime to stats
2364
- this.lib.updateStats(
2365
- this.rendererPtr,
2366
- overallFrameTime,
2367
- this.renderStats.fps,
2368
- this.renderStats.frameCallbackTime,
2369
- )
2370
-
2371
- if (this.gatherStats) {
2372
- this.collectStatSample(overallFrameTime)
2373
- }
2374
-
2375
- if (this._isRunning || this.immediateRerenderRequested) {
2376
- const targetFrameTime = this.immediateRerenderRequested ? this.minTargetFrameTime : this.targetFrameTime
2377
- const delay = Math.max(1, targetFrameTime - Math.floor(overallFrameTime))
2378
- this.immediateRerenderRequested = false
2379
- this.renderTimeout = this.clock.setTimeout(() => {
2380
- this.renderTimeout = null
2381
- this.loop()
2382
- }, delay)
2383
- } else {
2384
- this.clock.clearTimeout(this.renderTimeout!)
2385
- this.renderTimeout = null
2386
- }
2387
- }
2388
- } finally {
2389
- this.rendering = false
2390
- if (this._destroyPending) {
2391
- this.finalizeDestroy()
2392
- }
2393
- this.resolveIdleIfNeeded()
2394
- }
2395
- }
2396
-
2397
- public intermediateRender(): void {
2398
- this.immediateRerenderRequested = true
2399
- this.loop()
2400
- }
2401
-
2402
- private renderNative(): void {
2403
- if (this.renderingNative) {
2404
- console.error("Rendering called concurrently")
2405
- throw new Error("Rendering called concurrently")
2406
- }
2407
-
2408
- let force = false
2409
- if (this._splitHeight > 0) {
2410
- // TODO: Flickering could maybe be even more reduced by moving the flush to the native layer,
2411
- // to output the flush with the buffered writer, after the render is done.
2412
- force = this.flushStdoutCache(this._splitHeight)
2413
- }
2414
-
2415
- this.renderingNative = true
2416
- this.lib.render(this.rendererPtr, force)
2417
- // this.dumpStdoutBuffer(Date.now())
2418
- this.renderingNative = false
2419
- }
2420
-
2421
- private collectStatSample(frameTime: number): void {
2422
- this.frameTimes.push(frameTime)
2423
- if (this.frameTimes.length > this.maxStatSamples) {
2424
- this.frameTimes.shift()
2425
- }
2426
- }
2427
-
2428
- public getStats(): {
2429
- fps: number
2430
- frameCount: number
2431
- frameTimes: number[]
2432
- averageFrameTime: number
2433
- minFrameTime: number
2434
- maxFrameTime: number
2435
- } {
2436
- const frameTimes = [...this.frameTimes]
2437
- const sum = frameTimes.reduce((acc, time) => acc + time, 0)
2438
- const avg = frameTimes.length ? sum / frameTimes.length : 0
2439
- const min = frameTimes.length ? Math.min(...frameTimes) : 0
2440
- const max = frameTimes.length ? Math.max(...frameTimes) : 0
2441
-
2442
- return {
2443
- fps: this.renderStats.fps,
2444
- frameCount: this.renderStats.frameCount,
2445
- frameTimes,
2446
- averageFrameTime: avg,
2447
- minFrameTime: min,
2448
- maxFrameTime: max,
2449
- }
2450
- }
2451
-
2452
- public resetStats(): void {
2453
- this.frameTimes = []
2454
- this.renderStats.frameCount = 0
2455
- }
2456
-
2457
- public setGatherStats(enabled: boolean): void {
2458
- this.gatherStats = enabled
2459
- if (!enabled) {
2460
- this.frameTimes = []
2461
- }
2462
- }
2463
-
2464
- public getSelection(): Selection | null {
2465
- return this.currentSelection
2466
- }
2467
-
2468
- public get hasSelection(): boolean {
2469
- return !!this.currentSelection
2470
- }
2471
-
2472
- public getSelectionContainer(): Renderable | null {
2473
- return this.selectionContainers.length > 0 ? this.selectionContainers[this.selectionContainers.length - 1] : null
2474
- }
2475
-
2476
- public clearSelection(): void {
2477
- if (this.currentSelection) {
2478
- for (const renderable of this.currentSelection.touchedRenderables) {
2479
- if (renderable.selectable && !renderable.isDestroyed) {
2480
- renderable.onSelectionChanged(null)
2481
- }
2482
- }
2483
- this.currentSelection = null
2484
- }
2485
- this.selectionContainers = []
2486
- }
2487
-
2488
- /**
2489
- * Start a new selection at the given coordinates.
2490
- * Used by both mouse and keyboard selection.
2491
- */
2492
- public startSelection(renderable: Renderable, x: number, y: number): void {
2493
- if (!renderable.selectable) return
2494
-
2495
- this.clearSelection()
2496
- this.selectionContainers.push(renderable.parent || this.root)
2497
- this.currentSelection = new Selection(renderable, { x, y }, { x, y })
2498
- this.currentSelection.isStart = true
2499
-
2500
- this.notifySelectablesOfSelectionChange()
2501
- }
2502
-
2503
- public updateSelection(
2504
- currentRenderable: Renderable | undefined,
2505
- x: number,
2506
- y: number,
2507
- options?: { finishDragging?: boolean },
2508
- ): void {
2509
- if (this.currentSelection) {
2510
- this.currentSelection.isStart = false
2511
- this.currentSelection.focus = { x, y }
2512
-
2513
- if (options?.finishDragging) {
2514
- this.currentSelection.isDragging = false
2515
- }
2516
-
2517
- if (this.selectionContainers.length > 0) {
2518
- const currentContainer = this.selectionContainers[this.selectionContainers.length - 1]
2519
-
2520
- if (!currentRenderable || !this.isWithinContainer(currentRenderable, currentContainer)) {
2521
- const parentContainer = currentContainer.parent || this.root
2522
- this.selectionContainers.push(parentContainer)
2523
- } else if (currentRenderable && this.selectionContainers.length > 1) {
2524
- let containerIndex = this.selectionContainers.indexOf(currentRenderable)
2525
-
2526
- if (containerIndex === -1) {
2527
- const immediateParent = currentRenderable.parent || this.root
2528
- containerIndex = this.selectionContainers.indexOf(immediateParent)
2529
- }
2530
-
2531
- if (containerIndex !== -1 && containerIndex < this.selectionContainers.length - 1) {
2532
- this.selectionContainers = this.selectionContainers.slice(0, containerIndex + 1)
2533
- }
2534
- }
2535
- }
2536
-
2537
- this.notifySelectablesOfSelectionChange()
2538
- }
2539
- }
2540
-
2541
- public requestSelectionUpdate(): void {
2542
- if (this.currentSelection?.isDragging) {
2543
- const pointer = this._latestPointer
2544
-
2545
- const maybeRenderableId = this.hitTest(pointer.x, pointer.y)
2546
- const maybeRenderable = Renderable.renderablesByNumber.get(maybeRenderableId)
2547
-
2548
- this.updateSelection(maybeRenderable, pointer.x, pointer.y)
2549
- }
2550
- }
2551
-
2552
- private isWithinContainer(renderable: Renderable, container: Renderable): boolean {
2553
- let current: Renderable | null = renderable
2554
- while (current) {
2555
- if (current === container) return true
2556
- current = current.parent
2557
- }
2558
- return false
2559
- }
2560
-
2561
- private finishSelection(): void {
2562
- if (this.currentSelection) {
2563
- this.currentSelection.isDragging = false
2564
- this.emit(CliRenderEvents.SELECTION, this.currentSelection)
2565
- this.notifySelectablesOfSelectionChange()
2566
- }
2567
- }
2568
-
2569
- private notifySelectablesOfSelectionChange(): void {
2570
- const selectedRenderables: Renderable[] = []
2571
- const touchedRenderables: Renderable[] = []
2572
- const currentContainer =
2573
- this.selectionContainers.length > 0 ? this.selectionContainers[this.selectionContainers.length - 1] : this.root
2574
-
2575
- if (this.currentSelection) {
2576
- this.walkSelectableRenderables(
2577
- currentContainer,
2578
- this.currentSelection.bounds,
2579
- selectedRenderables,
2580
- touchedRenderables,
2581
- )
2582
-
2583
- for (const renderable of this.currentSelection.touchedRenderables) {
2584
- if (!touchedRenderables.includes(renderable) && !renderable.isDestroyed) {
2585
- renderable.onSelectionChanged(null)
2586
- }
2587
- }
2588
-
2589
- this.currentSelection.updateSelectedRenderables(selectedRenderables)
2590
- this.currentSelection.updateTouchedRenderables(touchedRenderables)
2591
- }
2592
- }
2593
-
2594
- private walkSelectableRenderables(
2595
- container: Renderable,
2596
- selectionBounds: ViewportBounds,
2597
- selectedRenderables: Renderable[],
2598
- touchedRenderables: Renderable[],
2599
- ): void {
2600
- const children = getObjectsInViewport<Renderable>(
2601
- selectionBounds,
2602
- container.getChildrenSortedByPrimaryAxis(),
2603
- container.primaryAxis,
2604
- 0, // padding
2605
- 0, // minTriggerSize - always perform overlap checks for selection
2606
- )
2607
-
2608
- for (const child of children) {
2609
- if (child.selectable) {
2610
- const hasSelection = child.onSelectionChanged(this.currentSelection)
2611
- if (hasSelection) {
2612
- selectedRenderables.push(child)
2613
- }
2614
- touchedRenderables.push(child)
2615
- }
2616
- if (child.getChildrenCount() > 0) {
2617
- this.walkSelectableRenderables(child, selectionBounds, selectedRenderables, touchedRenderables)
2618
- }
2619
- }
2620
- }
2621
-
2622
- public get paletteDetectionStatus(): "idle" | "detecting" | "cached" {
2623
- if (this._cachedPalette) return "cached"
2624
- if (this._paletteDetectionPromise) return "detecting"
2625
- return "idle"
2626
- }
2627
-
2628
- public clearPaletteCache(): void {
2629
- this._cachedPalette = null
2630
- }
2631
-
2632
- /**
2633
- * Detects the terminal's color palette
2634
- *
2635
- * @returns Promise resolving to TerminalColors object containing palette and special colors
2636
- * @throws Error if renderer is suspended
2637
- */
2638
- public async getPalette(options?: GetPaletteOptions): Promise<TerminalColors> {
2639
- if (this._controlState === RendererControlState.EXPLICIT_SUSPENDED) {
2640
- throw new Error("Cannot detect palette while renderer is suspended")
2641
- }
2642
-
2643
- const requestedSize = options?.size ?? 16
2644
-
2645
- if (this._cachedPalette && this._cachedPalette.palette.length !== requestedSize) {
2646
- this._cachedPalette = null
2647
- }
2648
-
2649
- if (this._cachedPalette) {
2650
- return this._cachedPalette
2651
- }
2652
-
2653
- if (this._paletteDetectionPromise) {
2654
- return this._paletteDetectionPromise
2655
- }
2656
-
2657
- if (!this._paletteDetector) {
2658
- const isLegacyTmux =
2659
- this.capabilities?.terminal?.name?.toLowerCase()?.includes("tmux") &&
2660
- this.capabilities?.terminal?.version?.localeCompare("3.6") < 0
2661
- this._paletteDetector = createTerminalPalette(
2662
- this.stdin,
2663
- this.stdout,
2664
- this.writeOut.bind(this),
2665
- isLegacyTmux,
2666
- {
2667
- subscribeOsc: this.subscribeOsc.bind(this),
2668
- },
2669
- this.clock,
2670
- )
2671
- }
2672
-
2673
- this._paletteDetectionPromise = this._paletteDetector.detect(options).then((result) => {
2674
- this._cachedPalette = result
2675
- this._paletteDetectionPromise = null
2676
- return result
2677
- })
2678
-
2679
- return this._paletteDetectionPromise
2680
- }
2681
- }