@fairyhunter13/opentui-core 0.1.91 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/3d/SpriteResourceManager.d.ts +74 -0
  2. package/3d/SpriteUtils.d.ts +13 -0
  3. package/3d/TextureUtils.d.ts +24 -0
  4. package/3d/ThreeRenderable.d.ts +40 -0
  5. package/3d/WGPURenderer.d.ts +61 -0
  6. package/3d/animation/ExplodingSpriteEffect.d.ts +71 -0
  7. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +76 -0
  8. package/3d/animation/SpriteAnimator.d.ts +124 -0
  9. package/3d/animation/SpriteParticleGenerator.d.ts +62 -0
  10. package/3d/canvas.d.ts +44 -0
  11. package/3d/index.d.ts +12 -0
  12. package/3d/physics/PlanckPhysicsAdapter.d.ts +19 -0
  13. package/3d/physics/RapierPhysicsAdapter.d.ts +19 -0
  14. package/3d/physics/physics-interface.d.ts +27 -0
  15. package/3d.d.ts +2 -0
  16. package/3d.js +34042 -0
  17. package/3d.js.map +155 -0
  18. package/LICENSE +21 -0
  19. package/NativeSpanFeed.d.ts +41 -0
  20. package/Renderable.d.ts +334 -0
  21. package/animation/Timeline.d.ts +126 -0
  22. package/ansi.d.ts +13 -0
  23. package/buffer.d.ts +107 -0
  24. package/console.d.ts +143 -0
  25. package/edit-buffer.d.ts +98 -0
  26. package/editor-view.d.ts +73 -0
  27. package/index-e6ec7apq.js +18415 -0
  28. package/index-e6ec7apq.js.map +64 -0
  29. package/index-h066zmrb.js +12619 -0
  30. package/index-h066zmrb.js.map +43 -0
  31. package/index-ynzawt3n.js +113 -0
  32. package/index-ynzawt3n.js.map +10 -0
  33. package/index.d.ts +21 -0
  34. package/index.js +430 -0
  35. package/index.js.map +9 -0
  36. package/lib/KeyHandler.d.ts +61 -0
  37. package/lib/RGBA.d.ts +25 -0
  38. package/lib/ascii.font.d.ts +508 -0
  39. package/lib/border.d.ts +49 -0
  40. package/lib/bunfs.d.ts +7 -0
  41. package/lib/clipboard.d.ts +17 -0
  42. package/lib/clock.d.ts +15 -0
  43. package/lib/data-paths.d.ts +26 -0
  44. package/lib/debounce.d.ts +42 -0
  45. package/lib/detect-links.d.ts +6 -0
  46. package/lib/env.d.ts +42 -0
  47. package/lib/extmarks-history.d.ts +17 -0
  48. package/lib/extmarks.d.ts +89 -0
  49. package/lib/hast-styled-text.d.ts +17 -0
  50. package/lib/index.d.ts +21 -0
  51. package/lib/keymapping.d.ts +25 -0
  52. package/lib/objects-in-viewport.d.ts +24 -0
  53. package/lib/output.capture.d.ts +24 -0
  54. package/lib/parse.keypress-kitty.d.ts +2 -0
  55. package/lib/parse.keypress.d.ts +26 -0
  56. package/lib/parse.mouse.d.ts +30 -0
  57. package/lib/paste.d.ts +7 -0
  58. package/lib/queue.d.ts +15 -0
  59. package/lib/renderable.validations.d.ts +12 -0
  60. package/lib/scroll-acceleration.d.ts +43 -0
  61. package/lib/selection.d.ts +63 -0
  62. package/lib/singleton.d.ts +7 -0
  63. package/lib/stdin-parser.d.ts +76 -0
  64. package/lib/styled-text.d.ts +63 -0
  65. package/lib/terminal-capability-detection.d.ts +30 -0
  66. package/lib/terminal-palette.d.ts +50 -0
  67. package/lib/tree-sitter/assets/update.d.ts +11 -0
  68. package/lib/tree-sitter/client.d.ts +47 -0
  69. package/lib/tree-sitter/default-parsers.d.ts +2 -0
  70. package/lib/tree-sitter/download-utils.d.ts +21 -0
  71. package/lib/tree-sitter/index.d.ts +8 -0
  72. package/lib/tree-sitter/parser.worker.d.ts +1 -0
  73. package/lib/tree-sitter/parsers-config.d.ts +38 -0
  74. package/lib/tree-sitter/resolve-ft.d.ts +2 -0
  75. package/lib/tree-sitter/types.d.ts +81 -0
  76. package/lib/tree-sitter-styled-text.d.ts +14 -0
  77. package/lib/validate-dir-name.d.ts +1 -0
  78. package/lib/yoga.options.d.ts +32 -0
  79. package/package.json +51 -63
  80. package/parser.worker.js +869 -0
  81. package/parser.worker.js.map +12 -0
  82. package/plugins/core-slot.d.ts +72 -0
  83. package/plugins/registry.d.ts +38 -0
  84. package/plugins/types.d.ts +34 -0
  85. package/post/filters.d.ts +105 -0
  86. package/renderables/ASCIIFont.d.ts +52 -0
  87. package/renderables/Box.d.ts +72 -0
  88. package/renderables/Code.d.ts +78 -0
  89. package/renderables/Diff.d.ts +142 -0
  90. package/renderables/EditBufferRenderable.d.ts +162 -0
  91. package/renderables/FrameBuffer.d.ts +16 -0
  92. package/renderables/Input.d.ts +67 -0
  93. package/renderables/LineNumberRenderable.d.ts +74 -0
  94. package/renderables/Markdown.d.ts +173 -0
  95. package/renderables/ScrollBar.d.ts +77 -0
  96. package/renderables/ScrollBox.d.ts +124 -0
  97. package/renderables/Select.d.ts +115 -0
  98. package/renderables/Slider.d.ts +44 -0
  99. package/renderables/TabSelect.d.ts +96 -0
  100. package/renderables/Text.d.ts +36 -0
  101. package/renderables/TextBufferRenderable.d.ts +105 -0
  102. package/renderables/TextNode.d.ts +91 -0
  103. package/renderables/TextTable.d.ts +140 -0
  104. package/renderables/Textarea.d.ts +114 -0
  105. package/renderables/TimeToFirstDraw.d.ts +24 -0
  106. package/renderables/__tests__/renderable-test-utils.d.ts +12 -0
  107. package/renderables/composition/VRenderable.d.ts +16 -0
  108. package/renderables/composition/constructs.d.ts +35 -0
  109. package/renderables/composition/vnode.d.ts +46 -0
  110. package/renderables/index.d.ts +22 -0
  111. package/renderables/markdown-parser.d.ts +10 -0
  112. package/renderer.d.ts +388 -0
  113. package/runtime-plugin-support.d.ts +3 -0
  114. package/runtime-plugin-support.js +29 -0
  115. package/runtime-plugin-support.js.map +10 -0
  116. package/runtime-plugin.d.ts +11 -0
  117. package/runtime-plugin.js +16 -0
  118. package/runtime-plugin.js.map +9 -0
  119. package/syntax-style.d.ts +54 -0
  120. package/testing/manual-clock.d.ts +16 -0
  121. package/testing/mock-keys.d.ts +81 -0
  122. package/testing/mock-mouse.d.ts +38 -0
  123. package/testing/mock-tree-sitter-client.d.ts +23 -0
  124. package/testing/spy.d.ts +7 -0
  125. package/testing/test-recorder.d.ts +61 -0
  126. package/testing/test-renderer.d.ts +23 -0
  127. package/testing.d.ts +6 -0
  128. package/testing.js +675 -0
  129. package/testing.js.map +15 -0
  130. package/text-buffer-view.d.ts +42 -0
  131. package/text-buffer.d.ts +67 -0
  132. package/types.d.ts +131 -0
  133. package/utils.d.ts +14 -0
  134. package/zig-structs.d.ts +155 -0
  135. package/zig.d.ts +351 -0
  136. package/dev/keypress-debug-renderer.ts +0 -148
  137. package/dev/keypress-debug.ts +0 -43
  138. package/dev/print-env-vars.ts +0 -32
  139. package/dev/test-tmux-graphics-334.sh +0 -68
  140. package/dev/thai-debug-test.ts +0 -68
  141. package/docs/development.md +0 -141
  142. package/docs/env-vars.md +0 -140
  143. package/docs/getting-started.md +0 -353
  144. package/docs/renderables-vs-constructs.md +0 -159
  145. package/docs/tree-sitter.md +0 -311
  146. package/scripts/build.ts +0 -400
  147. package/scripts/publish.ts +0 -60
  148. package/src/3d/SpriteResourceManager.ts +0 -286
  149. package/src/3d/SpriteUtils.ts +0 -71
  150. package/src/3d/TextureUtils.ts +0 -196
  151. package/src/3d/ThreeRenderable.ts +0 -197
  152. package/src/3d/WGPURenderer.ts +0 -294
  153. package/src/3d/animation/ExplodingSpriteEffect.ts +0 -513
  154. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +0 -429
  155. package/src/3d/animation/SpriteAnimator.ts +0 -633
  156. package/src/3d/animation/SpriteParticleGenerator.ts +0 -435
  157. package/src/3d/canvas.ts +0 -464
  158. package/src/3d/index.ts +0 -12
  159. package/src/3d/physics/PlanckPhysicsAdapter.ts +0 -72
  160. package/src/3d/physics/RapierPhysicsAdapter.ts +0 -66
  161. package/src/3d/physics/physics-interface.ts +0 -31
  162. package/src/3d/shaders/supersampling.wgsl +0 -201
  163. package/src/3d.ts +0 -3
  164. package/src/NativeSpanFeed.ts +0 -300
  165. package/src/Renderable.ts +0 -1698
  166. package/src/__snapshots__/buffer.test.ts.snap +0 -28
  167. package/src/animation/Timeline.test.ts +0 -2709
  168. package/src/animation/Timeline.ts +0 -598
  169. package/src/ansi.ts +0 -18
  170. package/src/benchmark/latest-all-bench-run.json +0 -707
  171. package/src/benchmark/latest-async-bench-run.json +0 -336
  172. package/src/benchmark/latest-default-bench-run.json +0 -657
  173. package/src/benchmark/latest-large-bench-run.json +0 -707
  174. package/src/benchmark/latest-quick-bench-run.json +0 -207
  175. package/src/benchmark/markdown-benchmark.ts +0 -1804
  176. package/src/benchmark/native-span-feed-async-benchmark.ts +0 -355
  177. package/src/benchmark/native-span-feed-benchmark.md +0 -56
  178. package/src/benchmark/native-span-feed-benchmark.ts +0 -596
  179. package/src/benchmark/native-span-feed-compare.ts +0 -280
  180. package/src/benchmark/renderer-benchmark.ts +0 -754
  181. package/src/benchmark/text-table-benchmark.ts +0 -947
  182. package/src/buffer.test.ts +0 -291
  183. package/src/buffer.ts +0 -519
  184. package/src/console.test.ts +0 -612
  185. package/src/console.ts +0 -1255
  186. package/src/edit-buffer.test.ts +0 -1769
  187. package/src/edit-buffer.ts +0 -411
  188. package/src/editor-view.test.ts +0 -1032
  189. package/src/editor-view.ts +0 -284
  190. package/src/examples/ascii-font-selection-demo.ts +0 -245
  191. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  192. package/src/examples/assets/concrete.png +0 -0
  193. package/src/examples/assets/crate.png +0 -0
  194. package/src/examples/assets/crate_emissive.png +0 -0
  195. package/src/examples/assets/forrest_background.png +0 -0
  196. package/src/examples/assets/hast-example.json +0 -1018
  197. package/src/examples/assets/heart.png +0 -0
  198. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  199. package/src/examples/assets/main_char_idle.png +0 -0
  200. package/src/examples/assets/main_char_jump_end.png +0 -0
  201. package/src/examples/assets/main_char_jump_landing.png +0 -0
  202. package/src/examples/assets/main_char_jump_start.png +0 -0
  203. package/src/examples/assets/main_char_run_loop.png +0 -0
  204. package/src/examples/assets/roughness_map.jpg +0 -0
  205. package/src/examples/build.ts +0 -115
  206. package/src/examples/code-demo.ts +0 -584
  207. package/src/examples/console-demo.ts +0 -358
  208. package/src/examples/core-plugin-slots-demo.ts +0 -759
  209. package/src/examples/diff-demo.ts +0 -699
  210. package/src/examples/draggable-three-demo.ts +0 -259
  211. package/src/examples/editor-demo.ts +0 -322
  212. package/src/examples/extmarks-demo.ts +0 -204
  213. package/src/examples/focus-restore-demo.ts +0 -310
  214. package/src/examples/fonts.ts +0 -245
  215. package/src/examples/fractal-shader-demo.ts +0 -268
  216. package/src/examples/framebuffer-demo.ts +0 -674
  217. package/src/examples/full-unicode-demo.ts +0 -181
  218. package/src/examples/golden-star-demo.ts +0 -933
  219. package/src/examples/grayscale-buffer-demo.ts +0 -249
  220. package/src/examples/hast-syntax-highlighting-demo.ts +0 -129
  221. package/src/examples/index.ts +0 -925
  222. package/src/examples/input-demo.ts +0 -377
  223. package/src/examples/input-select-layout-demo.ts +0 -425
  224. package/src/examples/install.sh +0 -143
  225. package/src/examples/keypress-debug-demo.ts +0 -452
  226. package/src/examples/lib/HexList.ts +0 -122
  227. package/src/examples/lib/PaletteGrid.ts +0 -125
  228. package/src/examples/lib/standalone-keys.ts +0 -25
  229. package/src/examples/lib/tab-controller.ts +0 -243
  230. package/src/examples/lights-phong-demo.ts +0 -290
  231. package/src/examples/link-demo.ts +0 -220
  232. package/src/examples/live-state-demo.ts +0 -480
  233. package/src/examples/markdown-demo.ts +0 -620
  234. package/src/examples/mouse-interaction-demo.ts +0 -428
  235. package/src/examples/nested-zindex-demo.ts +0 -357
  236. package/src/examples/opacity-example.ts +0 -235
  237. package/src/examples/opentui-demo.ts +0 -1057
  238. package/src/examples/physx-planck-2d-demo.ts +0 -507
  239. package/src/examples/physx-rapier-2d-demo.ts +0 -526
  240. package/src/examples/relative-positioning-demo.ts +0 -323
  241. package/src/examples/scroll-example.ts +0 -214
  242. package/src/examples/scrollbox-mouse-test.ts +0 -112
  243. package/src/examples/scrollbox-overlay-hit-test.ts +0 -206
  244. package/src/examples/select-demo.ts +0 -237
  245. package/src/examples/shader-cube-demo.ts +0 -772
  246. package/src/examples/simple-layout-example.ts +0 -591
  247. package/src/examples/slider-demo.ts +0 -617
  248. package/src/examples/split-mode-demo.ts +0 -445
  249. package/src/examples/sprite-animation-demo.ts +0 -443
  250. package/src/examples/sprite-particle-generator-demo.ts +0 -486
  251. package/src/examples/static-sprite-demo.ts +0 -193
  252. package/src/examples/sticky-scroll-example.ts +0 -308
  253. package/src/examples/styled-text-demo.ts +0 -282
  254. package/src/examples/tab-select-demo.ts +0 -219
  255. package/src/examples/terminal-title.ts +0 -29
  256. package/src/examples/terminal.ts +0 -305
  257. package/src/examples/text-node-demo.ts +0 -416
  258. package/src/examples/text-selection-demo.ts +0 -377
  259. package/src/examples/text-table-demo.ts +0 -503
  260. package/src/examples/text-truncation-demo.ts +0 -481
  261. package/src/examples/text-wrap.ts +0 -757
  262. package/src/examples/texture-loading-demo.ts +0 -259
  263. package/src/examples/timeline-example.ts +0 -670
  264. package/src/examples/transparency-demo.ts +0 -241
  265. package/src/examples/vnode-composition-demo.ts +0 -404
  266. package/src/index.ts +0 -22
  267. package/src/lib/KeyHandler.integration.test.ts +0 -292
  268. package/src/lib/KeyHandler.stopPropagation.test.ts +0 -289
  269. package/src/lib/KeyHandler.test.ts +0 -662
  270. package/src/lib/KeyHandler.ts +0 -222
  271. package/src/lib/RGBA.test.ts +0 -984
  272. package/src/lib/RGBA.ts +0 -204
  273. package/src/lib/ascii.font.ts +0 -330
  274. package/src/lib/border.test.ts +0 -83
  275. package/src/lib/border.ts +0 -168
  276. package/src/lib/bunfs.test.ts +0 -27
  277. package/src/lib/bunfs.ts +0 -18
  278. package/src/lib/clipboard.test.ts +0 -41
  279. package/src/lib/clipboard.ts +0 -47
  280. package/src/lib/clock.ts +0 -31
  281. package/src/lib/data-paths.test.ts +0 -133
  282. package/src/lib/data-paths.ts +0 -109
  283. package/src/lib/debounce.ts +0 -106
  284. package/src/lib/detect-links.test.ts +0 -98
  285. package/src/lib/detect-links.ts +0 -56
  286. package/src/lib/env.test.ts +0 -228
  287. package/src/lib/env.ts +0 -209
  288. package/src/lib/extmarks-history.ts +0 -51
  289. package/src/lib/extmarks-multiwidth.test.ts +0 -322
  290. package/src/lib/extmarks.test.ts +0 -3457
  291. package/src/lib/extmarks.ts +0 -843
  292. package/src/lib/fonts/block.json +0 -405
  293. package/src/lib/fonts/grid.json +0 -265
  294. package/src/lib/fonts/huge.json +0 -741
  295. package/src/lib/fonts/pallet.json +0 -314
  296. package/src/lib/fonts/shade.json +0 -591
  297. package/src/lib/fonts/slick.json +0 -321
  298. package/src/lib/fonts/tiny.json +0 -69
  299. package/src/lib/hast-styled-text.ts +0 -59
  300. package/src/lib/index.ts +0 -21
  301. package/src/lib/keymapping.test.ts +0 -280
  302. package/src/lib/keymapping.ts +0 -87
  303. package/src/lib/objects-in-viewport.test.ts +0 -787
  304. package/src/lib/objects-in-viewport.ts +0 -153
  305. package/src/lib/output.capture.ts +0 -58
  306. package/src/lib/parse.keypress-kitty.protocol.test.ts +0 -340
  307. package/src/lib/parse.keypress-kitty.test.ts +0 -663
  308. package/src/lib/parse.keypress-kitty.ts +0 -439
  309. package/src/lib/parse.keypress.test.ts +0 -1849
  310. package/src/lib/parse.keypress.ts +0 -397
  311. package/src/lib/parse.mouse.test.ts +0 -552
  312. package/src/lib/parse.mouse.ts +0 -232
  313. package/src/lib/paste.ts +0 -16
  314. package/src/lib/queue.ts +0 -65
  315. package/src/lib/renderable.validations.test.ts +0 -87
  316. package/src/lib/renderable.validations.ts +0 -83
  317. package/src/lib/scroll-acceleration.ts +0 -98
  318. package/src/lib/selection.ts +0 -240
  319. package/src/lib/singleton.ts +0 -28
  320. package/src/lib/stdin-parser.test.ts +0 -1676
  321. package/src/lib/stdin-parser.ts +0 -1248
  322. package/src/lib/styled-text.ts +0 -178
  323. package/src/lib/terminal-capability-detection.test.ts +0 -202
  324. package/src/lib/terminal-capability-detection.ts +0 -79
  325. package/src/lib/terminal-palette.test.ts +0 -878
  326. package/src/lib/terminal-palette.ts +0 -383
  327. package/src/lib/tree-sitter/assets/README.md +0 -118
  328. package/src/lib/tree-sitter/assets/update.ts +0 -331
  329. package/src/lib/tree-sitter/assets.d.ts +0 -9
  330. package/src/lib/tree-sitter/cache.test.ts +0 -270
  331. package/src/lib/tree-sitter/client.test.ts +0 -1061
  332. package/src/lib/tree-sitter/client.ts +0 -615
  333. package/src/lib/tree-sitter/default-parsers.ts +0 -80
  334. package/src/lib/tree-sitter/download-utils.ts +0 -148
  335. package/src/lib/tree-sitter/index.ts +0 -28
  336. package/src/lib/tree-sitter/parser.worker.ts +0 -1001
  337. package/src/lib/tree-sitter/parsers-config.ts +0 -75
  338. package/src/lib/tree-sitter/resolve-ft.ts +0 -62
  339. package/src/lib/tree-sitter/types.ts +0 -81
  340. package/src/lib/tree-sitter-styled-text.test.ts +0 -1253
  341. package/src/lib/tree-sitter-styled-text.ts +0 -306
  342. package/src/lib/validate-dir-name.ts +0 -55
  343. package/src/lib/yoga.options.test.ts +0 -628
  344. package/src/lib/yoga.options.ts +0 -346
  345. package/src/plugins/core-slot.ts +0 -579
  346. package/src/plugins/registry.ts +0 -377
  347. package/src/plugins/types.ts +0 -46
  348. package/src/post/filters.ts +0 -888
  349. package/src/renderables/ASCIIFont.ts +0 -219
  350. package/src/renderables/Box.test.ts +0 -160
  351. package/src/renderables/Box.ts +0 -295
  352. package/src/renderables/Code.test.ts +0 -2062
  353. package/src/renderables/Code.ts +0 -357
  354. package/src/renderables/Diff.regression.test.ts +0 -226
  355. package/src/renderables/Diff.test.ts +0 -3027
  356. package/src/renderables/Diff.ts +0 -1209
  357. package/src/renderables/EditBufferRenderable.ts +0 -764
  358. package/src/renderables/FrameBuffer.ts +0 -47
  359. package/src/renderables/Input.test.ts +0 -1228
  360. package/src/renderables/Input.ts +0 -245
  361. package/src/renderables/LineNumberRenderable.ts +0 -675
  362. package/src/renderables/Markdown.ts +0 -1106
  363. package/src/renderables/ScrollBar.ts +0 -422
  364. package/src/renderables/ScrollBox.ts +0 -883
  365. package/src/renderables/Select.test.ts +0 -1010
  366. package/src/renderables/Select.ts +0 -523
  367. package/src/renderables/Slider.test.ts +0 -456
  368. package/src/renderables/Slider.ts +0 -347
  369. package/src/renderables/TabSelect.test.ts +0 -197
  370. package/src/renderables/TabSelect.ts +0 -455
  371. package/src/renderables/Text.selection-buffer.test.ts +0 -123
  372. package/src/renderables/Text.test.ts +0 -2660
  373. package/src/renderables/Text.ts +0 -147
  374. package/src/renderables/TextBufferRenderable.ts +0 -518
  375. package/src/renderables/TextNode.test.ts +0 -1058
  376. package/src/renderables/TextNode.ts +0 -325
  377. package/src/renderables/TextTable.test.ts +0 -1421
  378. package/src/renderables/TextTable.ts +0 -1344
  379. package/src/renderables/Textarea.ts +0 -732
  380. package/src/renderables/TimeToFirstDraw.ts +0 -89
  381. package/src/renderables/__snapshots__/Code.test.ts.snap +0 -13
  382. package/src/renderables/__snapshots__/Diff.test.ts.snap +0 -785
  383. package/src/renderables/__snapshots__/Text.test.ts.snap +0 -421
  384. package/src/renderables/__snapshots__/TextTable.test.ts.snap +0 -215
  385. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +0 -144
  386. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +0 -816
  387. package/src/renderables/__tests__/LineNumberRenderable.test.ts +0 -1787
  388. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +0 -85
  389. package/src/renderables/__tests__/Markdown.test.ts +0 -2287
  390. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +0 -87
  391. package/src/renderables/__tests__/Textarea.buffer.test.ts +0 -682
  392. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +0 -675
  393. package/src/renderables/__tests__/Textarea.editing.test.ts +0 -2041
  394. package/src/renderables/__tests__/Textarea.error-handling.test.ts +0 -35
  395. package/src/renderables/__tests__/Textarea.events.test.ts +0 -738
  396. package/src/renderables/__tests__/Textarea.highlights.test.ts +0 -590
  397. package/src/renderables/__tests__/Textarea.keybinding.test.ts +0 -3149
  398. package/src/renderables/__tests__/Textarea.paste.test.ts +0 -357
  399. package/src/renderables/__tests__/Textarea.rendering.test.ts +0 -1864
  400. package/src/renderables/__tests__/Textarea.scroll.test.ts +0 -733
  401. package/src/renderables/__tests__/Textarea.selection.test.ts +0 -1590
  402. package/src/renderables/__tests__/Textarea.stress.test.ts +0 -670
  403. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +0 -383
  404. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +0 -310
  405. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +0 -221
  406. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +0 -89
  407. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +0 -457
  408. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +0 -158
  409. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +0 -387
  410. package/src/renderables/__tests__/markdown-parser.test.ts +0 -217
  411. package/src/renderables/__tests__/renderable-test-utils.ts +0 -60
  412. package/src/renderables/composition/README.md +0 -8
  413. package/src/renderables/composition/VRenderable.ts +0 -32
  414. package/src/renderables/composition/constructs.ts +0 -127
  415. package/src/renderables/composition/vnode.ts +0 -289
  416. package/src/renderables/index.ts +0 -22
  417. package/src/renderables/markdown-parser.ts +0 -66
  418. package/src/renderer.ts +0 -2363
  419. package/src/runtime-plugin-support.ts +0 -39
  420. package/src/runtime-plugin.ts +0 -144
  421. package/src/syntax-style.test.ts +0 -841
  422. package/src/syntax-style.ts +0 -264
  423. package/src/testing/README.md +0 -210
  424. package/src/testing/capture-spans.test.ts +0 -194
  425. package/src/testing/integration.test.ts +0 -276
  426. package/src/testing/manual-clock.ts +0 -106
  427. package/src/testing/mock-keys.test.ts +0 -1356
  428. package/src/testing/mock-keys.ts +0 -449
  429. package/src/testing/mock-mouse.test.ts +0 -218
  430. package/src/testing/mock-mouse.ts +0 -247
  431. package/src/testing/mock-tree-sitter-client.ts +0 -73
  432. package/src/testing/spy.ts +0 -13
  433. package/src/testing/test-recorder.test.ts +0 -415
  434. package/src/testing/test-recorder.ts +0 -145
  435. package/src/testing/test-renderer.ts +0 -116
  436. package/src/testing.ts +0 -7
  437. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +0 -481
  438. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +0 -19
  439. package/src/tests/__snapshots__/scrollbox.test.ts.snap +0 -29
  440. package/src/tests/absolute-positioning.snapshot.test.ts +0 -638
  441. package/src/tests/allocator-stats.test.ts +0 -38
  442. package/src/tests/destroy-during-render.test.ts +0 -200
  443. package/src/tests/hover-cursor.test.ts +0 -98
  444. package/src/tests/native-span-feed-async.test.ts +0 -173
  445. package/src/tests/native-span-feed-close.test.ts +0 -120
  446. package/src/tests/native-span-feed-coverage.test.ts +0 -227
  447. package/src/tests/native-span-feed-edge-cases.test.ts +0 -352
  448. package/src/tests/native-span-feed-use-after-free.test.ts +0 -45
  449. package/src/tests/opacity.test.ts +0 -123
  450. package/src/tests/renderable.snapshot.test.ts +0 -524
  451. package/src/tests/renderable.test.ts +0 -1281
  452. package/src/tests/renderer.console-startup.test.ts +0 -65
  453. package/src/tests/renderer.control.test.ts +0 -364
  454. package/src/tests/renderer.core-slot-binding.test.ts +0 -952
  455. package/src/tests/renderer.cursor.test.ts +0 -26
  456. package/src/tests/renderer.destroy-during-render.test.ts +0 -110
  457. package/src/tests/renderer.focus-restore.test.ts +0 -228
  458. package/src/tests/renderer.focus.test.ts +0 -251
  459. package/src/tests/renderer.idle.test.ts +0 -219
  460. package/src/tests/renderer.input.test.ts +0 -2145
  461. package/src/tests/renderer.kitty-flags.test.ts +0 -195
  462. package/src/tests/renderer.mouse.test.ts +0 -1269
  463. package/src/tests/renderer.palette.test.ts +0 -629
  464. package/src/tests/renderer.selection.test.ts +0 -49
  465. package/src/tests/renderer.slot-registry.test.ts +0 -649
  466. package/src/tests/renderer.useMouse.test.ts +0 -50
  467. package/src/tests/runtime-plugin-support.fixture.ts +0 -11
  468. package/src/tests/runtime-plugin-support.test.ts +0 -28
  469. package/src/tests/runtime-plugin.fixture.ts +0 -40
  470. package/src/tests/runtime-plugin.test.ts +0 -190
  471. package/src/tests/scrollbox-culling-bug.test.ts +0 -114
  472. package/src/tests/scrollbox-hitgrid-resize.test.ts +0 -136
  473. package/src/tests/scrollbox-hitgrid.test.ts +0 -909
  474. package/src/tests/scrollbox.test.ts +0 -1530
  475. package/src/tests/wrap-resize-perf.test.ts +0 -229
  476. package/src/tests/yoga-setters.test.ts +0 -921
  477. package/src/text-buffer-view.test.ts +0 -705
  478. package/src/text-buffer-view.ts +0 -189
  479. package/src/text-buffer.test.ts +0 -347
  480. package/src/text-buffer.ts +0 -250
  481. package/src/types.ts +0 -152
  482. package/src/utils.ts +0 -88
  483. package/src/zig/ansi.zig +0 -268
  484. package/src/zig/bench/README.md +0 -50
  485. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +0 -887
  486. package/src/zig/bench/edit-buffer_bench.zig +0 -476
  487. package/src/zig/bench/native-span-feed_bench.zig +0 -100
  488. package/src/zig/bench/rope-markers_bench.zig +0 -713
  489. package/src/zig/bench/rope_bench.zig +0 -514
  490. package/src/zig/bench/styled-text_bench.zig +0 -470
  491. package/src/zig/bench/text-buffer-coords_bench.zig +0 -362
  492. package/src/zig/bench/text-buffer-view_bench.zig +0 -459
  493. package/src/zig/bench/text-chunk-graphemes_bench.zig +0 -273
  494. package/src/zig/bench/utf8_bench.zig +0 -799
  495. package/src/zig/bench-utils.zig +0 -431
  496. package/src/zig/bench.zig +0 -217
  497. package/src/zig/buffer.zig +0 -2223
  498. package/src/zig/build.zig +0 -289
  499. package/src/zig/build.zig.zon +0 -16
  500. package/src/zig/edit-buffer.zig +0 -825
  501. package/src/zig/editor-view.zig +0 -802
  502. package/src/zig/event-bus.zig +0 -13
  503. package/src/zig/event-emitter.zig +0 -65
  504. package/src/zig/file-logger.zig +0 -92
  505. package/src/zig/grapheme.zig +0 -599
  506. package/src/zig/lib.zig +0 -1834
  507. package/src/zig/link.zig +0 -333
  508. package/src/zig/logger.zig +0 -43
  509. package/src/zig/mem-registry.zig +0 -125
  510. package/src/zig/native-span-feed-bench-lib.zig +0 -7
  511. package/src/zig/native-span-feed.zig +0 -708
  512. package/src/zig/renderer.zig +0 -1386
  513. package/src/zig/rope.zig +0 -1220
  514. package/src/zig/syntax-style.zig +0 -161
  515. package/src/zig/terminal.zig +0 -975
  516. package/src/zig/test.zig +0 -70
  517. package/src/zig/tests/README.md +0 -18
  518. package/src/zig/tests/buffer_test.zig +0 -2526
  519. package/src/zig/tests/edit-buffer-history_test.zig +0 -271
  520. package/src/zig/tests/edit-buffer_test.zig +0 -1689
  521. package/src/zig/tests/editor-view_test.zig +0 -3299
  522. package/src/zig/tests/event-emitter_test.zig +0 -249
  523. package/src/zig/tests/grapheme_test.zig +0 -1304
  524. package/src/zig/tests/link_test.zig +0 -190
  525. package/src/zig/tests/mem-registry_test.zig +0 -473
  526. package/src/zig/tests/memory_leak_regression_test.zig +0 -159
  527. package/src/zig/tests/native-span-feed_test.zig +0 -1264
  528. package/src/zig/tests/renderer_test.zig +0 -1010
  529. package/src/zig/tests/rope-nested_test.zig +0 -712
  530. package/src/zig/tests/rope_fuzz_test.zig +0 -238
  531. package/src/zig/tests/rope_test.zig +0 -2362
  532. package/src/zig/tests/segment-merge.test.zig +0 -148
  533. package/src/zig/tests/syntax-style_test.zig +0 -557
  534. package/src/zig/tests/terminal_test.zig +0 -719
  535. package/src/zig/tests/text-buffer-drawing_test.zig +0 -3237
  536. package/src/zig/tests/text-buffer-highlights_test.zig +0 -666
  537. package/src/zig/tests/text-buffer-iterators_test.zig +0 -776
  538. package/src/zig/tests/text-buffer-segment_test.zig +0 -320
  539. package/src/zig/tests/text-buffer-selection_test.zig +0 -1035
  540. package/src/zig/tests/text-buffer-selection_viewport_test.zig +0 -358
  541. package/src/zig/tests/text-buffer-view_test.zig +0 -3649
  542. package/src/zig/tests/text-buffer_test.zig +0 -2191
  543. package/src/zig/tests/unicode-width-map.zon +0 -3909
  544. package/src/zig/tests/utf8_no_zwj_test.zig +0 -260
  545. package/src/zig/tests/utf8_test.zig +0 -4057
  546. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +0 -267
  547. package/src/zig/tests/utf8_wcwidth_test.zig +0 -357
  548. package/src/zig/tests/word-wrap-editing_test.zig +0 -498
  549. package/src/zig/tests/wrap-cache-perf_test.zig +0 -113
  550. package/src/zig/text-buffer-iterators.zig +0 -499
  551. package/src/zig/text-buffer-segment.zig +0 -404
  552. package/src/zig/text-buffer-view.zig +0 -1371
  553. package/src/zig/text-buffer.zig +0 -1180
  554. package/src/zig/utf8.zig +0 -1948
  555. package/src/zig/utils.zig +0 -9
  556. package/src/zig-structs.ts +0 -261
  557. package/src/zig.ts +0 -3843
  558. package/tsconfig.build.json +0 -22
  559. package/tsconfig.json +0 -28
  560. /package/{src/lib/tree-sitter/assets → assets}/javascript/highlights.scm +0 -0
  561. /package/{src/lib/tree-sitter/assets → assets}/javascript/tree-sitter-javascript.wasm +0 -0
  562. /package/{src/lib/tree-sitter/assets → assets}/markdown/highlights.scm +0 -0
  563. /package/{src/lib/tree-sitter/assets → assets}/markdown/injections.scm +0 -0
  564. /package/{src/lib/tree-sitter/assets → assets}/markdown/tree-sitter-markdown.wasm +0 -0
  565. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/highlights.scm +0 -0
  566. /package/{src/lib/tree-sitter/assets → assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  567. /package/{src/lib/tree-sitter/assets → assets}/typescript/highlights.scm +0 -0
  568. /package/{src/lib/tree-sitter/assets → assets}/typescript/tree-sitter-typescript.wasm +0 -0
  569. /package/{src/lib/tree-sitter/assets → assets}/zig/highlights.scm +0 -0
  570. /package/{src/lib/tree-sitter/assets → assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -1,3027 +0,0 @@
1
- import { test, expect, beforeEach, afterEach } from "bun:test"
2
- import { DiffRenderable } from "./Diff.js"
3
- import { SyntaxStyle } from "../syntax-style.js"
4
- import { RGBA } from "../lib/RGBA.js"
5
- import { createMockMouse, createTestRenderer, type TestRenderer } from "../testing.js"
6
- import { MockTreeSitterClient } from "../testing/mock-tree-sitter-client.js"
7
- import type { SimpleHighlight } from "../lib/tree-sitter/types.js"
8
- import { settleDiffHighlighting } from "./__tests__/renderable-test-utils.js"
9
-
10
- let currentRenderer: TestRenderer
11
- let renderOnce: () => Promise<void>
12
- let captureFrame: () => string
13
-
14
- beforeEach(async () => {
15
- const testRenderer = await createTestRenderer({ width: 80, height: 20 })
16
- currentRenderer = testRenderer.renderer
17
- renderOnce = testRenderer.renderOnce
18
- captureFrame = testRenderer.captureCharFrame
19
- })
20
-
21
- afterEach(async () => {
22
- if (currentRenderer) {
23
- currentRenderer.destroy()
24
- }
25
- })
26
-
27
- const simpleDiff = `--- a/test.js
28
- +++ b/test.js
29
- @@ -1,3 +1,3 @@
30
- function hello() {
31
- - console.log("Hello");
32
- + console.log("Hello, World!");
33
- }`
34
-
35
- const multiLineDiff = `--- a/math.js
36
- +++ b/math.js
37
- @@ -1,7 +1,11 @@
38
- function add(a, b) {
39
- return a + b;
40
- }
41
-
42
- +function subtract(a, b) {
43
- + return a - b;
44
- +}
45
- +
46
- function multiply(a, b) {
47
- - return a * b;
48
- + return a * b * 1;
49
- }`
50
-
51
- const addOnlyDiff = `--- a/new.js
52
- +++ b/new.js
53
- @@ -0,0 +1,3 @@
54
- +function newFunction() {
55
- + return true;
56
- +}`
57
-
58
- const removeOnlyDiff = `--- a/old.js
59
- +++ b/old.js
60
- @@ -1,3 +0,0 @@
61
- -function oldFunction() {
62
- - return false;
63
- -}`
64
-
65
- const largeDiff = `--- a/large.js
66
- +++ b/large.js
67
- @@ -42,9 +42,10 @@
68
- const line42 = 'context';
69
- const line43 = 'context';
70
- -const line44 = 'removed';
71
- +const line44 = 'added';
72
- const line45 = 'context';
73
- +const line46 = 'added';
74
- const line47 = 'context';
75
- const line48 = 'context';
76
- -const line49 = 'removed';
77
- +const line49 = 'changed';
78
- const line50 = 'context';
79
- const line51 = 'context';`
80
-
81
- test("DiffRenderable - basic construction with unified view", async () => {
82
- const syntaxStyle = SyntaxStyle.fromStyles({
83
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
84
- })
85
-
86
- const diffRenderable = new DiffRenderable(currentRenderer, {
87
- id: "test-diff",
88
- diff: simpleDiff,
89
- view: "unified",
90
- syntaxStyle,
91
- })
92
-
93
- expect(diffRenderable.diff).toBe(simpleDiff)
94
- expect(diffRenderable.view).toBe("unified")
95
- })
96
-
97
- test("DiffRenderable - basic construction with split view", async () => {
98
- const syntaxStyle = SyntaxStyle.fromStyles({
99
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
100
- })
101
-
102
- const diffRenderable = new DiffRenderable(currentRenderer, {
103
- id: "test-diff",
104
- diff: simpleDiff,
105
- view: "split",
106
- syntaxStyle,
107
- })
108
-
109
- expect(diffRenderable.diff).toBe(simpleDiff)
110
- expect(diffRenderable.view).toBe("split")
111
- })
112
-
113
- test("DiffRenderable - defaults to unified view", async () => {
114
- const syntaxStyle = SyntaxStyle.fromStyles({
115
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
116
- })
117
-
118
- const diffRenderable = new DiffRenderable(currentRenderer, {
119
- id: "test-diff",
120
- diff: simpleDiff,
121
- syntaxStyle,
122
- })
123
-
124
- expect(diffRenderable.view).toBe("unified")
125
- })
126
-
127
- test("DiffRenderable - unified view renders correctly", async () => {
128
- const syntaxStyle = SyntaxStyle.fromStyles({
129
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
130
- })
131
-
132
- const diffRenderable = new DiffRenderable(currentRenderer, {
133
- id: "test-diff",
134
- diff: simpleDiff,
135
- view: "unified",
136
- syntaxStyle,
137
- width: "100%",
138
- height: "100%",
139
- })
140
-
141
- currentRenderer.root.add(diffRenderable)
142
- await renderOnce()
143
-
144
- const frame = captureFrame()
145
- expect(frame).toMatchSnapshot("unified view simple diff")
146
-
147
- // Check that both removed and added lines are present
148
- expect(frame).toContain('console.log("Hello")')
149
- expect(frame).toContain('console.log("Hello, World!")')
150
- })
151
-
152
- test("DiffRenderable - split view renders correctly", async () => {
153
- const syntaxStyle = SyntaxStyle.fromStyles({
154
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
155
- })
156
-
157
- const diffRenderable = new DiffRenderable(currentRenderer, {
158
- id: "test-diff",
159
- diff: simpleDiff,
160
- view: "split",
161
- syntaxStyle,
162
- width: "100%",
163
- height: "100%",
164
- })
165
-
166
- currentRenderer.root.add(diffRenderable)
167
- await renderOnce()
168
-
169
- const frame = captureFrame()
170
- expect(frame).toMatchSnapshot("split view simple diff")
171
-
172
- // In split view, both sides should be visible (may be wrapped)
173
- expect(frame).toContain("console.log")
174
- expect(frame).toContain("Hello")
175
- expect(frame).toContain("World")
176
- })
177
-
178
- test("DiffRenderable - multi-line diff unified view", async () => {
179
- const syntaxStyle = SyntaxStyle.fromStyles({
180
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
181
- })
182
-
183
- const diffRenderable = new DiffRenderable(currentRenderer, {
184
- id: "test-diff",
185
- diff: multiLineDiff,
186
- view: "unified",
187
- syntaxStyle,
188
- width: "100%",
189
- height: "100%",
190
- })
191
-
192
- currentRenderer.root.add(diffRenderable)
193
- await renderOnce()
194
-
195
- const frame = captureFrame()
196
- expect(frame).toMatchSnapshot("unified view multi-line diff")
197
-
198
- // Check for additions
199
- expect(frame).toContain("function subtract")
200
- // Check for modifications
201
- expect(frame).toContain("a * b * 1")
202
- })
203
-
204
- test("DiffRenderable - multi-line diff split view", async () => {
205
- const syntaxStyle = SyntaxStyle.fromStyles({
206
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
207
- })
208
-
209
- const diffRenderable = new DiffRenderable(currentRenderer, {
210
- id: "test-diff",
211
- diff: multiLineDiff,
212
- view: "split",
213
- syntaxStyle,
214
- width: "100%",
215
- height: "100%",
216
- })
217
-
218
- currentRenderer.root.add(diffRenderable)
219
- await renderOnce()
220
-
221
- const frame = captureFrame()
222
- expect(frame).toMatchSnapshot("split view multi-line diff")
223
-
224
- // Left side should have old code
225
- expect(frame).toContain("a * b")
226
- // Right side should have new code
227
- expect(frame).toContain("subtract")
228
- })
229
-
230
- test("DiffRenderable - add-only diff unified view", async () => {
231
- const syntaxStyle = SyntaxStyle.fromStyles({
232
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
233
- })
234
-
235
- const diffRenderable = new DiffRenderable(currentRenderer, {
236
- id: "test-diff",
237
- diff: addOnlyDiff,
238
- view: "unified",
239
- syntaxStyle,
240
- width: "100%",
241
- height: "100%",
242
- })
243
-
244
- currentRenderer.root.add(diffRenderable)
245
- await renderOnce()
246
-
247
- const frame = captureFrame()
248
- expect(frame).toMatchSnapshot("unified view add-only diff")
249
-
250
- expect(frame).toContain("newFunction")
251
- })
252
-
253
- test("DiffRenderable - add-only diff split view", async () => {
254
- const syntaxStyle = SyntaxStyle.fromStyles({
255
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
256
- })
257
-
258
- const diffRenderable = new DiffRenderable(currentRenderer, {
259
- id: "test-diff",
260
- diff: addOnlyDiff,
261
- view: "split",
262
- syntaxStyle,
263
- width: "100%",
264
- height: "100%",
265
- })
266
-
267
- currentRenderer.root.add(diffRenderable)
268
- await renderOnce()
269
-
270
- const frame = captureFrame()
271
- expect(frame).toMatchSnapshot("split view add-only diff")
272
-
273
- // Right side should have the new function
274
- expect(frame).toContain("newFunction")
275
- })
276
-
277
- test("DiffRenderable - remove-only diff unified view", async () => {
278
- const syntaxStyle = SyntaxStyle.fromStyles({
279
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
280
- })
281
-
282
- const diffRenderable = new DiffRenderable(currentRenderer, {
283
- id: "test-diff",
284
- diff: removeOnlyDiff,
285
- view: "unified",
286
- syntaxStyle,
287
- width: "100%",
288
- height: "100%",
289
- })
290
-
291
- currentRenderer.root.add(diffRenderable)
292
- await renderOnce()
293
-
294
- const frame = captureFrame()
295
- expect(frame).toMatchSnapshot("unified view remove-only diff")
296
-
297
- expect(frame).toContain("oldFunction")
298
- })
299
-
300
- test("DiffRenderable - remove-only diff split view", async () => {
301
- const syntaxStyle = SyntaxStyle.fromStyles({
302
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
303
- })
304
-
305
- const diffRenderable = new DiffRenderable(currentRenderer, {
306
- id: "test-diff",
307
- diff: removeOnlyDiff,
308
- view: "split",
309
- syntaxStyle,
310
- width: "100%",
311
- height: "100%",
312
- })
313
-
314
- currentRenderer.root.add(diffRenderable)
315
- await renderOnce()
316
-
317
- const frame = captureFrame()
318
- expect(frame).toMatchSnapshot("split view remove-only diff")
319
-
320
- // Left side should have the old function
321
- expect(frame).toContain("oldFunction")
322
- })
323
-
324
- test("DiffRenderable - large line numbers displayed correctly", async () => {
325
- const syntaxStyle = SyntaxStyle.fromStyles({
326
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
327
- })
328
-
329
- const diffRenderable = new DiffRenderable(currentRenderer, {
330
- id: "test-diff",
331
- diff: largeDiff,
332
- view: "unified",
333
- syntaxStyle,
334
- showLineNumbers: true,
335
- width: "100%",
336
- height: "100%",
337
- })
338
-
339
- currentRenderer.root.add(diffRenderable)
340
- await renderOnce()
341
-
342
- const frame = captureFrame()
343
- expect(frame).toMatchSnapshot("unified view large line numbers")
344
-
345
- // Check that line numbers in the 40s are displayed
346
- expect(frame).toMatch(/4[0-9]/)
347
- })
348
-
349
- test("DiffRenderable - can toggle view mode", async () => {
350
- const syntaxStyle = SyntaxStyle.fromStyles({
351
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
352
- })
353
-
354
- const diffRenderable = new DiffRenderable(currentRenderer, {
355
- id: "test-diff",
356
- diff: simpleDiff,
357
- view: "unified",
358
- syntaxStyle,
359
- width: "100%",
360
- height: "100%",
361
- })
362
-
363
- currentRenderer.root.add(diffRenderable)
364
- await renderOnce()
365
-
366
- const unifiedFrame = captureFrame()
367
- expect(diffRenderable.view).toBe("unified")
368
-
369
- // Switch to split view
370
- diffRenderable.view = "split"
371
- await renderOnce()
372
-
373
- const splitFrame = captureFrame()
374
- expect(diffRenderable.view).toBe("split")
375
-
376
- // Frames should be different
377
- expect(unifiedFrame).not.toBe(splitFrame)
378
- })
379
-
380
- test("DiffRenderable - can update diff content", async () => {
381
- const syntaxStyle = SyntaxStyle.fromStyles({
382
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
383
- })
384
-
385
- const diffRenderable = new DiffRenderable(currentRenderer, {
386
- id: "test-diff",
387
- diff: simpleDiff,
388
- view: "unified",
389
- syntaxStyle,
390
- width: "100%",
391
- height: "100%",
392
- })
393
-
394
- currentRenderer.root.add(diffRenderable)
395
- await renderOnce()
396
-
397
- const frame1 = captureFrame()
398
- expect(frame1).toContain("Hello")
399
-
400
- // Update diff
401
- diffRenderable.diff = multiLineDiff
402
- await renderOnce()
403
-
404
- const frame2 = captureFrame()
405
- expect(frame2).toContain("subtract")
406
- expect(frame2).not.toContain('console.log("Hello")')
407
- })
408
-
409
- test("DiffRenderable - can toggle line numbers", async () => {
410
- const syntaxStyle = SyntaxStyle.fromStyles({
411
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
412
- })
413
-
414
- const diffRenderable = new DiffRenderable(currentRenderer, {
415
- id: "test-diff",
416
- diff: simpleDiff,
417
- view: "unified",
418
- syntaxStyle,
419
- showLineNumbers: true,
420
- width: "100%",
421
- height: "100%",
422
- })
423
-
424
- currentRenderer.root.add(diffRenderable)
425
- await renderOnce()
426
-
427
- expect(diffRenderable.showLineNumbers).toBe(true)
428
-
429
- // Hide line numbers
430
- diffRenderable.showLineNumbers = false
431
- await renderOnce()
432
-
433
- expect(diffRenderable.showLineNumbers).toBe(false)
434
- })
435
-
436
- test("DiffRenderable - can update filetype", async () => {
437
- const syntaxStyle = SyntaxStyle.fromStyles({
438
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
439
- keyword: { fg: RGBA.fromValues(1, 0, 0, 1) },
440
- })
441
-
442
- const diffRenderable = new DiffRenderable(currentRenderer, {
443
- id: "test-diff",
444
- diff: simpleDiff,
445
- view: "unified",
446
- syntaxStyle,
447
- filetype: "javascript",
448
- width: "100%",
449
- height: "100%",
450
- })
451
-
452
- currentRenderer.root.add(diffRenderable)
453
- await renderOnce()
454
-
455
- expect(diffRenderable.filetype).toBe("javascript")
456
-
457
- // Update filetype
458
- diffRenderable.filetype = "typescript"
459
- expect(diffRenderable.filetype).toBe("typescript")
460
- })
461
-
462
- test("DiffRenderable - handles empty diff", async () => {
463
- const syntaxStyle = SyntaxStyle.fromStyles({
464
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
465
- })
466
-
467
- const diffRenderable = new DiffRenderable(currentRenderer, {
468
- id: "test-diff",
469
- diff: "",
470
- view: "unified",
471
- syntaxStyle,
472
- width: "100%",
473
- height: "100%",
474
- })
475
-
476
- currentRenderer.root.add(diffRenderable)
477
- await renderOnce()
478
-
479
- // Should not crash with empty diff
480
- expect(diffRenderable.diff).toBe("")
481
- })
482
-
483
- test("DiffRenderable - handles diff with no changes", async () => {
484
- const syntaxStyle = SyntaxStyle.fromStyles({
485
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
486
- })
487
-
488
- const noChangeDiff = `--- a/test.js
489
- +++ b/test.js
490
- @@ -1,3 +1,3 @@
491
- function hello() {
492
- console.log("Hello");
493
- }`
494
-
495
- const diffRenderable = new DiffRenderable(currentRenderer, {
496
- id: "test-diff",
497
- diff: noChangeDiff,
498
- view: "unified",
499
- syntaxStyle,
500
- width: "100%",
501
- height: "100%",
502
- })
503
-
504
- currentRenderer.root.add(diffRenderable)
505
- await renderOnce()
506
-
507
- const frame = captureFrame()
508
- expect(frame).toContain("function hello")
509
- })
510
-
511
- test("DiffRenderable - can update wrapMode", async () => {
512
- const syntaxStyle = SyntaxStyle.fromStyles({
513
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
514
- })
515
-
516
- const diffRenderable = new DiffRenderable(currentRenderer, {
517
- id: "test-diff",
518
- diff: simpleDiff,
519
- view: "unified",
520
- syntaxStyle,
521
- wrapMode: "word",
522
- width: "100%",
523
- height: "100%",
524
- })
525
-
526
- currentRenderer.root.add(diffRenderable)
527
- await renderOnce()
528
-
529
- expect(diffRenderable.wrapMode).toBe("word")
530
-
531
- diffRenderable.wrapMode = "char"
532
- expect(diffRenderable.wrapMode).toBe("char")
533
- })
534
-
535
- test("DiffRenderable - split view alignment with empty lines", async () => {
536
- const syntaxStyle = SyntaxStyle.fromStyles({
537
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
538
- })
539
-
540
- // Diff with additions that should create empty lines on left
541
- const alignmentDiff = `--- a/test.js
542
- +++ b/test.js
543
- @@ -1,2 +1,5 @@
544
- line1
545
- +line2_added
546
- +line3_added
547
- +line4_added
548
- line5`
549
-
550
- const diffRenderable = new DiffRenderable(currentRenderer, {
551
- id: "test-diff",
552
- diff: alignmentDiff,
553
- view: "split",
554
- syntaxStyle,
555
- width: "100%",
556
- height: "100%",
557
- })
558
-
559
- currentRenderer.root.add(diffRenderable)
560
- await renderOnce()
561
-
562
- const frame = captureFrame()
563
- expect(frame).toMatchSnapshot("split view alignment")
564
-
565
- // Both sides should have same number of lines (with empty lines for alignment)
566
- expect(frame).toContain("line1")
567
- expect(frame).toContain("line5")
568
- expect(frame).toContain("line2_added")
569
- })
570
-
571
- test("DiffRenderable - context lines shown on both sides in split view", async () => {
572
- const syntaxStyle = SyntaxStyle.fromStyles({
573
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
574
- })
575
-
576
- const diffRenderable = new DiffRenderable(currentRenderer, {
577
- id: "test-diff",
578
- diff: multiLineDiff,
579
- view: "split",
580
- syntaxStyle,
581
- width: "100%",
582
- height: "100%",
583
- })
584
-
585
- currentRenderer.root.add(diffRenderable)
586
- await renderOnce()
587
-
588
- const frame = captureFrame()
589
-
590
- // Context lines should appear on both sides
591
- expect(frame).toContain("function add")
592
- expect(frame).toContain("function multiply")
593
- })
594
-
595
- test("DiffRenderable - custom colors applied correctly", async () => {
596
- const syntaxStyle = SyntaxStyle.fromStyles({
597
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
598
- })
599
-
600
- const diffRenderable = new DiffRenderable(currentRenderer, {
601
- id: "test-diff",
602
- diff: simpleDiff,
603
- view: "unified",
604
- syntaxStyle,
605
- addedBg: "#00ff00",
606
- removedBg: "#ff0000",
607
- addedSignColor: "#00ff00",
608
- removedSignColor: "#ff0000",
609
- width: "100%",
610
- height: "100%",
611
- })
612
-
613
- currentRenderer.root.add(diffRenderable)
614
- await renderOnce()
615
-
616
- // Should not crash with custom colors
617
- const frame = captureFrame()
618
- expect(frame).toContain('console.log("Hello")')
619
- })
620
-
621
- test("DiffRenderable - line numbers hidden for empty alignment lines in split view", async () => {
622
- const syntaxStyle = SyntaxStyle.fromStyles({
623
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
624
- })
625
-
626
- const diffRenderable = new DiffRenderable(currentRenderer, {
627
- id: "test-diff",
628
- diff: addOnlyDiff,
629
- view: "split",
630
- syntaxStyle,
631
- showLineNumbers: true,
632
- width: "100%",
633
- height: "100%",
634
- })
635
-
636
- currentRenderer.root.add(diffRenderable)
637
- await renderOnce()
638
-
639
- const frame = captureFrame()
640
- expect(frame).toMatchSnapshot("split view with hidden line numbers for empty lines")
641
-
642
- // Right side should have line numbers for new lines
643
- // Left side should have empty lines without line numbers
644
- })
645
-
646
- test("DiffRenderable - stable rendering across multiple frames (no visual glitches)", async () => {
647
- const syntaxStyle = SyntaxStyle.fromStyles({
648
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
649
- })
650
-
651
- const diffRenderable = new DiffRenderable(currentRenderer, {
652
- id: "test-diff",
653
- diff: multiLineDiff,
654
- view: "unified",
655
- syntaxStyle,
656
- showLineNumbers: true,
657
- width: "100%",
658
- height: "100%",
659
- })
660
-
661
- currentRenderer.root.add(diffRenderable)
662
-
663
- // Render the initial frame
664
- await renderOnce()
665
-
666
- const frameAfterAutoRender = captureFrame()
667
-
668
- // Now call renderOnce explicitly (this would be the second render)
669
- await renderOnce()
670
- const firstFrame = captureFrame()
671
-
672
- // Render a third time
673
- await renderOnce()
674
- const secondFrame = captureFrame()
675
-
676
- // BEHAVIORAL EXPECTATION: All frames should be identical
677
- // If frames differ, it indicates a visual glitch (e.g., gutter width changing,
678
- // content shifting, or partial rendering)
679
- expect(frameAfterAutoRender).toBe(firstFrame)
680
- expect(firstFrame).toBe(secondFrame)
681
-
682
- // Verify all frames have complete content (not partial rendering)
683
- expect(frameAfterAutoRender).toContain("function add")
684
- expect(frameAfterAutoRender).toContain("function subtract")
685
- expect(frameAfterAutoRender).toContain("function multiply")
686
-
687
- // Verify line numbers are present and properly aligned
688
- // If gutter width is wrong, line numbers will be misaligned or cut off
689
- const frameLines = frameAfterAutoRender.split("\n")
690
- const linesWithLineNumbers = frameLines.filter((l) => l.match(/^\s*\d+\s+/))
691
-
692
- // Should have multiple lines with line numbers
693
- expect(linesWithLineNumbers.length).toBeGreaterThan(5)
694
-
695
- // All line number widths should be consistent (not change between renders)
696
- // Extract just the line number part (before the sign)
697
- const lineNumberWidths = linesWithLineNumbers
698
- .map((line) => {
699
- const match = line.match(/^(\s*\d+)\s/)
700
- return match ? match[1].length : -1
701
- })
702
- .filter((w) => w > 0)
703
-
704
- // All line numbers should have the same width (indicating stable gutter)
705
- const uniqueWidths = new Set(lineNumberWidths)
706
- expect(uniqueWidths.size).toBe(1) // Gutter width should be consistent
707
- })
708
-
709
- test("DiffRenderable - can be constructed without diff and set via setter", async () => {
710
- const syntaxStyle = SyntaxStyle.fromStyles({
711
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
712
- })
713
-
714
- // Construct without diff
715
- const diffRenderable = new DiffRenderable(currentRenderer, {
716
- id: "test-diff",
717
- view: "unified",
718
- syntaxStyle,
719
- width: "100%",
720
- height: "100%",
721
- })
722
-
723
- currentRenderer.root.add(diffRenderable)
724
- await renderOnce()
725
-
726
- // Should render empty
727
- let frame = captureFrame()
728
- expect(frame.trim()).toBe("")
729
-
730
- // Now set diff via setter
731
- diffRenderable.diff = simpleDiff
732
- await renderOnce()
733
-
734
- frame = captureFrame()
735
- expect(frame).toContain("function hello")
736
- expect(frame).toContain('console.log("Hello")')
737
- expect(frame).toContain('console.log("Hello, World!")')
738
- })
739
-
740
- test("DiffRenderable - consistent left padding for line numbers > 9", async () => {
741
- const syntaxStyle = SyntaxStyle.fromStyles({
742
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
743
- })
744
-
745
- // Create a diff with line numbers that go into double digits
746
- const diffWith10PlusLines = `--- a/test.js
747
- +++ b/test.js
748
- @@ -8,7 +8,9 @@
749
- line8
750
- line9
751
- -line10_old
752
- +line10_new
753
- line11
754
- +line12_added
755
- +line13_added
756
- line14
757
- line15
758
- -line16_old
759
- +line16_new`
760
-
761
- const diffRenderable = new DiffRenderable(currentRenderer, {
762
- id: "test-diff",
763
- diff: diffWith10PlusLines,
764
- view: "unified",
765
- syntaxStyle,
766
- showLineNumbers: true,
767
- width: "100%",
768
- height: "100%",
769
- })
770
-
771
- currentRenderer.root.add(diffRenderable)
772
- await renderOnce()
773
-
774
- const frame = captureFrame()
775
- expect(frame).toMatchSnapshot("unified view with double-digit line numbers")
776
-
777
- const frameLines = frame.split("\n")
778
-
779
- // Find lines in the output
780
- // Line 8 (single digit) should have left padding (appears as " 8 line8")
781
- const line8 = frameLines.find((l) => l.includes("line8"))
782
- expect(line8).toBeTruthy()
783
- const line8Match = line8!.match(/^( +)8 /)
784
- expect(line8Match).toBeTruthy()
785
- expect(line8Match![1].length).toBeGreaterThanOrEqual(1) // At least 1 space of left padding
786
-
787
- // Line 10 (double digit) should have left padding (appears as " 10 line10" or " 11 line10")
788
- const line10 = frameLines.find((l) => l.includes("line10"))
789
- expect(line10).toBeTruthy()
790
- const line10Match = line10!.match(/^( +)1[01] /)
791
- expect(line10Match).toBeTruthy()
792
- expect(line10Match![1].length).toBeGreaterThanOrEqual(1) // At least 1 space of left padding
793
-
794
- // Line 16 (double digit) should have left padding
795
- // Note: With correct line numbers, the removed line shows as 14 - and added shows as 16 +
796
- const line16 = frameLines.find((l) => l.includes("line16"))
797
- expect(line16).toBeTruthy()
798
- // Match either 14 - or 16 + (the correct line numbers after the fix)
799
- const line16Match = line16!.match(/^( +)(14 -|16 \+) /)
800
- expect(line16Match).toBeTruthy()
801
- expect(line16Match![1].length).toBeGreaterThanOrEqual(1) // At least 1 space of left padding
802
- })
803
-
804
- test("DiffRenderable - line numbers are correct in unified view", async () => {
805
- const syntaxStyle = SyntaxStyle.fromStyles({
806
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
807
- })
808
-
809
- const diffRenderable = new DiffRenderable(currentRenderer, {
810
- id: "test-diff",
811
- diff: simpleDiff,
812
- view: "unified",
813
- syntaxStyle,
814
- showLineNumbers: true,
815
- width: "100%",
816
- height: "100%",
817
- })
818
-
819
- currentRenderer.root.add(diffRenderable)
820
- await renderOnce()
821
-
822
- const frame = captureFrame()
823
- const frameLines = frame.split("\n")
824
-
825
- // Line 2 is removed (old file line 2)
826
- const removedLine = frameLines.find((l) => l.includes('console.log("Hello");'))
827
- expect(removedLine).toBeTruthy()
828
- expect(removedLine).toMatch(/^ *2 -/)
829
-
830
- // Line 2 is added (new file line 2) - NOT line 3!
831
- const addedLine = frameLines.find((l) => l.includes('console.log("Hello, World!")'))
832
- expect(addedLine).toBeTruthy()
833
- expect(addedLine).toMatch(/^ *2 \+/)
834
- })
835
-
836
- test("DiffRenderable - line numbers are correct in split view", async () => {
837
- const syntaxStyle = SyntaxStyle.fromStyles({
838
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
839
- })
840
-
841
- const diffRenderable = new DiffRenderable(currentRenderer, {
842
- id: "test-diff",
843
- diff: simpleDiff,
844
- view: "split",
845
- syntaxStyle,
846
- showLineNumbers: true,
847
- width: "100%",
848
- height: "100%",
849
- })
850
-
851
- currentRenderer.root.add(diffRenderable)
852
- await renderOnce()
853
-
854
- const frame = captureFrame()
855
- const frameLines = frame.split("\n")
856
-
857
- // In split view, both sides are on the same terminal line
858
- // Left side: line 2 is removed, Right side: line 2 is added
859
- const splitLine = frameLines.find((l) => l.includes('console.log("Hello, World!")'))
860
- expect(splitLine).toBeTruthy()
861
- // Should contain line 2 with - on left side
862
- expect(splitLine).toMatch(/^ *2 -/)
863
- // Should contain line 2 with + on right side (later in the same line)
864
- expect(splitLine).toMatch(/2 \+.*console\.log\("Hello, World!"\)/)
865
- })
866
-
867
- test("DiffRenderable - split view should not wrap lines prematurely", async () => {
868
- const syntaxStyle = SyntaxStyle.fromStyles({
869
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
870
- })
871
-
872
- // Create a diff with long lines that should fit in split view
873
- const longLineDiff = `--- a/test.js
874
- +++ b/test.js
875
- @@ -1,4 +1,4 @@
876
- class Calculator {
877
- - subtract(a: number, b: number): number {
878
- + subtract(a: number, b: number, c: number = 0): number {
879
- return a - b;
880
- }`
881
-
882
- const diffRenderable = new DiffRenderable(currentRenderer, {
883
- id: "test-diff",
884
- diff: longLineDiff,
885
- view: "split",
886
- syntaxStyle,
887
- showLineNumbers: true,
888
- wrapMode: "word",
889
- width: "100%",
890
- height: "100%",
891
- })
892
-
893
- currentRenderer.root.add(diffRenderable)
894
- await renderOnce()
895
-
896
- const frame = captureFrame()
897
- const frameLines = frame.split("\n")
898
-
899
- // Find the line with "subtract" on the left side
900
- const leftSubtractLine = frameLines.find((l) => l.includes("subtract") && l.includes("b: number):"))
901
- expect(leftSubtractLine).toBeTruthy()
902
-
903
- // The line should NOT be wrapped - "subtract(a: number, b: number):" should be on one line
904
- // In an 80-char terminal with split view, each side gets ~40 chars (minus line numbers)
905
- // "subtract(a: number, b: number):" is 34 chars, so it should fit without wrapping
906
- expect(leftSubtractLine).toMatch(/subtract\(a: number, b: number\):/)
907
-
908
- // Find the line with "subtract" on the right side - it might be on the same line or next line
909
- // The signature is longer and might wrap
910
- const rightSubtractLines = frameLines.filter((l) => l.includes("subtract") || l.includes("c: number"))
911
- expect(rightSubtractLines.length).toBeGreaterThan(0)
912
-
913
- // The key assertion is that the left side doesn't wrap prematurely
914
- // We've already verified that above
915
- })
916
-
917
- test("DiffRenderable - split view alignment with calculator diff", async () => {
918
- const syntaxStyle = SyntaxStyle.fromStyles({
919
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
920
- })
921
-
922
- const calculatorDiff = `--- a/calculator.ts
923
- +++ b/calculator.ts
924
- @@ -1,13 +1,20 @@
925
- class Calculator {
926
- add(a: number, b: number): number {
927
- return a + b;
928
- }
929
-
930
- - subtract(a: number, b: number): number {
931
- - return a - b;
932
- + subtract(a: number, b: number, c: number = 0): number {
933
- + return a - b - c;
934
- }
935
-
936
- multiply(a: number, b: number): number {
937
- return a * b;
938
- }
939
- +
940
- + divide(a: number, b: number): number {
941
- + if (b === 0) {
942
- + throw new Error("Division by zero");
943
- + }
944
- + return a / b;
945
- + }
946
- }`
947
-
948
- const diffRenderable = new DiffRenderable(currentRenderer, {
949
- id: "test-diff",
950
- diff: calculatorDiff,
951
- view: "split",
952
- syntaxStyle,
953
- showLineNumbers: true,
954
- wrapMode: "none",
955
- width: "100%",
956
- height: "100%",
957
- })
958
-
959
- currentRenderer.root.add(diffRenderable)
960
- await renderOnce()
961
-
962
- const frame = captureFrame()
963
- const frameLines = frame.split("\n")
964
-
965
- // Find the closing brace on the left (old line 13)
966
- const leftClosingBrace = frameLines.find((l) => l.match(/^\s*13\s+\}/))
967
- expect(leftClosingBrace).toBeTruthy()
968
-
969
- // Find the closing brace on the right (new line 20)
970
- const rightClosingBrace = frameLines.find((l) => l.match(/\s*20\s+\}/))
971
- expect(rightClosingBrace).toBeTruthy()
972
-
973
- // They should be on the SAME line in the output
974
- expect(leftClosingBrace).toBe(rightClosingBrace)
975
- })
976
-
977
- test("DiffRenderable - switching between unified and split views multiple times", async () => {
978
- const syntaxStyle = SyntaxStyle.fromStyles({
979
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
980
- })
981
-
982
- const diffRenderable = new DiffRenderable(currentRenderer, {
983
- id: "test-diff",
984
- diff: simpleDiff,
985
- view: "unified",
986
- syntaxStyle,
987
- showLineNumbers: true,
988
- width: "100%",
989
- height: "100%",
990
- })
991
-
992
- currentRenderer.root.add(diffRenderable)
993
- await renderOnce()
994
-
995
- // Step 1: Verify unified view works
996
- let frame = captureFrame()
997
- expect(frame).toContain("function hello")
998
- expect(frame).toContain('console.log("Hello")')
999
- expect(frame).toContain('console.log("Hello, World!")')
1000
-
1001
- // Step 2: Switch to split view
1002
- diffRenderable.view = "split"
1003
- await renderOnce()
1004
-
1005
- frame = captureFrame()
1006
- expect(frame).toContain("function hello")
1007
- expect(frame).toContain('console.log("Hello")')
1008
- expect(frame).toContain('console.log("Hello, World!")')
1009
-
1010
- // Step 3: Switch back to unified view
1011
- diffRenderable.view = "unified"
1012
- await renderOnce()
1013
-
1014
- frame = captureFrame()
1015
- expect(frame).toContain("function hello")
1016
- expect(frame).toContain('console.log("Hello")')
1017
- expect(frame).toContain('console.log("Hello, World!")')
1018
-
1019
- // Step 4: Switch to split view again (this currently fails)
1020
- diffRenderable.view = "split"
1021
- await renderOnce()
1022
-
1023
- frame = captureFrame()
1024
- expect(frame).toContain("function hello")
1025
- expect(frame).toContain('console.log("Hello")')
1026
- expect(frame).toContain('console.log("Hello, World!")')
1027
- })
1028
-
1029
- test("DiffRenderable - wrapMode works in unified view", async () => {
1030
- const syntaxStyle = SyntaxStyle.fromStyles({
1031
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1032
- })
1033
-
1034
- // Create a diff with a very long line that will wrap
1035
- const longLineDiff = `--- a/test.js
1036
- +++ b/test.js
1037
- @@ -1,3 +1,3 @@
1038
- function hello() {
1039
- - console.log("This is a very long line that should wrap when wrapMode is set to word but not when it is set to none");
1040
- + console.log("This is a very long line that has been modified and should wrap when wrapMode is set to word but not when it is set to none");
1041
- }`
1042
-
1043
- const diffRenderable = new DiffRenderable(currentRenderer, {
1044
- id: "test-diff",
1045
- diff: longLineDiff,
1046
- view: "unified",
1047
- syntaxStyle,
1048
- showLineNumbers: true,
1049
- wrapMode: "none",
1050
- width: 80,
1051
- height: "100%",
1052
- })
1053
-
1054
- currentRenderer.root.add(diffRenderable)
1055
- await renderOnce()
1056
-
1057
- // Capture with wrapMode: none
1058
- const frameNone = captureFrame()
1059
- expect(frameNone).toMatchSnapshot("wrapMode-none")
1060
-
1061
- // Change to wrapMode: word
1062
- diffRenderable.wrapMode = "word"
1063
- await renderOnce()
1064
-
1065
- // Capture with wrapMode: word
1066
- const frameWord = captureFrame()
1067
- expect(frameWord).toMatchSnapshot("wrapMode-word")
1068
-
1069
- // Frames should be different (word wrapping should create more lines)
1070
- expect(frameNone).not.toBe(frameWord)
1071
-
1072
- // Change back to wrapMode: none
1073
- diffRenderable.wrapMode = "none"
1074
- await renderOnce()
1075
-
1076
- // Should match the original
1077
- const frameNoneAgain = captureFrame()
1078
- expect(frameNoneAgain).toMatchSnapshot("wrapMode-none")
1079
- expect(frameNoneAgain).toBe(frameNone)
1080
- })
1081
-
1082
- test("DiffRenderable - split view with wrapMode honors wrapping alignment", async () => {
1083
- // Create a larger test renderer to fit the whole diff with wrapping
1084
- const testRenderer = await createTestRenderer({ width: 80, height: 40 })
1085
- const renderer = testRenderer.renderer
1086
- const renderOnce = testRenderer.renderOnce
1087
- const captureFrame = testRenderer.captureCharFrame
1088
-
1089
- const syntaxStyle = SyntaxStyle.fromStyles({
1090
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1091
- })
1092
-
1093
- const calculatorDiff = `--- a/calculator.ts
1094
- +++ b/calculator.ts
1095
- @@ -1,13 +1,20 @@
1096
- class Calculator {
1097
- add(a: number, b: number): number {
1098
- return a + b;
1099
- }
1100
-
1101
- - subtract(a: number, b: number): number {
1102
- - return a - b;
1103
- + subtract(a: number, b: number, c: number = 0): number {
1104
- + return a - b - c;
1105
- }
1106
-
1107
- multiply(a: number, b: number): number {
1108
- return a * b;
1109
- }
1110
- +
1111
- + divide(a: number, b: number): number {
1112
- + if (b === 0) {
1113
- + throw new Error("Division by zero");
1114
- + }
1115
- + return a / b;
1116
- + }
1117
- }`
1118
-
1119
- const diffRenderable = new DiffRenderable(renderer, {
1120
- id: "test-diff",
1121
- diff: calculatorDiff,
1122
- view: "split",
1123
- syntaxStyle,
1124
- showLineNumbers: true,
1125
- wrapMode: "word",
1126
- width: "100%",
1127
- height: "100%",
1128
- })
1129
-
1130
- renderer.root.add(diffRenderable)
1131
- await renderOnce()
1132
-
1133
- // Flush microtask-based deferred rebuild for wrap alignment
1134
- await Promise.resolve()
1135
- await renderOnce()
1136
-
1137
- const frame = captureFrame()
1138
- const frameLines = frame.split("\n")
1139
-
1140
- // Find the closing brace on the left (old line 13)
1141
- const leftClosingBraceLine = frameLines.find((l) => l.match(/^\s*13\s+\}/))
1142
- expect(leftClosingBraceLine).toBeTruthy()
1143
-
1144
- // Find the closing brace on the right (new line 20)
1145
- const rightClosingBraceLine = frameLines.find((l) => l.match(/\s*20\s+\}/))
1146
- expect(rightClosingBraceLine).toBeTruthy()
1147
-
1148
- // They should be on the SAME line in the output (same visual row)
1149
- // even though the right side has wrapped lines above it
1150
- expect(leftClosingBraceLine).toBe(rightClosingBraceLine)
1151
-
1152
- // Both sides should have the same number of final visual lines
1153
- // (counting both logical lines and wrap continuations)
1154
- // This is hard to assert directly, but if alignment is correct,
1155
- // the closing braces being on the same line proves it worked
1156
-
1157
- // Clean up
1158
- renderer.destroy()
1159
- })
1160
-
1161
- test("DiffRenderable - context lines show new line numbers in unified view", async () => {
1162
- // Create a larger test renderer to fit the whole diff
1163
- const testRenderer = await createTestRenderer({ width: 80, height: 30 })
1164
- const renderer = testRenderer.renderer
1165
- const renderOnce = testRenderer.renderOnce
1166
- const captureFrame = testRenderer.captureCharFrame
1167
-
1168
- const syntaxStyle = SyntaxStyle.fromStyles({
1169
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1170
- })
1171
-
1172
- // This diff adds lines in the middle, so context lines after additions
1173
- // should show their NEW line numbers, not old ones
1174
- const calculatorDiff = `--- a/calculator.ts
1175
- +++ b/calculator.ts
1176
- @@ -1,13 +1,20 @@
1177
- class Calculator {
1178
- add(a: number, b: number): number {
1179
- return a + b;
1180
- }
1181
-
1182
- - subtract(a: number, b: number): number {
1183
- - return a - b;
1184
- + subtract(a: number, b: number, c: number = 0): number {
1185
- + return a - b - c;
1186
- }
1187
-
1188
- multiply(a: number, b: number): number {
1189
- return a * b;
1190
- }
1191
- +
1192
- + divide(a: number, b: number): number {
1193
- + if (b === 0) {
1194
- + throw new Error("Division by zero");
1195
- + }
1196
- + return a / b;
1197
- + }
1198
- }`
1199
-
1200
- const diffRenderable = new DiffRenderable(renderer, {
1201
- id: "test-diff",
1202
- diff: calculatorDiff,
1203
- view: "unified",
1204
- syntaxStyle,
1205
- showLineNumbers: true,
1206
- width: "100%",
1207
- height: "100%",
1208
- })
1209
-
1210
- renderer.root.add(diffRenderable)
1211
- await renderOnce()
1212
-
1213
- const frame = captureFrame()
1214
- const frameLines = frame.split("\n")
1215
-
1216
- // The closing brace "}" for the Calculator class is a context line
1217
- // In the old file it was at line 13
1218
- // In the new file it's at line 20 (after adding 7 lines for divide method)
1219
- // Unified view should show line 20, not line 13
1220
- // Find the LAST closing brace that's just "}" (at the beginning of indentation, not nested)
1221
- // This regex matches: optional spaces, digits, spaces, optional sign (+/-), spaces, "}", trailing spaces
1222
- const closingBraceLines = frameLines.filter((l) => l.match(/^\s*\d+\s+[+-]?\s*\}\s*$/))
1223
-
1224
- // The last one should be the class closing brace
1225
- const classClosingBraceLine = closingBraceLines[closingBraceLines.length - 1]
1226
- expect(classClosingBraceLine).toBeTruthy()
1227
-
1228
- // Extract the line number from the closing brace line
1229
- const lineNumberMatch = classClosingBraceLine!.match(/^\s*(\d+)/)
1230
- expect(lineNumberMatch).toBeTruthy()
1231
-
1232
- const lineNumber = parseInt(lineNumberMatch![1])
1233
-
1234
- // The closing brace should show line 20 (new file position), not 13 (old file position)
1235
- expect(lineNumber).toBe(20)
1236
-
1237
- // Clean up
1238
- renderer.destroy()
1239
- })
1240
-
1241
- test("DiffRenderable - multiple hunks in unified view", async () => {
1242
- const syntaxStyle = SyntaxStyle.fromStyles({
1243
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1244
- })
1245
-
1246
- // Diff with three separate hunks
1247
- const multiHunkDiff = `--- a/file.js
1248
- +++ b/file.js
1249
- @@ -1,3 +1,3 @@
1250
- function first() {
1251
- - return 1;
1252
- + return "one";
1253
- }
1254
- @@ -15,4 +15,5 @@
1255
- function second() {
1256
- var x = 10;
1257
- + var y = 20;
1258
- return x;
1259
- }
1260
- @@ -30,3 +31,3 @@
1261
- function third() {
1262
- - console.log("old");
1263
- + console.log("new");
1264
- }`
1265
-
1266
- const diffRenderable = new DiffRenderable(currentRenderer, {
1267
- id: "test-diff",
1268
- diff: multiHunkDiff,
1269
- view: "unified",
1270
- syntaxStyle,
1271
- showLineNumbers: true,
1272
- width: "100%",
1273
- height: "100%",
1274
- })
1275
-
1276
- currentRenderer.root.add(diffRenderable)
1277
- await renderOnce()
1278
-
1279
- const frame = captureFrame()
1280
- expect(frame).toMatchSnapshot("unified view multiple hunks")
1281
-
1282
- // All three hunks should be present
1283
- expect(frame).toContain('return "one"')
1284
- expect(frame).toContain("var y = 20")
1285
- expect(frame).toContain('console.log("new")')
1286
-
1287
- // Line numbers should be correct for each hunk
1288
- const frameLines = frame.split("\n")
1289
-
1290
- // First hunk around line 2
1291
- const firstHunkLine = frameLines.find((l) => l.includes('return "one"'))
1292
- expect(firstHunkLine).toMatch(/2 \+/)
1293
-
1294
- // Second hunk around line 17 (added line)
1295
- const secondHunkLine = frameLines.find((l) => l.includes("var y = 20"))
1296
- expect(secondHunkLine).toMatch(/17 \+/)
1297
-
1298
- // Third hunk around line 32
1299
- const thirdHunkLine = frameLines.find((l) => l.includes('console.log("new")'))
1300
- expect(thirdHunkLine).toMatch(/32 \+/)
1301
- })
1302
-
1303
- test("DiffRenderable - multiple hunks in split view", async () => {
1304
- const syntaxStyle = SyntaxStyle.fromStyles({
1305
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1306
- })
1307
-
1308
- const multiHunkDiff = `--- a/file.js
1309
- +++ b/file.js
1310
- @@ -1,3 +1,3 @@
1311
- function first() {
1312
- - return 1;
1313
- + return "one";
1314
- }
1315
- @@ -15,4 +15,5 @@
1316
- function second() {
1317
- var x = 10;
1318
- + var y = 20;
1319
- return x;
1320
- }
1321
- @@ -30,3 +31,3 @@
1322
- function third() {
1323
- - console.log("old");
1324
- + console.log("new");
1325
- }`
1326
-
1327
- const diffRenderable = new DiffRenderable(currentRenderer, {
1328
- id: "test-diff",
1329
- diff: multiHunkDiff,
1330
- view: "split",
1331
- syntaxStyle,
1332
- showLineNumbers: true,
1333
- width: "100%",
1334
- height: "100%",
1335
- })
1336
-
1337
- currentRenderer.root.add(diffRenderable)
1338
- await renderOnce()
1339
-
1340
- const frame = captureFrame()
1341
- expect(frame).toMatchSnapshot("split view multiple hunks")
1342
-
1343
- // All three hunks should be present in split view
1344
- expect(frame).toContain('return "one"')
1345
- expect(frame).toContain("var y = 20")
1346
- expect(frame).toContain('console.log("new")')
1347
-
1348
- // Both old and new content should be visible
1349
- expect(frame).toContain("return 1")
1350
- expect(frame).toContain('console.log("old")')
1351
- })
1352
-
1353
- test("DiffRenderable - no newline at end of file in unified view", async () => {
1354
- const syntaxStyle = SyntaxStyle.fromStyles({
1355
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1356
- })
1357
-
1358
- const noNewlineDiff = `--- a/test.js
1359
- +++ b/test.js
1360
- @@ -1,3 +1,3 @@
1361
- line1
1362
- line2
1363
- -line3
1364
- \
1365
- +line3_modified
1366
- \`
1367
-
1368
- const diffRenderable = new DiffRenderable(currentRenderer, {
1369
- id: "test-diff",
1370
- diff: noNewlineDiff,
1371
- view: "unified",
1372
- syntaxStyle,
1373
- showLineNumbers: true,
1374
- width: "100%",
1375
- height: "100%",
1376
- })
1377
-
1378
- currentRenderer.root.add(diffRenderable)
1379
- await renderOnce()
1380
-
1381
- const frame = captureFrame()
1382
- expect(frame).toMatchSnapshot("unified view with no newline marker")
1383
-
1384
- // Should show both old and new versions
1385
- expect(frame).toContain("line3")
1386
- expect(frame).toContain("line3_modified")
1387
-
1388
- // Should NOT show the "No newline" marker as content
1389
- // (it's a special marker that should be skipped)
1390
- const frameLines = frame.split("\n")
1391
- const markerLines = frameLines.filter((l) => l.includes("No newline at end of file"))
1392
- expect(markerLines.length).toBe(0)
1393
- })
1394
-
1395
- test("DiffRenderable - no newline at end of file in split view", async () => {
1396
- const syntaxStyle = SyntaxStyle.fromStyles({
1397
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1398
- })
1399
-
1400
- const noNewlineDiff = `--- a/test.js
1401
- +++ b/test.js
1402
- @@ -1,3 +1,3 @@
1403
- line1
1404
- line2
1405
- -line3
1406
- \
1407
- +line3_modified
1408
- \`
1409
-
1410
- const diffRenderable = new DiffRenderable(currentRenderer, {
1411
- id: "test-diff",
1412
- diff: noNewlineDiff,
1413
- view: "split",
1414
- syntaxStyle,
1415
- showLineNumbers: true,
1416
- width: "100%",
1417
- height: "100%",
1418
- })
1419
-
1420
- currentRenderer.root.add(diffRenderable)
1421
- await renderOnce()
1422
-
1423
- const frame = captureFrame()
1424
- expect(frame).toMatchSnapshot("split view with no newline marker")
1425
-
1426
- // Both sides should show their respective versions
1427
- expect(frame).toContain("line3")
1428
- expect(frame).toContain("line3_modified")
1429
-
1430
- // Should NOT show the "No newline" marker
1431
- const frameLines = frame.split("\n")
1432
- const markerLines = frameLines.filter((l) => l.includes("No newline at end of file"))
1433
- expect(markerLines.length).toBe(0)
1434
- })
1435
-
1436
- test("DiffRenderable - asymmetric block with more removes than adds in split view", async () => {
1437
- const syntaxStyle = SyntaxStyle.fromStyles({
1438
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1439
- })
1440
-
1441
- const asymmetricDiff = `--- a/test.js
1442
- +++ b/test.js
1443
- @@ -1,7 +1,4 @@
1444
- context_before
1445
- -remove1
1446
- -remove2
1447
- -remove3
1448
- -remove4
1449
- -remove5
1450
- +add1
1451
- +add2
1452
- context_after`
1453
-
1454
- const diffRenderable = new DiffRenderable(currentRenderer, {
1455
- id: "test-diff",
1456
- diff: asymmetricDiff,
1457
- view: "split",
1458
- syntaxStyle,
1459
- showLineNumbers: true,
1460
- width: "100%",
1461
- height: "100%",
1462
- })
1463
-
1464
- currentRenderer.root.add(diffRenderable)
1465
- await renderOnce()
1466
-
1467
- const frame = captureFrame()
1468
- expect(frame).toMatchSnapshot("split view asymmetric block more removes")
1469
-
1470
- // Left side should have all 5 removes
1471
- expect(frame).toContain("remove1")
1472
- expect(frame).toContain("remove2")
1473
- expect(frame).toContain("remove3")
1474
- expect(frame).toContain("remove4")
1475
- expect(frame).toContain("remove5")
1476
-
1477
- // Right side should have 2 adds
1478
- expect(frame).toContain("add1")
1479
- expect(frame).toContain("add2")
1480
-
1481
- // Context lines should appear on both sides at the same visual position
1482
- const frameLines = frame.split("\n")
1483
- const contextBeforeLines = frameLines.filter((l) => l.includes("context_before"))
1484
- const contextAfterLines = frameLines.filter((l) => l.includes("context_after"))
1485
-
1486
- // context_before should appear once (on same visual line for both sides)
1487
- expect(contextBeforeLines.length).toBeGreaterThanOrEqual(1)
1488
-
1489
- // context_after should appear once (on same visual line for both sides)
1490
- expect(contextAfterLines.length).toBeGreaterThanOrEqual(1)
1491
-
1492
- // The right side should have empty padding lines to align with left side's extra removes
1493
- // We can verify this by checking that context_after appears at similar vertical positions
1494
- })
1495
-
1496
- test("DiffRenderable - asymmetric block with more adds than removes in split view", async () => {
1497
- const syntaxStyle = SyntaxStyle.fromStyles({
1498
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1499
- })
1500
-
1501
- const asymmetricDiff = `--- a/test.js
1502
- +++ b/test.js
1503
- @@ -1,4 +1,7 @@
1504
- context_before
1505
- -remove1
1506
- -remove2
1507
- +add1
1508
- +add2
1509
- +add3
1510
- +add4
1511
- +add5
1512
- context_after`
1513
-
1514
- const diffRenderable = new DiffRenderable(currentRenderer, {
1515
- id: "test-diff",
1516
- diff: asymmetricDiff,
1517
- view: "split",
1518
- syntaxStyle,
1519
- showLineNumbers: true,
1520
- width: "100%",
1521
- height: "100%",
1522
- })
1523
-
1524
- currentRenderer.root.add(diffRenderable)
1525
- await renderOnce()
1526
-
1527
- const frame = captureFrame()
1528
- expect(frame).toMatchSnapshot("split view asymmetric block more adds")
1529
-
1530
- // Left side should have 2 removes
1531
- expect(frame).toContain("remove1")
1532
- expect(frame).toContain("remove2")
1533
-
1534
- // Right side should have all 5 adds
1535
- expect(frame).toContain("add1")
1536
- expect(frame).toContain("add2")
1537
- expect(frame).toContain("add3")
1538
- expect(frame).toContain("add4")
1539
- expect(frame).toContain("add5")
1540
-
1541
- // Context lines should be aligned
1542
- const frameLines = frame.split("\n")
1543
- const contextBeforeLines = frameLines.filter((l) => l.includes("context_before"))
1544
- const contextAfterLines = frameLines.filter((l) => l.includes("context_after"))
1545
-
1546
- expect(contextBeforeLines.length).toBeGreaterThanOrEqual(1)
1547
- expect(contextAfterLines.length).toBeGreaterThanOrEqual(1)
1548
- })
1549
-
1550
- test("DiffRenderable - back-to-back change blocks without context lines in split view", async () => {
1551
- const syntaxStyle = SyntaxStyle.fromStyles({
1552
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1553
- })
1554
-
1555
- const backToBackDiff = `--- a/test.js
1556
- +++ b/test.js
1557
- @@ -1,4 +1,4 @@
1558
- -remove1
1559
- -remove2
1560
- -remove3
1561
- -remove4
1562
- +add1
1563
- +add2
1564
- +add3
1565
- +add4`
1566
-
1567
- const diffRenderable = new DiffRenderable(currentRenderer, {
1568
- id: "test-diff",
1569
- diff: backToBackDiff,
1570
- view: "split",
1571
- syntaxStyle,
1572
- showLineNumbers: true,
1573
- width: "100%",
1574
- height: "100%",
1575
- })
1576
-
1577
- currentRenderer.root.add(diffRenderable)
1578
- await renderOnce()
1579
-
1580
- const frame = captureFrame()
1581
- expect(frame).toMatchSnapshot("split view back-to-back blocks")
1582
-
1583
- // All removes should be on left
1584
- expect(frame).toContain("remove1")
1585
- expect(frame).toContain("remove2")
1586
- expect(frame).toContain("remove3")
1587
- expect(frame).toContain("remove4")
1588
-
1589
- // All adds should be on right
1590
- expect(frame).toContain("add1")
1591
- expect(frame).toContain("add2")
1592
- expect(frame).toContain("add3")
1593
- expect(frame).toContain("add4")
1594
-
1595
- // Both sides should have same number of visual lines (with alignment)
1596
- const frameLines = frame.split("\n").filter((l) => l.trim().length > 0)
1597
- expect(frameLines.length).toBeGreaterThan(0)
1598
- })
1599
-
1600
- test("DiffRenderable - very long lines wrapping multiple times in split view", async () => {
1601
- const syntaxStyle = SyntaxStyle.fromStyles({
1602
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1603
- })
1604
-
1605
- const longLineDiff = `--- a/test.js
1606
- +++ b/test.js
1607
- @@ -1,3 +1,3 @@
1608
- short line
1609
- -This is an extremely long line that will definitely wrap multiple times when rendered in a split view with word wrapping enabled because it contains so many words and characters
1610
- +This is an extremely long line that has been modified and will definitely wrap multiple times when rendered in a split view with word wrapping enabled because it contains so many words and characters and even more content
1611
- another short line`
1612
-
1613
- const diffRenderable = new DiffRenderable(currentRenderer, {
1614
- id: "test-diff",
1615
- diff: longLineDiff,
1616
- view: "split",
1617
- syntaxStyle,
1618
- showLineNumbers: true,
1619
- wrapMode: "word",
1620
- width: "100%",
1621
- height: "100%",
1622
- })
1623
-
1624
- currentRenderer.root.add(diffRenderable)
1625
- await renderOnce()
1626
-
1627
- // Flush microtask-based wrap alignment
1628
- await Promise.resolve()
1629
- await renderOnce()
1630
-
1631
- const frame = captureFrame()
1632
- expect(frame).toMatchSnapshot("split view multi-wrap lines")
1633
-
1634
- // Both versions of the long line should be present
1635
- expect(frame).toContain("extremely long line")
1636
- expect(frame).toContain("has been modified")
1637
-
1638
- // Short lines should still be aligned
1639
- expect(frame).toContain("short line")
1640
- expect(frame).toContain("another short line")
1641
-
1642
- const frameLines = frame.split("\n")
1643
-
1644
- // Find the "another short line" on both sides
1645
- const shortLineMatches = frameLines.filter((l) => l.includes("another short line"))
1646
-
1647
- // Should appear (on the same visual line in split view)
1648
- expect(shortLineMatches.length).toBeGreaterThanOrEqual(1)
1649
- })
1650
-
1651
- test("DiffRenderable - rapid diff updates trigger microtask coalescing", async () => {
1652
- const syntaxStyle = SyntaxStyle.fromStyles({
1653
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1654
- })
1655
-
1656
- const diffRenderable = new DiffRenderable(currentRenderer, {
1657
- id: "test-diff",
1658
- diff: simpleDiff,
1659
- view: "split",
1660
- syntaxStyle,
1661
- showLineNumbers: true,
1662
- wrapMode: "word",
1663
- width: "100%",
1664
- height: "100%",
1665
- })
1666
-
1667
- currentRenderer.root.add(diffRenderable)
1668
- await renderOnce()
1669
-
1670
- // Rapidly update the diff multiple times
1671
- diffRenderable.diff = multiLineDiff
1672
- diffRenderable.diff = addOnlyDiff
1673
- diffRenderable.diff = removeOnlyDiff
1674
- diffRenderable.diff = simpleDiff
1675
-
1676
- // Flush microtask-based coalesced rebuild
1677
- await Promise.resolve()
1678
- await renderOnce()
1679
-
1680
- const frame = captureFrame()
1681
-
1682
- // Should show the final diff (simpleDiff)
1683
- expect(frame).toContain("function hello")
1684
- expect(frame).toContain('console.log("Hello")')
1685
- expect(frame).toContain('console.log("Hello, World!")')
1686
-
1687
- // Should NOT show content from intermediate diffs
1688
- expect(frame).not.toContain("subtract")
1689
- expect(frame).not.toContain("newFunction")
1690
- expect(frame).not.toContain("oldFunction")
1691
- })
1692
-
1693
- test("DiffRenderable - explicit content background colors differ from gutter", async () => {
1694
- const syntaxStyle = SyntaxStyle.fromStyles({
1695
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1696
- })
1697
-
1698
- const diffRenderable = new DiffRenderable(currentRenderer, {
1699
- id: "test-diff",
1700
- diff: simpleDiff,
1701
- view: "unified",
1702
- syntaxStyle,
1703
- showLineNumbers: true,
1704
- addedBg: "#1a4d1a",
1705
- removedBg: "#4d1a1a",
1706
- addedContentBg: "#2a5d2a",
1707
- removedContentBg: "#5d2a2a",
1708
- width: "100%",
1709
- height: "100%",
1710
- })
1711
-
1712
- currentRenderer.root.add(diffRenderable)
1713
- await renderOnce()
1714
-
1715
- const frame = captureFrame()
1716
-
1717
- // Verify content is rendered
1718
- expect(frame).toContain("function hello")
1719
- expect(frame).toContain('console.log("Hello")')
1720
- expect(frame).toContain('console.log("Hello, World!")')
1721
-
1722
- // Verify properties are set correctly
1723
- expect(diffRenderable.addedBg).toEqual(RGBA.fromHex("#1a4d1a"))
1724
- expect(diffRenderable.removedBg).toEqual(RGBA.fromHex("#4d1a1a"))
1725
- expect(diffRenderable.addedContentBg).toEqual(RGBA.fromHex("#2a5d2a"))
1726
- expect(diffRenderable.removedContentBg).toEqual(RGBA.fromHex("#5d2a2a"))
1727
-
1728
- // Test that we can update them
1729
- diffRenderable.addedContentBg = "#3a6d3a"
1730
- expect(diffRenderable.addedContentBg).toEqual(RGBA.fromHex("#3a6d3a"))
1731
-
1732
- await renderOnce()
1733
- const frame2 = captureFrame()
1734
-
1735
- // Should still render correctly after update
1736
- expect(frame2).toContain("function hello")
1737
- })
1738
-
1739
- test("DiffRenderable - malformed diff string handled gracefully", async () => {
1740
- const syntaxStyle = SyntaxStyle.fromStyles({
1741
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1742
- })
1743
-
1744
- const malformedDiff = `This is not a valid diff format
1745
- Just some random text
1746
- Without proper headers`
1747
-
1748
- const diffRenderable = new DiffRenderable(currentRenderer, {
1749
- id: "test-diff",
1750
- diff: malformedDiff,
1751
- view: "unified",
1752
- syntaxStyle,
1753
- width: "100%",
1754
- height: "100%",
1755
- })
1756
-
1757
- currentRenderer.root.add(diffRenderable)
1758
-
1759
- // Should not crash when rendering malformed diff
1760
- await renderOnce()
1761
-
1762
- const frame = captureFrame()
1763
-
1764
- // Should render empty/blank since diff can't be parsed
1765
- // The important thing is it doesn't crash
1766
- expect(diffRenderable.diff).toBe(malformedDiff)
1767
- })
1768
-
1769
- test("DiffRenderable - invalid diff format shows error with raw diff", async () => {
1770
- const syntaxStyle = SyntaxStyle.fromStyles({
1771
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1772
- })
1773
-
1774
- // This diff has a malformed hunk header that will cause parsePatch to throw
1775
- // The hunk header must have the format @@ -oldStart,oldLines +newStart,newLines @@
1776
- const invalidDiff = `--- a/test.js
1777
- +++ b/test.js
1778
- @@ -a,b +c,d @@
1779
- function hello() {
1780
- - console.log("Hello");
1781
- + console.log("Hello, World!");
1782
- }`
1783
-
1784
- const diffRenderable = new DiffRenderable(currentRenderer, {
1785
- id: "test-diff",
1786
- diff: invalidDiff,
1787
- view: "unified",
1788
- syntaxStyle,
1789
- width: "100%",
1790
- height: "100%",
1791
- })
1792
-
1793
- currentRenderer.root.add(diffRenderable)
1794
-
1795
- // Should not crash when rendering invalid diff
1796
- await renderOnce()
1797
-
1798
- const frame = captureFrame()
1799
- expect(frame).toMatchSnapshot("invalid diff format with error")
1800
-
1801
- // Should contain error message (the error from parsePatch)
1802
- expect(frame).toContain("Unknown line")
1803
-
1804
- // Should show the raw diff content
1805
- expect(frame).toContain("@@ -a,b +c,d @@")
1806
- expect(frame).toContain("function hello")
1807
- })
1808
-
1809
- test("DiffRenderable - diff with only context lines (no changes)", async () => {
1810
- const syntaxStyle = SyntaxStyle.fromStyles({
1811
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1812
- })
1813
-
1814
- const contextOnlyDiff = `--- a/test.js
1815
- +++ b/test.js
1816
- @@ -1,5 +1,5 @@
1817
- line1
1818
- line2
1819
- line3
1820
- line4
1821
- line5`
1822
-
1823
- const diffRenderable = new DiffRenderable(currentRenderer, {
1824
- id: "test-diff",
1825
- diff: contextOnlyDiff,
1826
- view: "unified",
1827
- syntaxStyle,
1828
- showLineNumbers: true,
1829
- width: "100%",
1830
- height: "100%",
1831
- })
1832
-
1833
- currentRenderer.root.add(diffRenderable)
1834
- await renderOnce()
1835
-
1836
- const frame = captureFrame()
1837
- expect(frame).toMatchSnapshot("diff with only context lines")
1838
-
1839
- // All lines should be present as context
1840
- expect(frame).toContain("line1")
1841
- expect(frame).toContain("line2")
1842
- expect(frame).toContain("line3")
1843
- expect(frame).toContain("line4")
1844
- expect(frame).toContain("line5")
1845
-
1846
- // No +/- signs should be present (only context)
1847
- const frameLines = frame.split("\n")
1848
- const changedLines = frameLines.filter((l) => l.match(/[+-]\s*line/))
1849
- expect(changedLines.length).toBe(0)
1850
- })
1851
-
1852
- test("DiffRenderable - should not leak listeners on unified view updates", async () => {
1853
- const syntaxStyle = SyntaxStyle.fromStyles({
1854
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1855
- })
1856
-
1857
- const diffRenderable = new DiffRenderable(currentRenderer, {
1858
- id: "test-diff",
1859
- diff: simpleDiff,
1860
- view: "unified",
1861
- syntaxStyle,
1862
- width: "100%",
1863
- height: "100%",
1864
- })
1865
-
1866
- currentRenderer.root.add(diffRenderable)
1867
- await renderOnce()
1868
-
1869
- // Get the underlying CodeRenderable (leftCodeRenderable in unified view)
1870
- const codeRenderable = (diffRenderable as any).leftCodeRenderable
1871
- expect(codeRenderable).toBeDefined()
1872
-
1873
- // Check initial listener count
1874
- const initialListenerCount = codeRenderable.listenerCount("line-info-change")
1875
- expect(initialListenerCount).toBeGreaterThanOrEqual(1)
1876
-
1877
- // Update the diff multiple times - this should not add more listeners
1878
- for (let i = 0; i < 10; i++) {
1879
- diffRenderable.diff = simpleDiff.replace('"Hello"', `"Hello${i}"`)
1880
- await renderOnce()
1881
- }
1882
-
1883
- // Check that listener count hasn't grown
1884
- const finalListenerCount = codeRenderable.listenerCount("line-info-change")
1885
- expect(finalListenerCount).toBe(initialListenerCount)
1886
- })
1887
-
1888
- test("DiffRenderable - should not leak listeners on split view updates", async () => {
1889
- const syntaxStyle = SyntaxStyle.fromStyles({
1890
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1891
- })
1892
-
1893
- const diffRenderable = new DiffRenderable(currentRenderer, {
1894
- id: "test-diff",
1895
- diff: simpleDiff,
1896
- view: "split",
1897
- syntaxStyle,
1898
- width: "100%",
1899
- height: "100%",
1900
- })
1901
-
1902
- currentRenderer.root.add(diffRenderable)
1903
- await renderOnce()
1904
-
1905
- // Get the underlying CodeRenderables
1906
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
1907
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
1908
- expect(leftCodeRenderable).toBeDefined()
1909
- expect(rightCodeRenderable).toBeDefined()
1910
-
1911
- // Check initial listener counts
1912
- const leftInitialCount = leftCodeRenderable.listenerCount("line-info-change")
1913
- const rightInitialCount = rightCodeRenderable.listenerCount("line-info-change")
1914
- expect(leftInitialCount).toBeGreaterThanOrEqual(1)
1915
- expect(rightInitialCount).toBeGreaterThanOrEqual(1)
1916
-
1917
- // Update the diff multiple times - this should not add more listeners
1918
- for (let i = 0; i < 10; i++) {
1919
- diffRenderable.diff = simpleDiff.replace('"Hello"', `"Hello${i}"`)
1920
- await renderOnce()
1921
- }
1922
-
1923
- // Check that listener counts haven't grown
1924
- const leftFinalCount = leftCodeRenderable.listenerCount("line-info-change")
1925
- const rightFinalCount = rightCodeRenderable.listenerCount("line-info-change")
1926
- expect(leftFinalCount).toBe(leftInitialCount)
1927
- expect(rightFinalCount).toBe(rightInitialCount)
1928
- })
1929
-
1930
- test("DiffRenderable - should not leak listeners when switching views", async () => {
1931
- const syntaxStyle = SyntaxStyle.fromStyles({
1932
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1933
- })
1934
-
1935
- const diffRenderable = new DiffRenderable(currentRenderer, {
1936
- id: "test-diff",
1937
- diff: simpleDiff,
1938
- view: "unified",
1939
- syntaxStyle,
1940
- width: "100%",
1941
- height: "100%",
1942
- })
1943
-
1944
- currentRenderer.root.add(diffRenderable)
1945
- await renderOnce()
1946
-
1947
- // Get initial renderables
1948
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
1949
- expect(leftCodeRenderable).toBeDefined()
1950
- const initialLeftCount = leftCodeRenderable.listenerCount("line-info-change")
1951
-
1952
- // Switch to split view and back multiple times
1953
- for (let i = 0; i < 5; i++) {
1954
- diffRenderable.view = "split"
1955
- await renderOnce()
1956
-
1957
- diffRenderable.view = "unified"
1958
- await renderOnce()
1959
- }
1960
-
1961
- const finalLeftCount = leftCodeRenderable.listenerCount("line-info-change")
1962
-
1963
- // Listener count should remain stable (allow some flexibility for implementation details)
1964
- expect(finalLeftCount).toBeLessThanOrEqual(initialLeftCount + 2)
1965
- })
1966
-
1967
- test("DiffRenderable - should not leak listeners on rapid property changes", async () => {
1968
- const syntaxStyle = SyntaxStyle.fromStyles({
1969
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
1970
- })
1971
-
1972
- const diffRenderable = new DiffRenderable(currentRenderer, {
1973
- id: "test-diff",
1974
- diff: simpleDiff,
1975
- view: "split",
1976
- syntaxStyle,
1977
- width: "100%",
1978
- height: "100%",
1979
- })
1980
-
1981
- currentRenderer.root.add(diffRenderable)
1982
- await renderOnce()
1983
-
1984
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
1985
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
1986
- const leftInitialCount = leftCodeRenderable.listenerCount("line-info-change")
1987
- const rightInitialCount = rightCodeRenderable.listenerCount("line-info-change")
1988
-
1989
- // Make rapid changes that trigger rebuilds
1990
- for (let i = 0; i < 10; i++) {
1991
- diffRenderable.wrapMode = i % 2 === 0 ? "word" : "char"
1992
- diffRenderable.addedBg = i % 2 === 0 ? "#ff0000" : "#00ff00"
1993
- diffRenderable.removedBg = i % 2 === 0 ? "#0000ff" : "#ffff00"
1994
- await renderOnce()
1995
- }
1996
-
1997
- const leftFinalCount = leftCodeRenderable.listenerCount("line-info-change")
1998
- const rightFinalCount = rightCodeRenderable.listenerCount("line-info-change")
1999
-
2000
- // Listener counts should remain stable
2001
- expect(leftFinalCount).toBe(leftInitialCount)
2002
- expect(rightFinalCount).toBe(rightInitialCount)
2003
- })
2004
-
2005
- test("DiffRenderable - can toggle conceal with markdown diff", async () => {
2006
- const syntaxStyle = SyntaxStyle.fromStyles({
2007
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2008
- })
2009
-
2010
- const mockClient = new MockTreeSitterClient()
2011
-
2012
- const markdownDiff = `--- a/test.md
2013
- +++ b/test.md
2014
- @@ -1,3 +1,3 @@
2015
- First line
2016
- -Some text **old**
2017
- +Some text **boldtext** and *italic*
2018
- End line`
2019
-
2020
- const mockHighlightsWithConceal: SimpleHighlight[] = [
2021
- [21, 23, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // **
2022
- [31, 33, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // **
2023
- [38, 39, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // *
2024
- [45, 46, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // *
2025
- ]
2026
-
2027
- mockClient.setMockResult({ highlights: mockHighlightsWithConceal })
2028
-
2029
- const diffRenderable = new DiffRenderable(currentRenderer, {
2030
- id: "test-diff",
2031
- diff: markdownDiff,
2032
- view: "unified",
2033
- syntaxStyle,
2034
- filetype: "markdown",
2035
- conceal: true,
2036
- treeSitterClient: mockClient,
2037
- width: "100%",
2038
- height: "100%",
2039
- })
2040
-
2041
- currentRenderer.root.add(diffRenderable)
2042
- await settleDiffHighlighting(diffRenderable, mockClient, renderOnce)
2043
-
2044
- const frameWithConceal = captureFrame()
2045
- expect(frameWithConceal).toMatchSnapshot("markdown diff with conceal enabled")
2046
- expect(diffRenderable.conceal).toBe(true)
2047
-
2048
- diffRenderable.conceal = false
2049
- await settleDiffHighlighting(diffRenderable, mockClient, renderOnce)
2050
-
2051
- const frameWithoutConceal = captureFrame()
2052
- expect(frameWithoutConceal).toMatchSnapshot("markdown diff with conceal disabled")
2053
- expect(diffRenderable.conceal).toBe(false)
2054
-
2055
- expect(frameWithConceal).not.toBe(frameWithoutConceal)
2056
-
2057
- diffRenderable.conceal = true
2058
- await settleDiffHighlighting(diffRenderable, mockClient, renderOnce)
2059
-
2060
- const frameWithConcealAgain = captureFrame()
2061
- expect(frameWithConcealAgain).toBe(frameWithConceal)
2062
- })
2063
-
2064
- test("DiffRenderable - conceal works in split view", async () => {
2065
- const syntaxStyle = SyntaxStyle.fromStyles({
2066
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2067
- })
2068
-
2069
- const mockClient = new MockTreeSitterClient()
2070
-
2071
- const markdownDiff = `--- a/test.md
2072
- +++ b/test.md
2073
- @@ -1,3 +1,3 @@
2074
- First line
2075
- -Some **old** text
2076
- +Some **new** text
2077
- End line`
2078
-
2079
- const mockHighlightsWithConceal: SimpleHighlight[] = [
2080
- [16, 18, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // **
2081
- [21, 23, "conceal", { isInjection: true, injectionLang: "markdown_inline", conceal: "" }], // **
2082
- ]
2083
-
2084
- mockClient.setMockResult({ highlights: mockHighlightsWithConceal })
2085
-
2086
- const diffRenderable = new DiffRenderable(currentRenderer, {
2087
- id: "test-diff",
2088
- diff: markdownDiff,
2089
- view: "split",
2090
- syntaxStyle,
2091
- filetype: "markdown",
2092
- conceal: true,
2093
- treeSitterClient: mockClient,
2094
- width: "100%",
2095
- height: "100%",
2096
- })
2097
-
2098
- currentRenderer.root.add(diffRenderable)
2099
- await settleDiffHighlighting(diffRenderable, mockClient, renderOnce)
2100
-
2101
- const frameWithConceal = captureFrame()
2102
- expect(frameWithConceal).toMatchSnapshot("split view markdown diff with conceal enabled")
2103
- expect(diffRenderable.conceal).toBe(true)
2104
-
2105
- diffRenderable.conceal = false
2106
- await settleDiffHighlighting(diffRenderable, mockClient, renderOnce)
2107
-
2108
- const frameWithoutConceal = captureFrame()
2109
- expect(frameWithoutConceal).toMatchSnapshot("split view markdown diff with conceal disabled")
2110
- expect(diffRenderable.conceal).toBe(false)
2111
-
2112
- expect(frameWithConceal).not.toBe(frameWithoutConceal)
2113
- })
2114
-
2115
- test("DiffRenderable - conceal defaults to false when not specified", async () => {
2116
- const syntaxStyle = SyntaxStyle.fromStyles({
2117
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2118
- })
2119
-
2120
- const diffRenderable = new DiffRenderable(currentRenderer, {
2121
- id: "test-diff",
2122
- diff: simpleDiff,
2123
- view: "unified",
2124
- syntaxStyle,
2125
- filetype: "javascript",
2126
- width: "100%",
2127
- height: "100%",
2128
- })
2129
-
2130
- currentRenderer.root.add(diffRenderable)
2131
- await renderOnce()
2132
-
2133
- expect(diffRenderable.conceal).toBe(false)
2134
- })
2135
-
2136
- test("DiffRenderable - should handle resize with wrapping without leaking listeners", async () => {
2137
- const syntaxStyle = SyntaxStyle.fromStyles({
2138
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2139
- })
2140
-
2141
- const diffRenderable = new DiffRenderable(currentRenderer, {
2142
- id: "test-diff",
2143
- diff: simpleDiff,
2144
- view: "split",
2145
- syntaxStyle,
2146
- wrapMode: "word",
2147
- width: 100,
2148
- height: "100%",
2149
- })
2150
-
2151
- currentRenderer.root.add(diffRenderable)
2152
- await renderOnce()
2153
-
2154
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2155
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2156
- const leftInitialCount = leftCodeRenderable.listenerCount("line-info-change")
2157
- const rightInitialCount = rightCodeRenderable.listenerCount("line-info-change")
2158
-
2159
- // Simulate multiple resizes (which trigger rebuilds in split view with wrapping)
2160
- for (let i = 0; i < 10; i++) {
2161
- diffRenderable.width = 50 + i * 5
2162
- await renderOnce()
2163
- // Flush microtask rebuild
2164
- await Promise.resolve()
2165
- await renderOnce()
2166
- }
2167
-
2168
- const leftFinalCount = leftCodeRenderable.listenerCount("line-info-change")
2169
- const rightFinalCount = rightCodeRenderable.listenerCount("line-info-change")
2170
-
2171
- expect(leftFinalCount).toBe(leftInitialCount)
2172
- expect(rightFinalCount).toBe(rightInitialCount)
2173
- })
2174
-
2175
- test("DiffRenderable - gutter configuration updates work correctly", async () => {
2176
- const syntaxStyle = SyntaxStyle.fromStyles({
2177
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2178
- })
2179
-
2180
- const diffRenderable = new DiffRenderable(currentRenderer, {
2181
- id: "test-diff",
2182
- diff: simpleDiff,
2183
- view: "unified",
2184
- syntaxStyle,
2185
- showLineNumbers: true,
2186
- width: "100%",
2187
- height: "100%",
2188
- })
2189
-
2190
- currentRenderer.root.add(diffRenderable)
2191
- await renderOnce()
2192
-
2193
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2194
- const leftSide = (diffRenderable as any).leftSide
2195
-
2196
- // Verify initial state
2197
- expect(leftSide).toBeDefined()
2198
- expect(leftCodeRenderable).toBeDefined()
2199
- const initialListenerCount = leftCodeRenderable.listenerCount("line-info-change")
2200
-
2201
- // Get initial frame to verify line numbers are showing
2202
- let frame = captureFrame()
2203
- expect(frame).toContain("function hello")
2204
-
2205
- // Update multiple gutter configurations that trigger recreateGutter()
2206
- // Each of these calls setLineNumbers/setHideLineNumbers internally
2207
- for (let i = 0; i < 5; i++) {
2208
- diffRenderable.diff = simpleDiff.replace('"Hello"', `"Hello${i}"`)
2209
- await renderOnce()
2210
- }
2211
-
2212
- // Verify listener count is stable
2213
- const finalListenerCount = leftCodeRenderable.listenerCount("line-info-change")
2214
- expect(finalListenerCount).toBe(initialListenerCount)
2215
-
2216
- // Verify rendering still works
2217
- frame = captureFrame()
2218
- expect(frame).toContain("function hello")
2219
- expect(frame).toContain("Hello4") // Last update should be visible
2220
- })
2221
-
2222
- test("DiffRenderable - target remains functional after multiple updates", async () => {
2223
- const syntaxStyle = SyntaxStyle.fromStyles({
2224
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2225
- })
2226
-
2227
- const diffRenderable = new DiffRenderable(currentRenderer, {
2228
- id: "test-diff",
2229
- diff: multiLineDiff,
2230
- view: "split",
2231
- syntaxStyle,
2232
- showLineNumbers: true,
2233
- width: "100%",
2234
- height: "100%",
2235
- })
2236
-
2237
- currentRenderer.root.add(diffRenderable)
2238
- await renderOnce()
2239
-
2240
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2241
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2242
-
2243
- // Verify targets are responding to line-info-change events
2244
- let leftEventFired = false
2245
- let rightEventFired = false
2246
-
2247
- const leftListener = () => {
2248
- leftEventFired = true
2249
- }
2250
- const rightListener = () => {
2251
- rightEventFired = true
2252
- }
2253
-
2254
- leftCodeRenderable.on("line-info-change", leftListener)
2255
- rightCodeRenderable.on("line-info-change", rightListener)
2256
-
2257
- // Update diff multiple times
2258
- for (let i = 0; i < 5; i++) {
2259
- leftEventFired = false
2260
- rightEventFired = false
2261
-
2262
- diffRenderable.diff = multiLineDiff.replace("add(a, b)", `add(a, b, ${i})`)
2263
- await renderOnce()
2264
-
2265
- // Events should have fired during the update
2266
- expect(leftEventFired).toBe(true)
2267
- expect(rightEventFired).toBe(true)
2268
- }
2269
-
2270
- leftCodeRenderable.off("line-info-change", leftListener)
2271
- rightCodeRenderable.off("line-info-change", rightListener)
2272
- })
2273
-
2274
- test("DiffRenderable - split view scroll is not synchronized by default", async () => {
2275
- const mockMouse = createMockMouse(currentRenderer)
2276
- const syntaxStyle = SyntaxStyle.fromStyles({
2277
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2278
- })
2279
-
2280
- const diffRenderable = new DiffRenderable(currentRenderer, {
2281
- id: "test-diff",
2282
- diff: multiLineDiff,
2283
- view: "split",
2284
- syntaxStyle,
2285
- showLineNumbers: true,
2286
- width: "100%",
2287
- height: 4,
2288
- })
2289
-
2290
- currentRenderer.root.add(diffRenderable)
2291
- await renderOnce()
2292
-
2293
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2294
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2295
-
2296
- expect(leftCodeRenderable).toBeTruthy()
2297
- expect(rightCodeRenderable).toBeTruthy()
2298
-
2299
- // Scroll over left pane
2300
- mockMouse.scroll(leftCodeRenderable.x, leftCodeRenderable.y + 1, "down")
2301
- await renderOnce()
2302
-
2303
- expect(leftCodeRenderable.scrollY).toBe(1)
2304
- expect(rightCodeRenderable.scrollY).toBe(0)
2305
-
2306
- // Scroll over right pane
2307
- mockMouse.scroll(rightCodeRenderable.x + 1, rightCodeRenderable.y + 1, "down")
2308
- await renderOnce()
2309
-
2310
- expect(rightCodeRenderable.scrollY).toBe(1)
2311
- expect(leftCodeRenderable.scrollY).toBe(1)
2312
- })
2313
-
2314
- test("DiffRenderable - split view wheel scroll keeps panes synchronized", async () => {
2315
- const mockMouse = createMockMouse(currentRenderer)
2316
- const syntaxStyle = SyntaxStyle.fromStyles({
2317
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2318
- })
2319
-
2320
- const diffRenderable = new DiffRenderable(currentRenderer, {
2321
- id: "test-diff",
2322
- diff: multiLineDiff,
2323
- syncScroll: true,
2324
- view: "split",
2325
- syntaxStyle,
2326
- showLineNumbers: true,
2327
- width: "100%",
2328
- height: 4,
2329
- })
2330
-
2331
- currentRenderer.root.add(diffRenderable)
2332
- await renderOnce()
2333
-
2334
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2335
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2336
-
2337
- expect(leftCodeRenderable).toBeTruthy()
2338
- expect(rightCodeRenderable).toBeTruthy()
2339
-
2340
- // Scroll over left pane
2341
- await mockMouse.scroll(leftCodeRenderable.x + 1, leftCodeRenderable.y + 1, "down")
2342
- await renderOnce()
2343
-
2344
- expect(leftCodeRenderable.scrollY).toBeGreaterThan(0)
2345
- expect(leftCodeRenderable.scrollY).toBe(rightCodeRenderable.scrollY)
2346
-
2347
- // Scroll over right pane
2348
- await mockMouse.scroll(rightCodeRenderable.x + 1, rightCodeRenderable.y + 1, "down")
2349
- await renderOnce()
2350
-
2351
- expect(rightCodeRenderable.scrollY).toBeGreaterThan(0)
2352
- expect(leftCodeRenderable.scrollY).toBe(rightCodeRenderable.scrollY)
2353
- })
2354
-
2355
- test("DiffRenderable - gutter remains in correct position after updates", async () => {
2356
- const syntaxStyle = SyntaxStyle.fromStyles({
2357
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2358
- })
2359
-
2360
- const diffRenderable = new DiffRenderable(currentRenderer, {
2361
- id: "test-diff",
2362
- diff: simpleDiff,
2363
- view: "unified",
2364
- syntaxStyle,
2365
- showLineNumbers: true,
2366
- width: "100%",
2367
- height: "100%",
2368
- })
2369
-
2370
- currentRenderer.root.add(diffRenderable)
2371
- await renderOnce()
2372
-
2373
- // Initial frame should have line numbers on the left
2374
- let frame = captureFrame()
2375
- const lines = frame.split("\n")
2376
-
2377
- // Find a line with content
2378
- const contentLine = lines.find((l) => l.includes("function hello"))
2379
- expect(contentLine).toBeDefined()
2380
-
2381
- // Line number should be at the start (before the content)
2382
- expect(contentLine).toMatch(/^\s*\d+/)
2383
-
2384
- // Update diff multiple times
2385
- for (let i = 0; i < 5; i++) {
2386
- diffRenderable.diff = simpleDiff.replace('"Hello"', `"Hello${i}"`)
2387
- await renderOnce()
2388
-
2389
- frame = captureFrame()
2390
- const updatedLines = frame.split("\n")
2391
- const updatedContentLine = updatedLines.find((l) => l.includes("function hello"))
2392
-
2393
- // Line numbers should still be at the start
2394
- expect(updatedContentLine).toBeDefined()
2395
- expect(updatedContentLine).toMatch(/^\s*\d+/)
2396
- }
2397
- })
2398
-
2399
- test("DiffRenderable - properly cleans up listeners on destroy", async () => {
2400
- const syntaxStyle = SyntaxStyle.fromStyles({
2401
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2402
- })
2403
-
2404
- const diffRenderable = new DiffRenderable(currentRenderer, {
2405
- id: "test-diff",
2406
- diff: simpleDiff,
2407
- view: "split",
2408
- syntaxStyle,
2409
- width: "100%",
2410
- height: "100%",
2411
- })
2412
-
2413
- currentRenderer.root.add(diffRenderable)
2414
- await renderOnce()
2415
-
2416
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2417
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2418
-
2419
- // Update multiple times to potentially create leaks
2420
- for (let i = 0; i < 5; i++) {
2421
- diffRenderable.diff = simpleDiff.replace('"Hello"', `"Hello${i}"`)
2422
- await renderOnce()
2423
- }
2424
-
2425
- const leftCountBeforeDestroy = leftCodeRenderable.listenerCount("line-info-change")
2426
- const rightCountBeforeDestroy = rightCodeRenderable.listenerCount("line-info-change")
2427
-
2428
- // Verify listeners exist
2429
- expect(leftCountBeforeDestroy).toBeGreaterThan(0)
2430
- expect(rightCountBeforeDestroy).toBeGreaterThan(0)
2431
-
2432
- // Destroy the diff
2433
- diffRenderable.destroyRecursively()
2434
-
2435
- // The LineNumberRenderables should have been destroyed
2436
- // Check that they're either null or destroyed
2437
- const leftSide = (diffRenderable as any).leftSide
2438
- const rightSide = (diffRenderable as any).rightSide
2439
-
2440
- if (leftSide) {
2441
- expect(leftSide.isDestroyed).toBe(true)
2442
- }
2443
- if (rightSide) {
2444
- expect(rightSide.isDestroyed).toBe(true)
2445
- }
2446
- })
2447
-
2448
- test("DiffRenderable - line numbers update correctly after resize causes wrapping changes", async () => {
2449
- const testRenderer = await createTestRenderer({ width: 120, height: 40 })
2450
- const renderer = testRenderer.renderer
2451
- const renderOnce = testRenderer.renderOnce
2452
- const captureFrame = testRenderer.captureCharFrame
2453
- const resize = testRenderer.resize
2454
-
2455
- const syntaxStyle = SyntaxStyle.fromStyles({
2456
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2457
- })
2458
-
2459
- const longLineDiff = `--- a/test.js
2460
- +++ b/test.js
2461
- @@ -1,4 +1,4 @@
2462
- function calculateSomethingVeryComplexWithALongFunctionNameThatWillWrap() {
2463
- - const oldResultWithAVeryLongVariableNameThatWillDefinitelyWrapWhenRenderedInASmallerTerminal = 42;
2464
- + const newResultWithAVeryLongVariableNameThatWillDefinitelyWrapWhenRenderedInASmallerTerminal = 100;
2465
- return result;
2466
- }`
2467
-
2468
- const diffRenderable = new DiffRenderable(renderer, {
2469
- id: "test-diff",
2470
- diff: longLineDiff,
2471
- view: "unified",
2472
- syntaxStyle,
2473
- showLineNumbers: true,
2474
- wrapMode: "word",
2475
- width: "100%",
2476
- height: "100%",
2477
- })
2478
-
2479
- renderer.root.add(diffRenderable)
2480
- await renderOnce()
2481
-
2482
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2483
-
2484
- let lineInfoChangeEmitted = false
2485
- const lineInfoChangeListener = () => {
2486
- lineInfoChangeEmitted = true
2487
- }
2488
- leftCodeRenderable.on("line-info-change", lineInfoChangeListener)
2489
-
2490
- const frameBefore = captureFrame()
2491
- expect(frameBefore).toMatchSnapshot("before resize - line numbers with no wrapping")
2492
-
2493
- const lineInfoBefore = leftCodeRenderable.lineInfo
2494
- expect(lineInfoBefore.lineSources).toEqual([0, 1, 2, 3, 4])
2495
- expect(leftCodeRenderable.virtualLineCount).toBe(5)
2496
-
2497
- lineInfoChangeEmitted = false
2498
-
2499
- resize(60, 40)
2500
-
2501
- await Promise.resolve()
2502
- await renderOnce()
2503
-
2504
- expect(lineInfoChangeEmitted).toBe(true)
2505
- expect(leftCodeRenderable.virtualLineCount).toBe(11)
2506
-
2507
- await Promise.resolve()
2508
- await renderOnce()
2509
-
2510
- const frameAfter = captureFrame()
2511
- expect(frameAfter).toMatchSnapshot("after resize - line numbers with wrapping")
2512
-
2513
- const lineInfoAfter = leftCodeRenderable.lineInfo
2514
- expect(lineInfoAfter.lineSources).toEqual([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 4])
2515
-
2516
- const linesAfter = frameAfter.split("\n").filter((l) => l.trim().length > 0)
2517
-
2518
- const lineNumberMatches = linesAfter
2519
- .map((line, idx) => {
2520
- const match = line.match(/^\s*(\d+)\s+([+-]?)/)
2521
- if (match) {
2522
- return { lineIdx: idx, lineNum: parseInt(match[1]), sign: match[2], content: line }
2523
- }
2524
- return null
2525
- })
2526
- .filter((m) => m !== null)
2527
-
2528
- expect(lineNumberMatches.length).toBe(5)
2529
-
2530
- expect(lineNumberMatches[0]!.lineNum).toBe(1)
2531
- expect(lineNumberMatches[1]!.lineNum).toBe(2)
2532
- expect(lineNumberMatches[1]!.sign).toBe("-")
2533
- expect(lineNumberMatches[2]!.lineNum).toBe(2)
2534
- expect(lineNumberMatches[2]!.sign).toBe("+")
2535
- expect(lineNumberMatches[3]!.lineNum).toBe(3)
2536
- expect(lineNumberMatches[4]!.lineNum).toBe(4)
2537
-
2538
- leftCodeRenderable.off("line-info-change", lineInfoChangeListener)
2539
- renderer.destroy()
2540
- })
2541
-
2542
- test("DiffRenderable - fg prop is passed to CodeRenderable on construction", async () => {
2543
- const syntaxStyle = SyntaxStyle.fromStyles({
2544
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2545
- })
2546
- const customFg = "#000000"
2547
-
2548
- const diffRenderable = new DiffRenderable(currentRenderer, {
2549
- id: "test-diff",
2550
- diff: simpleDiff,
2551
- view: "unified",
2552
- syntaxStyle,
2553
- fg: customFg,
2554
- width: "100%",
2555
- height: "100%",
2556
- })
2557
-
2558
- currentRenderer.root.add(diffRenderable)
2559
- await renderOnce()
2560
-
2561
- expect(diffRenderable.fg).toEqual(RGBA.fromHex(customFg))
2562
-
2563
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2564
- expect(leftCodeRenderable).toBeDefined()
2565
- expect(leftCodeRenderable.fg).toEqual(RGBA.fromHex(customFg))
2566
- })
2567
-
2568
- test("DiffRenderable - fg prop can be updated via setter", async () => {
2569
- const syntaxStyle = SyntaxStyle.fromStyles({
2570
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2571
- })
2572
- const initialFg = "#000000"
2573
- const updatedFg = "#333333"
2574
-
2575
- const diffRenderable = new DiffRenderable(currentRenderer, {
2576
- id: "test-diff",
2577
- diff: simpleDiff,
2578
- view: "unified",
2579
- syntaxStyle,
2580
- fg: initialFg,
2581
- width: "100%",
2582
- height: "100%",
2583
- })
2584
-
2585
- currentRenderer.root.add(diffRenderable)
2586
- await renderOnce()
2587
-
2588
- diffRenderable.fg = updatedFg
2589
- await renderOnce()
2590
-
2591
- expect(diffRenderable.fg).toEqual(RGBA.fromHex(updatedFg))
2592
-
2593
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2594
- expect(leftCodeRenderable.fg).toEqual(RGBA.fromHex(updatedFg))
2595
- })
2596
-
2597
- test("DiffRenderable - fg prop is passed to both CodeRenderables in split view", async () => {
2598
- const syntaxStyle = SyntaxStyle.fromStyles({
2599
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2600
- })
2601
- const customFg = "#222222"
2602
-
2603
- const diffRenderable = new DiffRenderable(currentRenderer, {
2604
- id: "test-diff",
2605
- diff: simpleDiff,
2606
- view: "split",
2607
- syntaxStyle,
2608
- fg: customFg,
2609
- width: "100%",
2610
- height: "100%",
2611
- })
2612
-
2613
- currentRenderer.root.add(diffRenderable)
2614
- await renderOnce()
2615
-
2616
- expect(diffRenderable.fg).toEqual(RGBA.fromHex(customFg))
2617
-
2618
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2619
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2620
-
2621
- expect(leftCodeRenderable).toBeDefined()
2622
- expect(rightCodeRenderable).toBeDefined()
2623
- expect(leftCodeRenderable.fg).toEqual(RGBA.fromHex(customFg))
2624
- expect(rightCodeRenderable.fg).toEqual(RGBA.fromHex(customFg))
2625
- })
2626
-
2627
- test("DiffRenderable - fg prop updates both CodeRenderables in split view", async () => {
2628
- const syntaxStyle = SyntaxStyle.fromStyles({
2629
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2630
- })
2631
- const initialFg = "#111111"
2632
- const updatedFg = "#444444"
2633
-
2634
- const diffRenderable = new DiffRenderable(currentRenderer, {
2635
- id: "test-diff",
2636
- diff: simpleDiff,
2637
- view: "split",
2638
- syntaxStyle,
2639
- fg: initialFg,
2640
- width: "100%",
2641
- height: "100%",
2642
- })
2643
-
2644
- currentRenderer.root.add(diffRenderable)
2645
- await renderOnce()
2646
-
2647
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2648
- const rightCodeRenderable = (diffRenderable as any).rightCodeRenderable
2649
-
2650
- diffRenderable.fg = updatedFg
2651
- await renderOnce()
2652
-
2653
- expect(diffRenderable.fg).toEqual(RGBA.fromHex(updatedFg))
2654
- expect(leftCodeRenderable.fg).toEqual(RGBA.fromHex(updatedFg))
2655
- expect(rightCodeRenderable.fg).toEqual(RGBA.fromHex(updatedFg))
2656
- })
2657
-
2658
- test("DiffRenderable - fg prop defaults to undefined when not specified", async () => {
2659
- const syntaxStyle = SyntaxStyle.fromStyles({
2660
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2661
- })
2662
-
2663
- const diffRenderable = new DiffRenderable(currentRenderer, {
2664
- id: "test-diff",
2665
- diff: simpleDiff,
2666
- view: "unified",
2667
- syntaxStyle,
2668
- width: "100%",
2669
- height: "100%",
2670
- })
2671
-
2672
- currentRenderer.root.add(diffRenderable)
2673
- await renderOnce()
2674
-
2675
- expect(diffRenderable.fg).toBeUndefined()
2676
- })
2677
-
2678
- test("DiffRenderable - fg prop can be set to undefined to clear it", async () => {
2679
- const syntaxStyle = SyntaxStyle.fromStyles({
2680
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2681
- })
2682
- const initialFg = "#000000"
2683
-
2684
- const diffRenderable = new DiffRenderable(currentRenderer, {
2685
- id: "test-diff",
2686
- diff: simpleDiff,
2687
- view: "unified",
2688
- syntaxStyle,
2689
- fg: initialFg,
2690
- width: "100%",
2691
- height: "100%",
2692
- })
2693
-
2694
- currentRenderer.root.add(diffRenderable)
2695
- await renderOnce()
2696
-
2697
- expect(diffRenderable.fg).toEqual(RGBA.fromHex(initialFg))
2698
-
2699
- diffRenderable.fg = undefined
2700
- await renderOnce()
2701
-
2702
- expect(diffRenderable.fg).toBeUndefined()
2703
- })
2704
-
2705
- test("DiffRenderable - fg prop accepts RGBA directly", async () => {
2706
- const syntaxStyle = SyntaxStyle.fromStyles({
2707
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2708
- })
2709
- const customFg = RGBA.fromValues(0.2, 0.2, 0.2, 1)
2710
-
2711
- const diffRenderable = new DiffRenderable(currentRenderer, {
2712
- id: "test-diff",
2713
- diff: simpleDiff,
2714
- view: "unified",
2715
- syntaxStyle,
2716
- fg: customFg,
2717
- width: "100%",
2718
- height: "100%",
2719
- })
2720
-
2721
- currentRenderer.root.add(diffRenderable)
2722
- await renderOnce()
2723
-
2724
- expect(diffRenderable.fg).toEqual(customFg)
2725
-
2726
- const leftCodeRenderable = (diffRenderable as any).leftCodeRenderable
2727
- expect(leftCodeRenderable.fg).toEqual(customFg)
2728
- })
2729
-
2730
- test("DiffRenderable - split view with word wrapping: changing diff content should not misalign sides", async () => {
2731
- const { BoxRenderable } = await import("./Box")
2732
- const { parseColor } = await import("../lib/RGBA")
2733
-
2734
- // Use terminal width that matches the demo (~116 chars)
2735
- const testRenderer = await createTestRenderer({ width: 116, height: 30 })
2736
- const renderer = testRenderer.renderer
2737
- const captureFrame = testRenderer.captureCharFrame
2738
-
2739
- // GitHub Dark theme - EXACTLY as in diff-demo.ts
2740
- const theme = {
2741
- backgroundColor: "#0D1117",
2742
- addedBg: "#1a4d1a",
2743
- removedBg: "#4d1a1a",
2744
- contextBg: "transparent",
2745
- addedSignColor: "#22c55e",
2746
- removedSignColor: "#ef4444",
2747
- lineNumberFg: "#6b7280",
2748
- lineNumberBg: "#161b22",
2749
- addedLineNumberBg: "#0d3a0d",
2750
- removedLineNumberBg: "#3a0d0d",
2751
- selectionBg: "#264F78",
2752
- selectionFg: "#FFFFFF",
2753
- }
2754
-
2755
- // Syntax style EXACTLY as in diff-demo.ts GitHub Dark theme
2756
- const syntaxStyle = SyntaxStyle.fromStyles({
2757
- keyword: { fg: parseColor("#FF7B72"), bold: true },
2758
- "keyword.import": { fg: parseColor("#FF7B72"), bold: true },
2759
- string: { fg: parseColor("#A5D6FF") },
2760
- comment: { fg: parseColor("#8B949E"), italic: true },
2761
- number: { fg: parseColor("#79C0FF") },
2762
- boolean: { fg: parseColor("#79C0FF") },
2763
- constant: { fg: parseColor("#79C0FF") },
2764
- function: { fg: parseColor("#D2A8FF") },
2765
- "function.call": { fg: parseColor("#D2A8FF") },
2766
- constructor: { fg: parseColor("#FFA657") },
2767
- type: { fg: parseColor("#FFA657") },
2768
- operator: { fg: parseColor("#FF7B72") },
2769
- variable: { fg: parseColor("#E6EDF3") },
2770
- property: { fg: parseColor("#79C0FF") },
2771
- bracket: { fg: parseColor("#F0F6FC") },
2772
- punctuation: { fg: parseColor("#F0F6FC") },
2773
- default: { fg: parseColor("#E6EDF3") },
2774
- })
2775
-
2776
- // contentExamples[0] - TypeScript Calculator diff
2777
- const calculatorDiff = `--- a/calculator.ts
2778
- +++ b/calculator.ts
2779
- @@ -1,13 +1,20 @@
2780
- class Calculator {
2781
- add(a: number, b: number): number {
2782
- return a + b;
2783
- }
2784
-
2785
- - subtract(a: number, b: number): number {
2786
- - return a - b;
2787
- + subtract(a: number, b: number, c: number = 0): number {
2788
- + return a - b - c;
2789
- }
2790
-
2791
- multiply(a: number, b: number): number {
2792
- return a * b;
2793
- }
2794
- +
2795
- + divide(a: number, b: number): number {
2796
- + if (b === 0) {
2797
- + throw new Error("Division by zero");
2798
- + }
2799
- + return a / b;
2800
- + }
2801
- }`
2802
-
2803
- // contentExamples[1] - Real Session: Text Demo
2804
- const textDemoDiff = `Index: packages/core/src/examples/index.ts
2805
- ===================================================================
2806
- --- packages/core/src/examples/index.ts before
2807
- +++ packages/core/src/examples/index.ts after
2808
- @@ -56,6 +56,7 @@
2809
- import * as terminalDemo from "./terminal"
2810
- import * as diffDemo from "./diff-demo"
2811
- import * as keypressDebugDemo from "./keypress-debug-demo"
2812
- +import * as textTruncationDemo from "./text-truncation-demo"
2813
- import { setupCommonDemoKeys } from "./lib/standalone-keys"
2814
-
2815
- interface Example {
2816
- @@ -85,6 +86,12 @@
2817
- destroy: textSelectionExample.destroy,
2818
- },
2819
- {
2820
- + name: "Text Truncation Demo",
2821
- + description: "Middle truncation with ellipsis - toggle with 'T' key and resize to test responsive behavior",
2822
- + run: textTruncationDemo.run,
2823
- + destroy: textTruncationDemo.destroy,
2824
- + },
2825
- + {
2826
- name: "ASCII Font Selection Demo",
2827
- description: "Text selection with ASCII fonts - precise character-level selection across different font types",
2828
- run: asciiFontSelectionExample.run,`
2829
-
2830
- renderer.setBackgroundColor(theme.backgroundColor)
2831
-
2832
- // PART 1: CORRECT PATH
2833
- // Start with textDemoDiff, view="unified", wrapMode="none"
2834
- // Then toggle to split, then toggle to word wrap
2835
- // This produces CORRECT alignment
2836
- const parentContainer1 = new BoxRenderable(renderer, {
2837
- id: "parent-container-1",
2838
- padding: 1,
2839
- })
2840
- renderer.root.add(parentContainer1)
2841
-
2842
- const correctDiff = new DiffRenderable(renderer, {
2843
- id: "correct-diff",
2844
- diff: textDemoDiff, // Start with textDemoDiff directly
2845
- view: "unified",
2846
- filetype: "typescript",
2847
- syntaxStyle,
2848
- showLineNumbers: true,
2849
- wrapMode: "none",
2850
- conceal: true,
2851
- addedBg: theme.addedBg,
2852
- removedBg: theme.removedBg,
2853
- contextBg: theme.contextBg,
2854
- addedSignColor: theme.addedSignColor,
2855
- removedSignColor: theme.removedSignColor,
2856
- lineNumberFg: theme.lineNumberFg,
2857
- lineNumberBg: theme.lineNumberBg,
2858
- addedLineNumberBg: theme.addedLineNumberBg,
2859
- removedLineNumberBg: theme.removedLineNumberBg,
2860
- selectionBg: theme.selectionBg,
2861
- selectionFg: theme.selectionFg,
2862
- flexGrow: 1,
2863
- flexShrink: 1,
2864
- })
2865
-
2866
- parentContainer1.add(correctDiff)
2867
- await renderOnce()
2868
-
2869
- // Press V - toggle to split view
2870
- correctDiff.view = "split"
2871
- await Promise.resolve()
2872
- await renderOnce()
2873
-
2874
- // Press W - toggle to word wrap
2875
- correctDiff.wrapMode = "word"
2876
- await Promise.resolve()
2877
- await renderOnce()
2878
- await Promise.resolve()
2879
- await renderOnce()
2880
-
2881
- const correctFrame = captureFrame()
2882
-
2883
- // Clean up
2884
- parentContainer1.destroyRecursively()
2885
- renderer.root.remove("parent-container-1")
2886
- await renderOnce()
2887
-
2888
- // PART 2: BUGGY PATH
2889
- // Start with calculatorDiff, view="unified", wrapMode="none"
2890
- // Press V (split), Press W (word), Press C (change to textDemoDiff)
2891
- // This produces WRONG alignment due to stale lineInfo
2892
- const parentContainer2 = new BoxRenderable(renderer, {
2893
- id: "parent-container-2",
2894
- padding: 1,
2895
- })
2896
- renderer.root.add(parentContainer2)
2897
-
2898
- const buggyDiff = new DiffRenderable(renderer, {
2899
- id: "buggy-diff",
2900
- diff: calculatorDiff, // Start with calculatorDiff (contentExamples[0])
2901
- view: "unified",
2902
- filetype: "typescript",
2903
- syntaxStyle,
2904
- showLineNumbers: true,
2905
- wrapMode: "none",
2906
- conceal: true,
2907
- addedBg: theme.addedBg,
2908
- removedBg: theme.removedBg,
2909
- contextBg: theme.contextBg,
2910
- addedSignColor: theme.addedSignColor,
2911
- removedSignColor: theme.removedSignColor,
2912
- lineNumberFg: theme.lineNumberFg,
2913
- lineNumberBg: theme.lineNumberBg,
2914
- addedLineNumberBg: theme.addedLineNumberBg,
2915
- removedLineNumberBg: theme.removedLineNumberBg,
2916
- selectionBg: theme.selectionBg,
2917
- selectionFg: theme.selectionFg,
2918
- flexGrow: 1,
2919
- flexShrink: 1,
2920
- })
2921
-
2922
- parentContainer2.add(buggyDiff)
2923
- await renderOnce()
2924
-
2925
- // Press V - toggle to split view
2926
- buggyDiff.view = "split"
2927
- await Promise.resolve()
2928
- await renderOnce()
2929
-
2930
- // Press W - toggle to word wrap
2931
- buggyDiff.wrapMode = "word"
2932
- await Promise.resolve()
2933
- await renderOnce()
2934
-
2935
- // Press C - change diff content to textDemoDiff
2936
- // THIS IS WHERE THE BUG MANIFESTS - lineInfo is STALE
2937
- buggyDiff.diff = textDemoDiff
2938
- buggyDiff.filetype = "typescript"
2939
- await Promise.resolve()
2940
- await renderOnce()
2941
- await Promise.resolve()
2942
- await renderOnce()
2943
-
2944
- const buggyFrame = captureFrame()
2945
-
2946
- // Clean up
2947
- renderer.destroy()
2948
-
2949
- // ASSERTION: Both frames should be identical since they show the same diff content
2950
- // with the same view settings (split + word wrap)
2951
- // But due to the bug, the buggy frame has misaligned left/right sides because
2952
- // the lineInfo from CodeRenderable is STALE after changing diff content
2953
- expect(buggyFrame).toBe(correctFrame)
2954
- })
2955
-
2956
- test("DiffRenderable - setLineColor applies color to line", async () => {
2957
- const syntaxStyle = SyntaxStyle.fromStyles({
2958
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2959
- })
2960
-
2961
- const diffRenderable = new DiffRenderable(currentRenderer, {
2962
- id: "test-diff",
2963
- diff: simpleDiff,
2964
- view: "unified",
2965
- syntaxStyle,
2966
- })
2967
-
2968
- diffRenderable.setLineColor(0, "#ff0000")
2969
- diffRenderable.setLineColor(1, { gutter: "#00ff00", content: "#0000ff" })
2970
- diffRenderable.clearLineColor(0)
2971
- diffRenderable.clearLineColor(1)
2972
- })
2973
-
2974
- test("DiffRenderable - highlightLines applies color to range", async () => {
2975
- const syntaxStyle = SyntaxStyle.fromStyles({
2976
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2977
- })
2978
-
2979
- const diffRenderable = new DiffRenderable(currentRenderer, {
2980
- id: "test-diff",
2981
- diff: multiLineDiff,
2982
- view: "unified",
2983
- syntaxStyle,
2984
- })
2985
-
2986
- diffRenderable.highlightLines(0, 3, "#ff0000")
2987
- diffRenderable.clearHighlightLines(0, 3)
2988
- })
2989
-
2990
- test("DiffRenderable - setLineColors and clearAllLineColors", async () => {
2991
- const syntaxStyle = SyntaxStyle.fromStyles({
2992
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
2993
- })
2994
-
2995
- const diffRenderable = new DiffRenderable(currentRenderer, {
2996
- id: "test-diff",
2997
- diff: simpleDiff,
2998
- view: "unified",
2999
- syntaxStyle,
3000
- })
3001
-
3002
- const lineColors = new Map<number, string>()
3003
- lineColors.set(0, "#ff0000")
3004
- lineColors.set(1, "#00ff00")
3005
- lineColors.set(2, "#0000ff")
3006
-
3007
- diffRenderable.setLineColors(lineColors)
3008
- diffRenderable.clearAllLineColors()
3009
- })
3010
-
3011
- test("DiffRenderable - line highlighting works in split view", async () => {
3012
- const syntaxStyle = SyntaxStyle.fromStyles({
3013
- default: { fg: RGBA.fromValues(1, 1, 1, 1) },
3014
- })
3015
-
3016
- const diffRenderable = new DiffRenderable(currentRenderer, {
3017
- id: "test-diff",
3018
- diff: simpleDiff,
3019
- view: "split",
3020
- syntaxStyle,
3021
- })
3022
-
3023
- diffRenderable.setLineColor(0, "#ff0000")
3024
- diffRenderable.highlightLines(0, 2, "#00ff00")
3025
- diffRenderable.clearHighlightLines(0, 2)
3026
- diffRenderable.clearAllLineColors()
3027
- })