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