@xincli/opentui-core 0.4.6

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 (476) hide show
  1. package/README.md +63 -0
  2. package/dev/keypress-debug-renderer.ts +148 -0
  3. package/dev/keypress-debug.ts +43 -0
  4. package/dev/print-env-vars.ts +32 -0
  5. package/dev/test-tmux-graphics-334.sh +68 -0
  6. package/dev/test-tmux-theme-queries.sh +151 -0
  7. package/dev/thai-debug-test.ts +68 -0
  8. package/dev/theme-mode.ts +290 -0
  9. package/docs/development.md +144 -0
  10. package/package.json +102 -0
  11. package/prebuilt/aarch64-android/libopentui.so +0 -0
  12. package/scripts/build-android.ts +226 -0
  13. package/scripts/build-native-termux.sh +525 -0
  14. package/scripts/build.ts +530 -0
  15. package/scripts/dist-test.ts +312 -0
  16. package/scripts/package-prebuilt.ts +110 -0
  17. package/scripts/publish.ts +60 -0
  18. package/scripts/test-node-hook.mjs +16 -0
  19. package/scripts/test-node.ts +241 -0
  20. package/scripts/vendor-deps.sh +73 -0
  21. package/src/NativeSpanFeed.ts +375 -0
  22. package/src/Renderable.ts +1878 -0
  23. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  24. package/src/animation/Timeline.test.ts +2709 -0
  25. package/src/animation/Timeline.ts +598 -0
  26. package/src/ansi.ts +22 -0
  27. package/src/audio.ts +479 -0
  28. package/src/benchmark/attenuation-benchmark.ts +81 -0
  29. package/src/benchmark/audio-playback-benchmark.ts +344 -0
  30. package/src/benchmark/box-draw-benchmark.ts +1042 -0
  31. package/src/benchmark/colormatrix-benchmark.ts +123 -0
  32. package/src/benchmark/gain-benchmark.ts +80 -0
  33. package/src/benchmark/latest-all-bench-run.json +707 -0
  34. package/src/benchmark/latest-async-bench-run.json +336 -0
  35. package/src/benchmark/latest-default-bench-run.json +657 -0
  36. package/src/benchmark/latest-large-bench-run.json +707 -0
  37. package/src/benchmark/latest-quick-bench-run.json +207 -0
  38. package/src/benchmark/layout-benchmark.ts +2547 -0
  39. package/src/benchmark/markdown-benchmark.ts +1796 -0
  40. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  41. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  42. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  43. package/src/benchmark/native-span-feed-compare.ts +280 -0
  44. package/src/benchmark/render-traversal-benchmark.ts +928 -0
  45. package/src/benchmark/text-buffer-render-benchmark.ts +874 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +292 -0
  48. package/src/buffer.ts +564 -0
  49. package/src/console.test.ts +629 -0
  50. package/src/console.ts +1253 -0
  51. package/src/edit-buffer.test.ts +1852 -0
  52. package/src/edit-buffer.ts +417 -0
  53. package/src/editor-view.test.ts +1056 -0
  54. package/src/editor-view.ts +290 -0
  55. package/src/index.ts +25 -0
  56. package/src/lib/KeyHandler.integration.test.ts +292 -0
  57. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  58. package/src/lib/KeyHandler.test.ts +662 -0
  59. package/src/lib/KeyHandler.ts +222 -0
  60. package/src/lib/RGBA.test.ts +60 -0
  61. package/src/lib/RGBA.ts +343 -0
  62. package/src/lib/ascii.font.ts +330 -0
  63. package/src/lib/border.test.ts +83 -0
  64. package/src/lib/border.ts +170 -0
  65. package/src/lib/bunfs.test.ts +27 -0
  66. package/src/lib/bunfs.ts +18 -0
  67. package/src/lib/clipboard.test.ts +63 -0
  68. package/src/lib/clipboard.ts +41 -0
  69. package/src/lib/clock.ts +35 -0
  70. package/src/lib/data-paths.test.ts +133 -0
  71. package/src/lib/data-paths.ts +109 -0
  72. package/src/lib/debounce.ts +106 -0
  73. package/src/lib/detect-links.test.ts +98 -0
  74. package/src/lib/detect-links.ts +56 -0
  75. package/src/lib/env.test.ts +228 -0
  76. package/src/lib/env.ts +209 -0
  77. package/src/lib/extmarks-history.ts +51 -0
  78. package/src/lib/extmarks-multiwidth.test.ts +324 -0
  79. package/src/lib/extmarks.test.ts +3547 -0
  80. package/src/lib/extmarks.ts +861 -0
  81. package/src/lib/fonts/block.json +405 -0
  82. package/src/lib/fonts/grid.json +265 -0
  83. package/src/lib/fonts/huge.json +741 -0
  84. package/src/lib/fonts/pallet.json +314 -0
  85. package/src/lib/fonts/shade.json +591 -0
  86. package/src/lib/fonts/slick.json +321 -0
  87. package/src/lib/fonts/tiny.json +69 -0
  88. package/src/lib/hast-styled-text.ts +59 -0
  89. package/src/lib/index.ts +22 -0
  90. package/src/lib/keybinding.internal.test.ts +342 -0
  91. package/src/lib/keybinding.internal.ts +182 -0
  92. package/src/lib/objects-in-viewport.test.ts +789 -0
  93. package/src/lib/objects-in-viewport.ts +153 -0
  94. package/src/lib/output.capture.ts +58 -0
  95. package/src/lib/parse.keypress-kitty.protocol.test.ts +347 -0
  96. package/src/lib/parse.keypress-kitty.test.ts +712 -0
  97. package/src/lib/parse.keypress-kitty.ts +470 -0
  98. package/src/lib/parse.keypress.test.ts +2020 -0
  99. package/src/lib/parse.keypress.ts +480 -0
  100. package/src/lib/parse.mouse.test.ts +552 -0
  101. package/src/lib/parse.mouse.ts +232 -0
  102. package/src/lib/paste.ts +18 -0
  103. package/src/lib/queue.ts +65 -0
  104. package/src/lib/render-geometry.ts +36 -0
  105. package/src/lib/renderable.validations.test.ts +87 -0
  106. package/src/lib/renderable.validations.ts +83 -0
  107. package/src/lib/scroll-acceleration.ts +98 -0
  108. package/src/lib/selection.ts +261 -0
  109. package/src/lib/singleton.ts +34 -0
  110. package/src/lib/stdin-parser.test.ts +2439 -0
  111. package/src/lib/stdin-parser.ts +2007 -0
  112. package/src/lib/styled-text.ts +178 -0
  113. package/src/lib/terminal-capability-detection.test.ts +226 -0
  114. package/src/lib/terminal-capability-detection.ts +98 -0
  115. package/src/lib/terminal-palette.test.ts +954 -0
  116. package/src/lib/terminal-palette.ts +441 -0
  117. package/src/lib/tree-sitter/assets/README.md +119 -0
  118. package/src/lib/tree-sitter/assets/javascript/highlights.scm +205 -0
  119. package/src/lib/tree-sitter/assets/javascript/tree-sitter-javascript.wasm +0 -0
  120. package/src/lib/tree-sitter/assets/markdown/highlights.scm +150 -0
  121. package/src/lib/tree-sitter/assets/markdown/injections.scm +27 -0
  122. package/src/lib/tree-sitter/assets/markdown/tree-sitter-markdown.wasm +0 -0
  123. package/src/lib/tree-sitter/assets/markdown_inline/highlights.scm +115 -0
  124. package/src/lib/tree-sitter/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  125. package/src/lib/tree-sitter/assets/typescript/highlights.scm +604 -0
  126. package/src/lib/tree-sitter/assets/typescript/tree-sitter-typescript.wasm +0 -0
  127. package/src/lib/tree-sitter/assets/update.ts +360 -0
  128. package/src/lib/tree-sitter/assets/zig/highlights.scm +284 -0
  129. package/src/lib/tree-sitter/assets/zig/tree-sitter-zig.wasm +0 -0
  130. package/src/lib/tree-sitter/assets.d.ts +9 -0
  131. package/src/lib/tree-sitter/cache.test.ts +291 -0
  132. package/src/lib/tree-sitter/client.test.ts +1434 -0
  133. package/src/lib/tree-sitter/client.ts +847 -0
  134. package/src/lib/tree-sitter/default-parsers.ts +133 -0
  135. package/src/lib/tree-sitter/download-utils.ts +148 -0
  136. package/src/lib/tree-sitter/index.ts +75 -0
  137. package/src/lib/tree-sitter/parser.worker.ts +1098 -0
  138. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  139. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  140. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  141. package/src/lib/tree-sitter/types.ts +132 -0
  142. package/src/lib/tree-sitter/update-assets.ts +10 -0
  143. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  144. package/src/lib/tree-sitter-styled-text.ts +314 -0
  145. package/src/lib/validate-dir-name.ts +55 -0
  146. package/src/lib/yoga.options.test.ts +628 -0
  147. package/src/lib/yoga.options.ts +346 -0
  148. package/src/native-event-worker-repro.test.ts +48 -0
  149. package/src/native-event-worker-repro.worker.ts +9 -0
  150. package/src/native-handle.test.ts +221 -0
  151. package/src/platform/ffi.test.ts +516 -0
  152. package/src/platform/ffi.ts +653 -0
  153. package/src/platform/runtime.test.ts +52 -0
  154. package/src/platform/runtime.ts +121 -0
  155. package/src/platform/test.ts +45 -0
  156. package/src/platform/worker-handler-cleanup.fixture.ts +12 -0
  157. package/src/platform/worker-onmessage-startup.fixture.ts +10 -0
  158. package/src/platform/worker-startup.fixture.ts +9 -0
  159. package/src/platform/worker.node-test.ts +153 -0
  160. package/src/platform/worker.ts +425 -0
  161. package/src/plugins/core-slot.ts +579 -0
  162. package/src/plugins/registry.ts +402 -0
  163. package/src/plugins/types.ts +46 -0
  164. package/src/post/effects.ts +948 -0
  165. package/src/post/filters.ts +512 -0
  166. package/src/post/matrices.ts +288 -0
  167. package/src/renderables/ASCIIFont.ts +219 -0
  168. package/src/renderables/Box.test.ts +474 -0
  169. package/src/renderables/Box.ts +353 -0
  170. package/src/renderables/Code.test.ts +2388 -0
  171. package/src/renderables/Code.ts +555 -0
  172. package/src/renderables/Diff.regression.test.ts +381 -0
  173. package/src/renderables/Diff.test.ts +3212 -0
  174. package/src/renderables/Diff.ts +1278 -0
  175. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  176. package/src/renderables/EditBufferRenderable.ts +1152 -0
  177. package/src/renderables/FrameBuffer.ts +47 -0
  178. package/src/renderables/Input.test.ts +1310 -0
  179. package/src/renderables/Input.ts +273 -0
  180. package/src/renderables/LineNumberRenderable.ts +725 -0
  181. package/src/renderables/Markdown.ts +2144 -0
  182. package/src/renderables/ScrollBar.ts +423 -0
  183. package/src/renderables/ScrollBox.ts +903 -0
  184. package/src/renderables/Select.test.ts +1134 -0
  185. package/src/renderables/Select.ts +535 -0
  186. package/src/renderables/Slider.test.ts +456 -0
  187. package/src/renderables/Slider.ts +342 -0
  188. package/src/renderables/TabSelect.test.ts +238 -0
  189. package/src/renderables/TabSelect.ts +447 -0
  190. package/src/renderables/Text.selection-buffer.test.ts +118 -0
  191. package/src/renderables/Text.test.ts +2662 -0
  192. package/src/renderables/Text.ts +147 -0
  193. package/src/renderables/TextBufferRenderable.ts +504 -0
  194. package/src/renderables/TextNode.test.ts +1137 -0
  195. package/src/renderables/TextNode.ts +379 -0
  196. package/src/renderables/TextTable.test.ts +1411 -0
  197. package/src/renderables/TextTable.ts +1434 -0
  198. package/src/renderables/Textarea.ts +425 -0
  199. package/src/renderables/TimeToFirstDraw.ts +89 -0
  200. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  201. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  202. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  203. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  204. package/src/renderables/__tests__/Code.test.ts +55 -0
  205. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  206. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +827 -0
  207. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1906 -0
  208. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  209. package/src/renderables/__tests__/Markdown.code-colors.test.ts +288 -0
  210. package/src/renderables/__tests__/Markdown.test.ts +4175 -0
  211. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  212. package/src/renderables/__tests__/Textarea.buffer.test.ts +683 -0
  213. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  214. package/src/renderables/__tests__/Textarea.editing.test.ts +2120 -0
  215. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  216. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  217. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  218. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3275 -0
  219. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  220. package/src/renderables/__tests__/Textarea.rendering.test.ts +1927 -0
  221. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  222. package/src/renderables/__tests__/Textarea.selection.test.ts +1589 -0
  223. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  224. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  225. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  226. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  227. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  228. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  229. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  230. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  231. package/src/renderables/__tests__/markdown-parser.test.ts +241 -0
  232. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  233. package/src/renderables/composition/README.md +8 -0
  234. package/src/renderables/composition/VRenderable.ts +32 -0
  235. package/src/renderables/composition/constructs.ts +127 -0
  236. package/src/renderables/composition/vnode.ts +277 -0
  237. package/src/renderables/index.ts +23 -0
  238. package/src/renderables/markdown-parser.ts +79 -0
  239. package/src/renderer-theme-mode.ts +199 -0
  240. package/src/renderer.ts +4975 -0
  241. package/src/runtime-plugin-support-configure.ts +63 -0
  242. package/src/runtime-plugin-support.ts +13 -0
  243. package/src/runtime-plugin.ts +626 -0
  244. package/src/specs/terminal-startup.md +64 -0
  245. package/src/syntax-style.test.ts +841 -0
  246. package/src/syntax-style.ts +274 -0
  247. package/src/testing/README.md +219 -0
  248. package/src/testing/bun-test-node.ts +880 -0
  249. package/src/testing/capture-spans.test.ts +216 -0
  250. package/src/testing/integration.test.ts +276 -0
  251. package/src/testing/manual-clock.ts +117 -0
  252. package/src/testing/mock-keys.test.ts +1378 -0
  253. package/src/testing/mock-keys.ts +457 -0
  254. package/src/testing/mock-mouse.test.ts +218 -0
  255. package/src/testing/mock-mouse.ts +247 -0
  256. package/src/testing/mock-tree-sitter-client.ts +81 -0
  257. package/src/testing/spy.ts +13 -0
  258. package/src/testing/terminal-capabilities.ts +47 -0
  259. package/src/testing/test-recorder.test.ts +410 -0
  260. package/src/testing/test-recorder.ts +133 -0
  261. package/src/testing/test-renderer.ts +375 -0
  262. package/src/testing/test-renderer.wait.test.ts +278 -0
  263. package/src/testing/test-streams.ts +31 -0
  264. package/src/testing.ts +9 -0
  265. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  266. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  267. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  268. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  269. package/src/tests/allocator-stats.test.ts +53 -0
  270. package/src/tests/audio.test.ts +303 -0
  271. package/src/tests/destroy-during-render.test.ts +200 -0
  272. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  273. package/src/tests/destroy-on-exit.test.ts +62 -0
  274. package/src/tests/ffi-borrowed-pointer-callsites.test.ts +153 -0
  275. package/src/tests/hover-cursor.test.ts +98 -0
  276. package/src/tests/native-backed-measurement-lifecycle.test.ts +133 -0
  277. package/src/tests/native-backed-measurement-parity.test.ts +322 -0
  278. package/src/tests/native-span-feed-async.test.ts +173 -0
  279. package/src/tests/native-span-feed-close.test.ts +120 -0
  280. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  281. package/src/tests/native-span-feed-edge-cases.test.ts +384 -0
  282. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  283. package/src/tests/opacity.test.ts +123 -0
  284. package/src/tests/renderable.snapshot.test.ts +524 -0
  285. package/src/tests/renderable.test.ts +1851 -0
  286. package/src/tests/renderer.clock.test.ts +270 -0
  287. package/src/tests/renderer.console-startup.test.ts +2151 -0
  288. package/src/tests/renderer.control.test.ts +458 -0
  289. package/src/tests/renderer.core-slot-binding.test.ts +962 -0
  290. package/src/tests/renderer.cursor.test.ts +26 -0
  291. package/src/tests/renderer.custom-stdout.test.ts +1112 -0
  292. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  293. package/src/tests/renderer.focus-restore.test.ts +257 -0
  294. package/src/tests/renderer.focus.test.ts +329 -0
  295. package/src/tests/renderer.idle.test.ts +219 -0
  296. package/src/tests/renderer.input.test.ts +2615 -0
  297. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  298. package/src/tests/renderer.lifecycle.test.ts +34 -0
  299. package/src/tests/renderer.mouse.test.ts +1315 -0
  300. package/src/tests/renderer.notifications.test.ts +27 -0
  301. package/src/tests/renderer.palette.test.ts +1199 -0
  302. package/src/tests/renderer.remote.test.ts +247 -0
  303. package/src/tests/renderer.render-stats.test.ts +146 -0
  304. package/src/tests/renderer.scrollback-surface.test.ts +474 -0
  305. package/src/tests/renderer.selection.test.ts +138 -0
  306. package/src/tests/renderer.slot-registry.test.ts +684 -0
  307. package/src/tests/renderer.tracker.test.ts +202 -0
  308. package/src/tests/renderer.useMouse.test.ts +83 -0
  309. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  310. package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +48 -0
  311. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  312. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  313. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  314. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  315. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  316. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  317. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  318. package/src/tests/runtime-plugin-support-configure.fixture.ts +31 -0
  319. package/src/tests/runtime-plugin-support-late-addition.fixture.ts +17 -0
  320. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  321. package/src/tests/runtime-plugin-support.test.ts +54 -0
  322. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  323. package/src/tests/runtime-plugin.fixture.ts +40 -0
  324. package/src/tests/runtime-plugin.test.ts +369 -0
  325. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  326. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  327. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  328. package/src/tests/scrollbox.test.ts +1895 -0
  329. package/src/tests/wrap-resize-perf.test.ts +276 -0
  330. package/src/tests/yoga-api-coverage.test.ts +409 -0
  331. package/src/tests/yoga-callback-stress.test.ts +122 -0
  332. package/src/tests/yoga-ffi.test.ts +117 -0
  333. package/src/tests/yoga-setters.test.ts +921 -0
  334. package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +124 -0
  335. package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +27 -0
  336. package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +27 -0
  337. package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +27 -0
  338. package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +157 -0
  339. package/src/tests/yoga-upstream/YGErrataTest.test.ts +40 -0
  340. package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +23 -0
  341. package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +81 -0
  342. package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +31 -0
  343. package/src/tests/yoga-upstream/YGMeasureTest.test.ts +70 -0
  344. package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +1864 -0
  345. package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +5829 -0
  346. package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +2444 -0
  347. package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +293 -0
  348. package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +315 -0
  349. package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +231 -0
  350. package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +347 -0
  351. package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +256 -0
  352. package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +2747 -0
  353. package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +129 -0
  354. package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +109 -0
  355. package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +1097 -0
  356. package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +4562 -0
  357. package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +677 -0
  358. package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +2071 -0
  359. package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +3303 -0
  360. package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +582 -0
  361. package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +2150 -0
  362. package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +1963 -0
  363. package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +1446 -0
  364. package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +354 -0
  365. package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +1760 -0
  366. package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +1161 -0
  367. package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +207 -0
  368. package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +6338 -0
  369. package/src/tests/yoga-upstream/tools/MeasureCounter.ts +54 -0
  370. package/src/tests/yoga-upstream/tools/utils.ts +81 -0
  371. package/src/text-buffer-view.test.ts +722 -0
  372. package/src/text-buffer-view.ts +193 -0
  373. package/src/text-buffer.test.ts +512 -0
  374. package/src/text-buffer.ts +250 -0
  375. package/src/types.ts +199 -0
  376. package/src/utils.ts +88 -0
  377. package/src/yoga.ts +1146 -0
  378. package/src/zig/ansi.zig +502 -0
  379. package/src/zig/audio.zig +1206 -0
  380. package/src/zig/bench/README.md +50 -0
  381. package/src/zig/bench/buffer-color-blending_bench.zig +350 -0
  382. package/src/zig/bench/buffer-draw-box_bench.zig +786 -0
  383. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +894 -0
  384. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  385. package/src/zig/bench/editor-view_bench.zig +169 -0
  386. package/src/zig/bench/native-span-feed_bench.zig +107 -0
  387. package/src/zig/bench/rope-markers_bench.zig +713 -0
  388. package/src/zig/bench/rope_bench.zig +514 -0
  389. package/src/zig/bench/styled-text_bench.zig +476 -0
  390. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  391. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  392. package/src/zig/bench/text-chunk-graphemes_bench.zig +272 -0
  393. package/src/zig/bench/utf8_bench.zig +799 -0
  394. package/src/zig/bench-utils.zig +431 -0
  395. package/src/zig/bench.zig +223 -0
  396. package/src/zig/buffer-methods.zig +215 -0
  397. package/src/zig/buffer.zig +2515 -0
  398. package/src/zig/build.zig +700 -0
  399. package/src/zig/build.zig.zon +19 -0
  400. package/src/zig/edit-buffer.zig +900 -0
  401. package/src/zig/editor-view.zig +812 -0
  402. package/src/zig/event-bus.zig +28 -0
  403. package/src/zig/event-emitter.zig +65 -0
  404. package/src/zig/file-logger.zig +92 -0
  405. package/src/zig/grapheme.zig +838 -0
  406. package/src/zig/handles.zig +267 -0
  407. package/src/zig/lib.zig +2813 -0
  408. package/src/zig/link.zig +349 -0
  409. package/src/zig/logger.zig +43 -0
  410. package/src/zig/mem-registry.zig +126 -0
  411. package/src/zig/miniaudio_shim.c +21 -0
  412. package/src/zig/native-renderable.zig +112 -0
  413. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  414. package/src/zig/native-span-feed.zig +747 -0
  415. package/src/zig/renderer-output.zig +677 -0
  416. package/src/zig/renderer.zig +2106 -0
  417. package/src/zig/rope.zig +1220 -0
  418. package/src/zig/split-scrollback.zig +88 -0
  419. package/src/zig/syntax-style.zig +174 -0
  420. package/src/zig/terminal.zig +1652 -0
  421. package/src/zig/test.zig +82 -0
  422. package/src/zig/tests/README.md +18 -0
  423. package/src/zig/tests/ansi_test.zig +18 -0
  424. package/src/zig/tests/audio_test.zig +439 -0
  425. package/src/zig/tests/buffer-methods_test.zig +1111 -0
  426. package/src/zig/tests/buffer_test.zig +2926 -0
  427. package/src/zig/tests/edit-buffer-history_test.zig +299 -0
  428. package/src/zig/tests/edit-buffer_test.zig +1688 -0
  429. package/src/zig/tests/editor-view_test.zig +3454 -0
  430. package/src/zig/tests/event-emitter_test.zig +249 -0
  431. package/src/zig/tests/grapheme_test.zig +1329 -0
  432. package/src/zig/tests/handles_test.zig +123 -0
  433. package/src/zig/tests/link_test.zig +220 -0
  434. package/src/zig/tests/mem-registry_test.zig +473 -0
  435. package/src/zig/tests/memory_leak_regression_test.zig +234 -0
  436. package/src/zig/tests/native-renderable_test.zig +16 -0
  437. package/src/zig/tests/native-span-feed_test.zig +1266 -0
  438. package/src/zig/tests/renderer_test.zig +2604 -0
  439. package/src/zig/tests/rope-nested_test.zig +712 -0
  440. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  441. package/src/zig/tests/rope_test.zig +2362 -0
  442. package/src/zig/tests/segment-merge.test.zig +148 -0
  443. package/src/zig/tests/split-scrollback_test.zig +70 -0
  444. package/src/zig/tests/syntax-style_test.zig +558 -0
  445. package/src/zig/tests/terminal_test.zig +1489 -0
  446. package/src/zig/tests/test-renderer.zig +126 -0
  447. package/src/zig/tests/text-buffer-drawing_test.zig +3291 -0
  448. package/src/zig/tests/text-buffer-highlights_test.zig +706 -0
  449. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  450. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  451. package/src/zig/tests/text-buffer-selection_test.zig +1056 -0
  452. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  453. package/src/zig/tests/text-buffer-view_test.zig +3672 -0
  454. package/src/zig/tests/text-buffer_test.zig +2191 -0
  455. package/src/zig/tests/unicode-width-map.zon +3909 -0
  456. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  457. package/src/zig/tests/utf8_test.zig +4060 -0
  458. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  459. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  460. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  461. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  462. package/src/zig/tests/yoga_test.zig +65 -0
  463. package/src/zig/text-buffer-iterators.zig +496 -0
  464. package/src/zig/text-buffer-segment.zig +403 -0
  465. package/src/zig/text-buffer-view.zig +1428 -0
  466. package/src/zig/text-buffer.zig +1272 -0
  467. package/src/zig/utf8.zig +1954 -0
  468. package/src/zig/utils.zig +12 -0
  469. package/src/zig/uucode-stub.zig +76 -0
  470. package/src/zig/vendor/miniaudio/miniaudio.h +93468 -0
  471. package/src/zig/yoga.zig +658 -0
  472. package/src/zig-structs.ts +381 -0
  473. package/src/zig.ts +5260 -0
  474. package/tsconfig.build.json +17 -0
  475. package/tsconfig.json +27 -0
  476. package/tsconfig.node-test.json +138 -0
@@ -0,0 +1,178 @@
1
+ import type { TextRenderable } from "../renderables/Text.js"
2
+ import type { TextBuffer, TextChunk } from "../text-buffer.js"
3
+ import { createTextAttributes } from "../utils.js"
4
+ import { parseColor, type ColorInput } from "./RGBA.js"
5
+
6
+ const BrandedStyledText: unique symbol = Symbol.for("@opentui/core/StyledText")
7
+
8
+ export type Color = ColorInput
9
+
10
+ export interface StyleAttrs {
11
+ fg?: Color
12
+ bg?: Color
13
+ bold?: boolean
14
+ italic?: boolean
15
+ underline?: boolean
16
+ strikethrough?: boolean
17
+ dim?: boolean
18
+ reverse?: boolean
19
+ blink?: boolean
20
+ }
21
+
22
+ export function isStyledText(obj: any): obj is StyledText {
23
+ return obj && obj[BrandedStyledText]
24
+ }
25
+
26
+ export class StyledText {
27
+ [BrandedStyledText] = true
28
+
29
+ public chunks: TextChunk[]
30
+
31
+ constructor(chunks: TextChunk[]) {
32
+ this.chunks = chunks
33
+ }
34
+ }
35
+
36
+ export function stringToStyledText(content: string): StyledText {
37
+ const chunk = {
38
+ __isChunk: true as const,
39
+ text: content,
40
+ }
41
+ return new StyledText([chunk])
42
+ }
43
+
44
+ export type StylableInput = string | number | boolean | TextChunk
45
+
46
+ function applyStyle(input: StylableInput, style: StyleAttrs): TextChunk {
47
+ if (typeof input === "object" && "__isChunk" in input) {
48
+ const existingChunk = input as TextChunk
49
+
50
+ const fg = style.fg ? parseColor(style.fg) : existingChunk.fg
51
+ const bg = style.bg ? parseColor(style.bg) : existingChunk.bg
52
+
53
+ const newAttrs = createTextAttributes(style)
54
+ const mergedAttrs = existingChunk.attributes ? existingChunk.attributes | newAttrs : newAttrs
55
+
56
+ return {
57
+ __isChunk: true,
58
+ text: existingChunk.text,
59
+ fg,
60
+ bg,
61
+ attributes: mergedAttrs,
62
+ link: existingChunk.link,
63
+ }
64
+ } else {
65
+ const plainTextStr = String(input)
66
+ const fg = style.fg ? parseColor(style.fg) : undefined
67
+ const bg = style.bg ? parseColor(style.bg) : undefined
68
+ const attributes = createTextAttributes(style)
69
+
70
+ return {
71
+ __isChunk: true,
72
+ text: plainTextStr,
73
+ fg,
74
+ bg,
75
+ attributes,
76
+ }
77
+ }
78
+ }
79
+
80
+ // Color functions
81
+ export const black = (input: StylableInput): TextChunk => applyStyle(input, { fg: "black" })
82
+ export const red = (input: StylableInput): TextChunk => applyStyle(input, { fg: "red" })
83
+ export const green = (input: StylableInput): TextChunk => applyStyle(input, { fg: "green" })
84
+ export const yellow = (input: StylableInput): TextChunk => applyStyle(input, { fg: "yellow" })
85
+ export const blue = (input: StylableInput): TextChunk => applyStyle(input, { fg: "blue" })
86
+ export const magenta = (input: StylableInput): TextChunk => applyStyle(input, { fg: "magenta" })
87
+ export const cyan = (input: StylableInput): TextChunk => applyStyle(input, { fg: "cyan" })
88
+ export const white = (input: StylableInput): TextChunk => applyStyle(input, { fg: "white" })
89
+
90
+ // Bright color functions
91
+ export const brightBlack = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightBlack" })
92
+ export const brightRed = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightRed" })
93
+ export const brightGreen = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightGreen" })
94
+ export const brightYellow = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightYellow" })
95
+ export const brightBlue = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightBlue" })
96
+ export const brightMagenta = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightMagenta" })
97
+ export const brightCyan = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightCyan" })
98
+ export const brightWhite = (input: StylableInput): TextChunk => applyStyle(input, { fg: "brightWhite" })
99
+
100
+ // Background color functions
101
+ export const bgBlack = (input: StylableInput): TextChunk => applyStyle(input, { bg: "black" })
102
+ export const bgRed = (input: StylableInput): TextChunk => applyStyle(input, { bg: "red" })
103
+ export const bgGreen = (input: StylableInput): TextChunk => applyStyle(input, { bg: "green" })
104
+ export const bgYellow = (input: StylableInput): TextChunk => applyStyle(input, { bg: "yellow" })
105
+ export const bgBlue = (input: StylableInput): TextChunk => applyStyle(input, { bg: "blue" })
106
+ export const bgMagenta = (input: StylableInput): TextChunk => applyStyle(input, { bg: "magenta" })
107
+ export const bgCyan = (input: StylableInput): TextChunk => applyStyle(input, { bg: "cyan" })
108
+ export const bgWhite = (input: StylableInput): TextChunk => applyStyle(input, { bg: "white" })
109
+
110
+ // Style functions
111
+ export const bold = (input: StylableInput): TextChunk => applyStyle(input, { bold: true })
112
+ export const italic = (input: StylableInput): TextChunk => applyStyle(input, { italic: true })
113
+ export const underline = (input: StylableInput): TextChunk => applyStyle(input, { underline: true })
114
+ export const strikethrough = (input: StylableInput): TextChunk => applyStyle(input, { strikethrough: true })
115
+ export const dim = (input: StylableInput): TextChunk => applyStyle(input, { dim: true })
116
+ export const reverse = (input: StylableInput): TextChunk => applyStyle(input, { reverse: true })
117
+ export const blink = (input: StylableInput): TextChunk => applyStyle(input, { blink: true })
118
+
119
+ // Custom color functions
120
+ export const fg =
121
+ (color: Color) =>
122
+ (input: StylableInput): TextChunk =>
123
+ applyStyle(input, { fg: color })
124
+ export const bg =
125
+ (color: Color) =>
126
+ (input: StylableInput): TextChunk =>
127
+ applyStyle(input, { bg: color })
128
+
129
+ export const link =
130
+ (url: string) =>
131
+ (input: StylableInput): TextChunk => {
132
+ const chunk =
133
+ typeof input === "object" && "__isChunk" in input
134
+ ? (input as TextChunk)
135
+ : {
136
+ __isChunk: true as const,
137
+ text: String(input),
138
+ }
139
+
140
+ return {
141
+ ...chunk,
142
+ link: { url },
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Template literal handler for styled text (non-cached version).
148
+ * Returns a StyledText object containing chunks of text with optional styles.
149
+ */
150
+ export function t(strings: TemplateStringsArray, ...values: StylableInput[]): StyledText {
151
+ const chunks: TextChunk[] = []
152
+
153
+ for (let i = 0; i < strings.length; i++) {
154
+ const raw = strings[i]
155
+
156
+ if (raw) {
157
+ chunks.push({
158
+ __isChunk: true,
159
+ text: raw,
160
+ attributes: 0,
161
+ })
162
+ }
163
+
164
+ const val = values[i]
165
+ if (typeof val === "object" && "__isChunk" in val) {
166
+ chunks.push(val as TextChunk)
167
+ } else if (val !== undefined) {
168
+ const plainTextStr = String(val)
169
+ chunks.push({
170
+ __isChunk: true,
171
+ text: plainTextStr,
172
+ attributes: 0,
173
+ })
174
+ }
175
+ }
176
+
177
+ return new StyledText(chunks)
178
+ }
@@ -0,0 +1,226 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import {
3
+ isCapabilityResponse,
4
+ isPixelResolutionResponse,
5
+ parsePixelResolution,
6
+ } from "./terminal-capability-detection.js"
7
+
8
+ describe("isCapabilityResponse", () => {
9
+ test("detects DECRPM responses", () => {
10
+ expect(isCapabilityResponse("\x1b[?1016;2$y")).toBe(true)
11
+ expect(isCapabilityResponse("\x1b[?2027;0$y")).toBe(true)
12
+ expect(isCapabilityResponse("\x1b[?2031;2$y")).toBe(true)
13
+ expect(isCapabilityResponse("\x1b[?1004;1$y")).toBe(true)
14
+ expect(isCapabilityResponse("\x1b[?2026;2$y")).toBe(true)
15
+ expect(isCapabilityResponse("\x1b[?2004;2$y")).toBe(true)
16
+ })
17
+
18
+ test("detects CPR responses for width detection", () => {
19
+ expect(isCapabilityResponse("\x1b[1;2R")).toBe(true) // explicit width
20
+ expect(isCapabilityResponse("\x1b[1;3R")).toBe(true) // scaled text
21
+ })
22
+
23
+ test("does not detect regular CPR responses as capabilities", () => {
24
+ // Regular cursor position reports are NOT capabilities
25
+ expect(isCapabilityResponse("\x1b[10;5R")).toBe(false)
26
+ expect(isCapabilityResponse("\x1b[20;30R")).toBe(false)
27
+ })
28
+
29
+ test("detects XTVersion responses", () => {
30
+ expect(isCapabilityResponse("\x1bP>|kitty(0.40.1)\x1b\\")).toBe(true)
31
+ expect(isCapabilityResponse("\x1bP>|ghostty 1.1.3\x1b\\")).toBe(true)
32
+ expect(isCapabilityResponse("\x1bP>|tmux 3.5a\x1b\\")).toBe(true)
33
+ })
34
+
35
+ test("detects XTGETTCAP Ms responses", () => {
36
+ expect(isCapabilityResponse("\x1bP1+r4d73=2570312573\x1b\\")).toBe(true)
37
+ expect(isCapabilityResponse("\x1bP1+r4d73\x1b\\")).toBe(true)
38
+ expect(isCapabilityResponse("\x1bP1+r4d73=\x1b\\")).toBe(true)
39
+ expect(isCapabilityResponse("\x1bP0+r\x1b\\")).toBe(true)
40
+ expect(isCapabilityResponse("\x1bP0+r4D73\x1b\\")).toBe(true)
41
+ expect(isCapabilityResponse("\x1bP1+r544e=787465726d\x1b\\")).toBe(false)
42
+ expect(isCapabilityResponse("\x1bP1+r4d73=abc\x1b\\")).toBe(true)
43
+ expect(isCapabilityResponse("\x1bP1+r4d73=zz\x1b\\")).toBe(true)
44
+ })
45
+
46
+ test("detects Kitty graphics responses", () => {
47
+ expect(isCapabilityResponse("\x1b_Gi=1;OK\x1b\\")).toBe(true)
48
+ expect(isCapabilityResponse("\x1b_Gi=1;EINVAL:Zero width/height not allowed\x1b\\")).toBe(true)
49
+ })
50
+
51
+ test("detects DA1 (Device Attributes) responses", () => {
52
+ expect(isCapabilityResponse("\x1b[?62;c")).toBe(true)
53
+ expect(isCapabilityResponse("\x1b[?62;22c")).toBe(true)
54
+ expect(isCapabilityResponse("\x1b[?1;2;4c")).toBe(true)
55
+ expect(isCapabilityResponse("\x1b[?6c")).toBe(true)
56
+ })
57
+
58
+ test("detects Kitty keyboard query responses", () => {
59
+ expect(isCapabilityResponse("\x1b[?0u")).toBe(true)
60
+ expect(isCapabilityResponse("\x1b[?1u")).toBe(true)
61
+ expect(isCapabilityResponse("\x1b[?31u")).toBe(true)
62
+ })
63
+
64
+ test("detects notification capability responses", () => {
65
+ expect(isCapabilityResponse("\x1b]99;i=opentui-notifications:p=?;p=title,body\x1b\\")).toBe(true)
66
+ expect(isCapabilityResponse("\x1b]99;i=opentui-notifications:p=?;p=title,body\x07")).toBe(true)
67
+ expect(isCapabilityResponse("\x1b]1337;Capabilities=T2NoH\x1b\\")).toBe(true)
68
+ })
69
+
70
+ test("does not detect regular keypresses", () => {
71
+ expect(isCapabilityResponse("a")).toBe(false)
72
+ expect(isCapabilityResponse("A")).toBe(false)
73
+ expect(isCapabilityResponse("\x1b")).toBe(false)
74
+ expect(isCapabilityResponse("\x1ba")).toBe(false)
75
+ })
76
+
77
+ test("does not detect arrow keys", () => {
78
+ expect(isCapabilityResponse("\x1b[A")).toBe(false)
79
+ expect(isCapabilityResponse("\x1b[B")).toBe(false)
80
+ expect(isCapabilityResponse("\x1b[C")).toBe(false)
81
+ expect(isCapabilityResponse("\x1b[D")).toBe(false)
82
+ })
83
+
84
+ test("does not detect function keys", () => {
85
+ expect(isCapabilityResponse("\x1bOP")).toBe(false)
86
+ expect(isCapabilityResponse("\x1b[11~")).toBe(false)
87
+ expect(isCapabilityResponse("\x1b[24~")).toBe(false)
88
+ })
89
+
90
+ test("does not detect modified arrow keys", () => {
91
+ expect(isCapabilityResponse("\x1b[1;2A")).toBe(false)
92
+ expect(isCapabilityResponse("\x1b[1;5C")).toBe(false)
93
+ })
94
+
95
+ test("does not detect mouse sequences", () => {
96
+ expect(isCapabilityResponse("\x1b[<35;20;5m")).toBe(false)
97
+ expect(isCapabilityResponse("\x1b[<0;10;10M")).toBe(false)
98
+ })
99
+
100
+ test("does not detect arbitrary OSC sequences as capabilities", () => {
101
+ expect(isCapabilityResponse("\x1b]9;hello\x1b\\")).toBe(false)
102
+ expect(isCapabilityResponse("\x1b]777;notify;title;body\x1b\\")).toBe(false)
103
+ expect(isCapabilityResponse("\x1b]99;i=other:p=?;p=title\x1b\\")).toBe(false)
104
+ })
105
+ })
106
+
107
+ describe("isPixelResolutionResponse", () => {
108
+ test("detects pixel resolution responses", () => {
109
+ expect(isPixelResolutionResponse("\x1b[4;720;1280t")).toBe(true)
110
+ expect(isPixelResolutionResponse("\x1b[4;1080;1920t")).toBe(true)
111
+ expect(isPixelResolutionResponse("\x1b[4;0;0t")).toBe(true)
112
+ })
113
+
114
+ test("does not detect other sequences", () => {
115
+ expect(isPixelResolutionResponse("a")).toBe(false)
116
+ expect(isPixelResolutionResponse("\x1b[A")).toBe(false)
117
+ expect(isPixelResolutionResponse("\x1b[?1016;2$y")).toBe(false)
118
+ })
119
+ })
120
+
121
+ describe("parsePixelResolution", () => {
122
+ test("parses valid pixel resolution responses", () => {
123
+ expect(parsePixelResolution("\x1b[4;720;1280t")).toEqual({ width: 1280, height: 720 })
124
+ expect(parsePixelResolution("\x1b[4;1080;1920t")).toEqual({ width: 1920, height: 1080 })
125
+ expect(parsePixelResolution("\x1b[4;0;0t")).toEqual({ width: 0, height: 0 })
126
+ })
127
+
128
+ test("returns null for invalid sequences", () => {
129
+ expect(parsePixelResolution("a")).toBeNull()
130
+ expect(parsePixelResolution("\x1b[A")).toBeNull()
131
+ expect(parsePixelResolution("\x1b[?1016;2$y")).toBeNull()
132
+ })
133
+ })
134
+
135
+ describe("real-world terminal capability sequences", () => {
136
+ test("kitty terminal full response - individual sequences", () => {
137
+ // Should detect multiple capability sequences
138
+ expect(isCapabilityResponse("\x1b[?1016;2$y")).toBe(true)
139
+ expect(isCapabilityResponse("\x1b[?2027;0$y")).toBe(true)
140
+ expect(isCapabilityResponse("\x1b[1;2R")).toBe(true)
141
+ expect(isCapabilityResponse("\x1b[1;3R")).toBe(true)
142
+ expect(isCapabilityResponse("\x1bP>|kitty(0.40.1)\x1b\\")).toBe(true)
143
+ expect(isCapabilityResponse("\x1b_Gi=1;EINVAL:Zero width/height not allowed\x1b\\")).toBe(true)
144
+ expect(isCapabilityResponse("\x1b[?62;c")).toBe(true)
145
+ })
146
+
147
+ test("ghostty terminal response - individual sequences", () => {
148
+ expect(isCapabilityResponse("\x1bP>|ghostty 1.1.3\x1b\\")).toBe(true)
149
+ expect(isCapabilityResponse("\x1b_Gi=1;OK\x1b\\")).toBe(true)
150
+ expect(isCapabilityResponse("\x1b[?62;22c")).toBe(true)
151
+ })
152
+
153
+ test("alacritty terminal response - individual sequences", () => {
154
+ expect(isCapabilityResponse("\x1b[?1016;0$y")).toBe(true)
155
+ expect(isCapabilityResponse("\x1b[?6c")).toBe(true)
156
+ })
157
+
158
+ test("vscode terminal minimal response", () => {
159
+ expect(isCapabilityResponse("\x1b[?1016;2$y")).toBe(true)
160
+ })
161
+ })
162
+
163
+ describe("renderer capabilities event", () => {
164
+ /**
165
+ * The renderer emits "capabilities" event each time a capability response is processed.
166
+ * This happens multiple times at startup because the terminal responds to multiple queries:
167
+ * - DECRPM queries (sgr_pixels, unicode, color_scheme, focus, bracketed_paste, sync)
168
+ * - CPR queries for width detection (explicit_width, scaled_text)
169
+ * - XTVersion (terminal name/version, kitty detection)
170
+ * - Kitty keyboard query response
171
+ *
172
+ * Each response arrives async and triggers the event, so consumers should expect
173
+ * multiple emissions and handle them reactively.
174
+ */
175
+ test("kitty terminal emits capabilities event for each response", async () => {
176
+ const { createTestRenderer } = await import("../testing/test-renderer.js")
177
+ const { renderer } = await createTestRenderer({})
178
+
179
+ const events: any[] = []
180
+ renderer.on("capabilities", (caps) => events.push({ ...caps }))
181
+
182
+ // Simulate all 10 Kitty capability responses (as they arrive separately)
183
+ const kittyResponses = [
184
+ "\x1b[?1016;2$y", // 1. sgr_pixels
185
+ "\x1b[?2027;0$y", // 2. unicode query
186
+ "\x1b[?2031;2$y", // 3. color_scheme_updates
187
+ "\x1b[?1004;2$y", // 4. focus_tracking
188
+ "\x1b[?2004;2$y", // 5. bracketed_paste
189
+ "\x1b[?2026;2$y", // 6. sync
190
+ "\x1b[1;2R", // 7. explicit_width (CPR)
191
+ "\x1b[1;3R", // 8. scaled_text (CPR)
192
+ "\x1bP>|kitty(0.42.2)\x1b\\", // 9. xtversion (triggers kitty detection)
193
+ "\x1b[?0u", // 10. kitty keyboard query
194
+ ]
195
+
196
+ for (const response of kittyResponses) {
197
+ renderer.stdin.emit("data", Buffer.from(response))
198
+ await new Promise((resolve) => setTimeout(resolve, 10))
199
+ }
200
+
201
+ // Should have received 10 capability events
202
+ expect(events.length).toBe(10)
203
+
204
+ // First event: sgr_pixels detected
205
+ expect(events[0].sgr_pixels).toBe(true)
206
+
207
+ // After xtversion (event 9): kitty_keyboard should be true
208
+ expect(events[8].kitty_keyboard).toBe(true)
209
+ expect(events[8].kitty_graphics).toBe(true)
210
+ expect(events[8].notifications).toBe(true)
211
+ expect(events[8].terminal.name).toBe("kitty")
212
+ expect(events[8].terminal.version).toBe("0.42.2")
213
+
214
+ // Final state should have all kitty capabilities
215
+ const finalCaps = events[9]
216
+ expect(finalCaps.kitty_keyboard).toBe(true)
217
+ expect(finalCaps.sgr_pixels).toBe(true)
218
+ expect(finalCaps.color_scheme_updates).toBe(true)
219
+ expect(finalCaps.focus_tracking).toBe(true)
220
+ expect(finalCaps.sync).toBe(true)
221
+ expect(finalCaps.explicit_width).toBe(true)
222
+ expect(finalCaps.scaled_text).toBe(true)
223
+
224
+ renderer.destroy()
225
+ })
226
+ })
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Terminal capability response detection utilities.
3
+ *
4
+ * Detects various terminal capability response sequences:
5
+ * - DECRPM (DEC Request Mode): ESC[?...;N$y where N is 0,1,2,3,4
6
+ * - CPR (Cursor Position Report): ESC[row;colR (used for width detection)
7
+ * - XTVersion: ESC P >| ... ESC \
8
+ * - Kitty Graphics: ESC _ G ... ESC \
9
+ * - Kitty Keyboard Query: ESC[?Nu where N is 0,1,2,etc
10
+ * - DA1 (Device Attributes): ESC[?...c
11
+ * - Pixel Resolution: ESC[4;height;widtht
12
+ * - OSC 99 notification capability query response
13
+ * - iTerm2 OSC 1337 feature-reporting response
14
+ */
15
+
16
+ /**
17
+ * Check if a sequence is a terminal capability response.
18
+ * Returns true if the sequence matches any known capability response pattern.
19
+ */
20
+ export function isCapabilityResponse(sequence: string): boolean {
21
+ // DECRPM: ESC[?digits;digits$y
22
+ if (/\x1b\[\?\d+(?:;\d+)*\$y/.test(sequence)) {
23
+ return true
24
+ }
25
+
26
+ // CPR for explicit width/scaled text detection: ESC[1;NR where N >= 2
27
+ // The column number tells us how many characters were rendered with width annotations
28
+ // ESC[1;1R means no width support (cursor didn't move)
29
+ // ESC[1;2R or higher means width support (cursor moved after rendering)
30
+ // We accept any column >= 2 to handle cases where cursor wasn't at exact home position
31
+ if (/\x1b\[1;(?!1R)\d+R/.test(sequence)) {
32
+ return true
33
+ }
34
+
35
+ // XTVersion: ESC P >| ... ESC \
36
+ if (/\x1bP>\|[\s\S]*?\x1b\\/.test(sequence)) {
37
+ return true
38
+ }
39
+
40
+ // XTGETTCAP Ms: consume replies to our query here; native parsing separately
41
+ // validates whether a positive value is sufficient evidence of support.
42
+ if (/\x1bP(?:1\+r4d73(?:=[^\x1b]*)?|0\+r(?:4d73)?)\x1b\\/i.test(sequence)) {
43
+ return true
44
+ }
45
+
46
+ // Kitty graphics response: ESC _ G ... ESC \
47
+ // Matches any graphics response including OK, errors, etc.
48
+ // This is for filtering capability responses from user input
49
+ if (/\x1b_G[\s\S]*?\x1b\\/.test(sequence)) {
50
+ return true
51
+ }
52
+
53
+ // Kitty keyboard query response: ESC[?Nu or ESC[?N;Mu (progressive enhancement)
54
+ if (/\x1b\[\?\d+(?:;\d+)?u/.test(sequence)) {
55
+ return true
56
+ }
57
+
58
+ // DA1 (Device Attributes): ESC[?...c
59
+ if (/\x1b\[\?[0-9;]*c/.test(sequence)) {
60
+ return true
61
+ }
62
+
63
+ // Kitty desktop notification capability query response.
64
+ if (/\x1b\]99;[^\x07\x1b]*i=opentui-notifications[^\x07\x1b]*p=\?[\s\S]*?(?:\x07|\x1b\\)/.test(sequence)) {
65
+ return true
66
+ }
67
+
68
+ // iTerm2 feature reporting response. The native parser decides whether the
69
+ // feature string contains the Notifications feature code.
70
+ if (/\x1b\]1337;Capabilities=[\s\S]*?(?:\x07|\x1b\\)/.test(sequence)) {
71
+ return true
72
+ }
73
+
74
+ return false
75
+ }
76
+
77
+ /**
78
+ * Check if a sequence is a pixel resolution response.
79
+ * Format: ESC[4;height;widtht
80
+ */
81
+ export function isPixelResolutionResponse(sequence: string): boolean {
82
+ return /\x1b\[4;\d+;\d+t/.test(sequence)
83
+ }
84
+
85
+ /**
86
+ * Parse pixel resolution from response sequence.
87
+ * Returns { width, height } or null if not a valid resolution response.
88
+ */
89
+ export function parsePixelResolution(sequence: string): { width: number; height: number } | null {
90
+ const match = sequence.match(/\x1b\[4;(\d+);(\d+)t/)
91
+ if (match) {
92
+ return {
93
+ width: parseInt(match[2]),
94
+ height: parseInt(match[1]),
95
+ }
96
+ }
97
+ return null
98
+ }