@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,1590 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, afterEach } from "bun:test"
|
|
2
|
+
import { createTestRenderer, type TestRenderer, type MockMouse, type MockInput } from "../../testing/test-renderer.js"
|
|
3
|
+
import { createTextareaRenderable } from "./renderable-test-utils.js"
|
|
4
|
+
import { RGBA } from "../../lib/RGBA.js"
|
|
5
|
+
import { OptimizedBuffer } from "../../buffer.js"
|
|
6
|
+
import { TextRenderable } from "../Text.js"
|
|
7
|
+
|
|
8
|
+
let currentRenderer: TestRenderer
|
|
9
|
+
let renderOnce: () => Promise<void>
|
|
10
|
+
let currentMouse: MockMouse
|
|
11
|
+
let currentMockInput: MockInput
|
|
12
|
+
|
|
13
|
+
describe("Textarea - Selection Tests", () => {
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
;({
|
|
16
|
+
renderer: currentRenderer,
|
|
17
|
+
renderOnce,
|
|
18
|
+
mockMouse: currentMouse,
|
|
19
|
+
mockInput: currentMockInput,
|
|
20
|
+
} = await createTestRenderer({
|
|
21
|
+
width: 80,
|
|
22
|
+
height: 24,
|
|
23
|
+
}))
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
currentRenderer.destroy()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe("Selection Support", () => {
|
|
31
|
+
it("should support selection via mouse drag", async () => {
|
|
32
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
33
|
+
initialValue: "Hello World",
|
|
34
|
+
width: 40,
|
|
35
|
+
height: 10,
|
|
36
|
+
selectable: true,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(editor.hasSelection()).toBe(false)
|
|
40
|
+
|
|
41
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
42
|
+
await renderOnce()
|
|
43
|
+
|
|
44
|
+
expect(editor.hasSelection()).toBe(true)
|
|
45
|
+
|
|
46
|
+
const sel = editor.getSelection()
|
|
47
|
+
expect(sel).not.toBe(null)
|
|
48
|
+
expect(sel!.start).toBe(0)
|
|
49
|
+
expect(sel!.end).toBe(5)
|
|
50
|
+
|
|
51
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("should return selected text from multi-line content", async () => {
|
|
55
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
56
|
+
initialValue: "AAAA\nBBBB\nCCCC",
|
|
57
|
+
width: 40,
|
|
58
|
+
height: 10,
|
|
59
|
+
selectable: true,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await currentMouse.drag(editor.x + 2, editor.y, editor.x + 2, editor.y + 2)
|
|
63
|
+
await renderOnce()
|
|
64
|
+
|
|
65
|
+
const selectedText = editor.getSelectedText()
|
|
66
|
+
expect(selectedText).toBe("AA\nBBBB\nCC")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("should handle selection with viewport scrolling", async () => {
|
|
70
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
71
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Line ${i}`).join("\n"),
|
|
72
|
+
width: 40,
|
|
73
|
+
height: 5,
|
|
74
|
+
selectable: true,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
editor.gotoLine(10)
|
|
78
|
+
await renderOnce()
|
|
79
|
+
|
|
80
|
+
const viewport = editor.editorView.getViewport()
|
|
81
|
+
expect(viewport.offsetY).toBeGreaterThan(0)
|
|
82
|
+
|
|
83
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 4, editor.y + 2)
|
|
84
|
+
await renderOnce()
|
|
85
|
+
|
|
86
|
+
expect(editor.hasSelection()).toBe(true)
|
|
87
|
+
const selectedText = editor.getSelectedText()
|
|
88
|
+
expect(selectedText.length).toBeGreaterThan(0)
|
|
89
|
+
expect(selectedText).not.toContain("Line 0")
|
|
90
|
+
expect(selectedText).not.toContain("Line 1")
|
|
91
|
+
expect(selectedText).toContain("Line")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("should disable selection when selectable is false", async () => {
|
|
95
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
96
|
+
initialValue: "Hello World",
|
|
97
|
+
width: 40,
|
|
98
|
+
height: 10,
|
|
99
|
+
selectable: false,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const shouldHandle = editor.shouldStartSelection(editor.x, editor.y)
|
|
103
|
+
expect(shouldHandle).toBe(false)
|
|
104
|
+
|
|
105
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
106
|
+
await renderOnce()
|
|
107
|
+
|
|
108
|
+
expect(editor.hasSelection()).toBe(false)
|
|
109
|
+
expect(editor.getSelectedText()).toBe("")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("should update selection when selectionBg/selectionFg changes", async () => {
|
|
113
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
114
|
+
initialValue: "Hello World",
|
|
115
|
+
width: 40,
|
|
116
|
+
height: 10,
|
|
117
|
+
selectable: true,
|
|
118
|
+
selectionBg: RGBA.fromValues(0, 0, 1, 1),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
122
|
+
await renderOnce()
|
|
123
|
+
|
|
124
|
+
expect(editor.hasSelection()).toBe(true)
|
|
125
|
+
|
|
126
|
+
editor.selectionBg = RGBA.fromValues(1, 0, 0, 1)
|
|
127
|
+
editor.selectionFg = RGBA.fromValues(1, 1, 1, 1)
|
|
128
|
+
|
|
129
|
+
expect(editor.hasSelection()).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("should clear selection", async () => {
|
|
133
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
134
|
+
initialValue: "Hello World",
|
|
135
|
+
width: 40,
|
|
136
|
+
height: 10,
|
|
137
|
+
selectable: true,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
141
|
+
await renderOnce()
|
|
142
|
+
|
|
143
|
+
expect(editor.hasSelection()).toBe(true)
|
|
144
|
+
|
|
145
|
+
currentRenderer.clearSelection()
|
|
146
|
+
await renderOnce()
|
|
147
|
+
|
|
148
|
+
expect(editor.hasSelection()).toBe(false)
|
|
149
|
+
expect(editor.getSelectedText()).toBe("")
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("should handle selection with wrapping enabled", async () => {
|
|
153
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
154
|
+
initialValue: "ABCDEFGHIJKLMNOP",
|
|
155
|
+
width: 10,
|
|
156
|
+
height: 10,
|
|
157
|
+
wrapMode: "word",
|
|
158
|
+
selectable: true,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const vlineCount = editor.editorView.getVirtualLineCount()
|
|
162
|
+
expect(vlineCount).toBe(2)
|
|
163
|
+
|
|
164
|
+
await currentMouse.drag(editor.x + 2, editor.y, editor.x + 3, editor.y + 1)
|
|
165
|
+
await renderOnce()
|
|
166
|
+
|
|
167
|
+
const sel = editor.getSelection()
|
|
168
|
+
expect(sel).not.toBe(null)
|
|
169
|
+
expect(sel!.start).toBe(2)
|
|
170
|
+
expect(sel!.end).toBe(13)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("should handle reverse selection (drag from end to start)", async () => {
|
|
174
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
175
|
+
initialValue: "Hello World",
|
|
176
|
+
width: 40,
|
|
177
|
+
height: 10,
|
|
178
|
+
selectable: true,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
await currentMouse.drag(editor.x + 11, editor.y, editor.x + 6, editor.y)
|
|
182
|
+
await renderOnce()
|
|
183
|
+
|
|
184
|
+
const sel = editor.getSelection()
|
|
185
|
+
expect(sel).not.toBe(null)
|
|
186
|
+
expect(sel!.start).toBe(6)
|
|
187
|
+
expect(sel!.end).toBe(11)
|
|
188
|
+
|
|
189
|
+
expect(editor.getSelectedText()).toBe("World")
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it("should render selection properly when drawing to buffer", async () => {
|
|
193
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
194
|
+
|
|
195
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
196
|
+
initialValue: "Hello World",
|
|
197
|
+
width: 40,
|
|
198
|
+
height: 10,
|
|
199
|
+
selectable: true,
|
|
200
|
+
selectionBg: RGBA.fromValues(0, 0, 1, 1),
|
|
201
|
+
selectionFg: RGBA.fromValues(1, 1, 1, 1),
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
205
|
+
await renderOnce()
|
|
206
|
+
|
|
207
|
+
expect(editor.hasSelection()).toBe(true)
|
|
208
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
209
|
+
|
|
210
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
211
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
212
|
+
|
|
213
|
+
const sel = editor.getSelection()
|
|
214
|
+
expect(sel).not.toBe(null)
|
|
215
|
+
expect(sel!.start).toBe(0)
|
|
216
|
+
expect(sel!.end).toBe(5)
|
|
217
|
+
|
|
218
|
+
buffer.destroy()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it("should handle viewport-aware selection correctly", async () => {
|
|
222
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
223
|
+
initialValue: Array.from({ length: 15 }, (_, i) => `Line ${i}`).join("\n"),
|
|
224
|
+
width: 40,
|
|
225
|
+
height: 5,
|
|
226
|
+
selectable: true,
|
|
227
|
+
scrollMargin: 0,
|
|
228
|
+
scrollSpeed: 0,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
editor.gotoLine(10)
|
|
232
|
+
await renderOnce()
|
|
233
|
+
|
|
234
|
+
const viewport = editor.editorView.getViewport()
|
|
235
|
+
expect(viewport.offsetY).toBeGreaterThan(0)
|
|
236
|
+
|
|
237
|
+
const expectedLineNumber = viewport.offsetY
|
|
238
|
+
|
|
239
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 6, editor.y)
|
|
240
|
+
await renderOnce()
|
|
241
|
+
|
|
242
|
+
expect(editor.hasSelection()).toBe(true)
|
|
243
|
+
const selectedText = editor.getSelectedText()
|
|
244
|
+
|
|
245
|
+
expect(selectedText).not.toContain("Line 0")
|
|
246
|
+
expect(selectedText).not.toContain("Line 1")
|
|
247
|
+
expect(selectedText).toContain(`Line ${expectedLineNumber}`)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it("should handle multi-line selection with viewport scrolling", async () => {
|
|
251
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
252
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `AAAA${i}`).join("\n"),
|
|
253
|
+
width: 40,
|
|
254
|
+
height: 5,
|
|
255
|
+
selectable: true,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
editor.gotoLine(8)
|
|
259
|
+
await renderOnce()
|
|
260
|
+
|
|
261
|
+
const viewport = editor.editorView.getViewport()
|
|
262
|
+
expect(viewport.offsetY).toBeGreaterThan(0)
|
|
263
|
+
|
|
264
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 4, editor.y + 2)
|
|
265
|
+
await renderOnce()
|
|
266
|
+
|
|
267
|
+
expect(editor.hasSelection()).toBe(true)
|
|
268
|
+
const selectedText = editor.getSelectedText()
|
|
269
|
+
|
|
270
|
+
const line1 = `AAAA${viewport.offsetY}`
|
|
271
|
+
const line2 = `AAAA${viewport.offsetY + 1}`
|
|
272
|
+
const line3 = `AAAA${viewport.offsetY + 2}`
|
|
273
|
+
|
|
274
|
+
expect(selectedText).toContain(line1)
|
|
275
|
+
expect(selectedText).toContain(line2)
|
|
276
|
+
expect(selectedText).toContain(line3.substring(0, 4))
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it("should handle horizontal scrolled selection without wrapping", async () => {
|
|
280
|
+
const longLine = "A".repeat(100)
|
|
281
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
282
|
+
initialValue: longLine,
|
|
283
|
+
width: 20,
|
|
284
|
+
height: 5,
|
|
285
|
+
wrapMode: "none",
|
|
286
|
+
selectable: true,
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
for (let i = 0; i < 50; i++) {
|
|
290
|
+
editor.moveCursorRight()
|
|
291
|
+
}
|
|
292
|
+
await renderOnce()
|
|
293
|
+
|
|
294
|
+
const viewport = editor.editorView.getViewport()
|
|
295
|
+
expect(viewport.offsetX).toBeGreaterThan(0)
|
|
296
|
+
|
|
297
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 10, editor.y)
|
|
298
|
+
await renderOnce()
|
|
299
|
+
|
|
300
|
+
expect(editor.hasSelection()).toBe(true)
|
|
301
|
+
const selectedText = editor.getSelectedText()
|
|
302
|
+
|
|
303
|
+
expect(selectedText).toBe("A".repeat(10))
|
|
304
|
+
|
|
305
|
+
const sel = editor.getSelection()
|
|
306
|
+
expect(sel).not.toBe(null)
|
|
307
|
+
expect(sel!.start).toBeGreaterThanOrEqual(viewport.offsetX)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it("should render selection highlighting at correct screen position with viewport scroll", async () => {
|
|
311
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
312
|
+
|
|
313
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
314
|
+
initialValue: Array.from({ length: 15 }, (_, i) => `Line${i}`).join("\n"),
|
|
315
|
+
width: 20,
|
|
316
|
+
height: 5,
|
|
317
|
+
selectable: true,
|
|
318
|
+
selectionBg: RGBA.fromValues(1, 0, 0, 1),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
editor.gotoLine(8)
|
|
322
|
+
await renderOnce()
|
|
323
|
+
|
|
324
|
+
const viewport = editor.editorView.getViewport()
|
|
325
|
+
expect(viewport.offsetY).toBeGreaterThan(0)
|
|
326
|
+
|
|
327
|
+
// Use manual drag steps instead of the drag helper to avoid timing issues
|
|
328
|
+
await currentMouse.pressDown(editor.x, editor.y)
|
|
329
|
+
await currentMouse.emitMouseEvent("drag", editor.x + 5, editor.y)
|
|
330
|
+
await currentMouse.release(editor.x + 5, editor.y)
|
|
331
|
+
await renderOnce()
|
|
332
|
+
|
|
333
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
334
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
335
|
+
|
|
336
|
+
const selectedText = editor.getSelectedText()
|
|
337
|
+
expect(selectedText).toBe(`Line${viewport.offsetY}`.substring(0, 5))
|
|
338
|
+
|
|
339
|
+
const { bg } = buffer.buffers
|
|
340
|
+
const bufferWidth = buffer.width
|
|
341
|
+
|
|
342
|
+
for (let cellX = editor.x; cellX < editor.x + 5; cellX++) {
|
|
343
|
+
const bufferIdx = editor.y * bufferWidth + cellX
|
|
344
|
+
const bgR = bg[bufferIdx * 4 + 0]
|
|
345
|
+
const bgG = bg[bufferIdx * 4 + 1]
|
|
346
|
+
const bgB = bg[bufferIdx * 4 + 2]
|
|
347
|
+
|
|
348
|
+
expect(Math.abs(bgR - 1.0)).toBeLessThan(0.01)
|
|
349
|
+
expect(Math.abs(bgG - 0.0)).toBeLessThan(0.01)
|
|
350
|
+
expect(Math.abs(bgB - 0.0)).toBeLessThan(0.01)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
buffer.destroy()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it("should render selection correctly with empty lines between content", async () => {
|
|
357
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
358
|
+
|
|
359
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
360
|
+
initialValue: "AAAA\n\nBBBB\n\nCCCC",
|
|
361
|
+
width: 40,
|
|
362
|
+
height: 10,
|
|
363
|
+
selectable: true,
|
|
364
|
+
selectionBg: RGBA.fromValues(1, 0, 0, 1),
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
editor.focus()
|
|
368
|
+
editor.gotoLine(2)
|
|
369
|
+
|
|
370
|
+
for (let i = 0; i < 4; i++) {
|
|
371
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
expect(editor.getSelectedText()).toBe("BBBB")
|
|
375
|
+
|
|
376
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
377
|
+
buffer.drawEditorView(editor.editorView, 0, 0)
|
|
378
|
+
|
|
379
|
+
const { bg } = buffer.buffers
|
|
380
|
+
const bufferWidth = buffer.width
|
|
381
|
+
|
|
382
|
+
for (let cellX = 0; cellX < 4; cellX++) {
|
|
383
|
+
const bufferIdx = 2 * bufferWidth + cellX
|
|
384
|
+
const bgR = bg[bufferIdx * 4 + 0]
|
|
385
|
+
const bgG = bg[bufferIdx * 4 + 1]
|
|
386
|
+
const bgB = bg[bufferIdx * 4 + 2]
|
|
387
|
+
|
|
388
|
+
expect(Math.abs(bgR - 1.0)).toBeLessThan(0.01)
|
|
389
|
+
expect(Math.abs(bgG - 0.0)).toBeLessThan(0.01)
|
|
390
|
+
expect(Math.abs(bgB - 0.0)).toBeLessThan(0.01)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
buffer.destroy()
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it("should handle shift+arrow selection with viewport scrolling", async () => {
|
|
397
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
398
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Line${i}`).join("\n"),
|
|
399
|
+
width: 40,
|
|
400
|
+
height: 5,
|
|
401
|
+
selectable: true,
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
editor.focus()
|
|
405
|
+
|
|
406
|
+
editor.gotoLine(15)
|
|
407
|
+
await renderOnce()
|
|
408
|
+
|
|
409
|
+
const viewport = editor.editorView.getViewport()
|
|
410
|
+
expect(viewport.offsetY).toBeGreaterThan(10)
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < 5; i++) {
|
|
413
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
expect(editor.hasSelection()).toBe(true)
|
|
417
|
+
const selectedText = editor.getSelectedText()
|
|
418
|
+
|
|
419
|
+
expect(selectedText).toBe("Line1")
|
|
420
|
+
|
|
421
|
+
const sel = editor.getSelection()
|
|
422
|
+
expect(sel).not.toBe(null)
|
|
423
|
+
expect(sel!.end - sel!.start).toBe(5)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it("should handle mouse drag selection with scrolled viewport using correct offset", async () => {
|
|
427
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
428
|
+
initialValue: Array.from({ length: 30 }, (_, i) => `AAAA${i}`).join("\n"),
|
|
429
|
+
width: 40,
|
|
430
|
+
height: 5,
|
|
431
|
+
selectable: true,
|
|
432
|
+
scrollSpeed: 0,
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
editor.gotoLine(20)
|
|
436
|
+
await renderOnce()
|
|
437
|
+
|
|
438
|
+
const viewport = editor.editorView.getViewport()
|
|
439
|
+
expect(viewport.offsetY).toBeGreaterThan(15)
|
|
440
|
+
|
|
441
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 4, editor.y)
|
|
442
|
+
await renderOnce()
|
|
443
|
+
|
|
444
|
+
expect(editor.hasSelection()).toBe(true)
|
|
445
|
+
const selectedText = editor.getSelectedText()
|
|
446
|
+
|
|
447
|
+
expect(selectedText).not.toContain("AAAA0")
|
|
448
|
+
expect(selectedText).not.toContain("AAAA1")
|
|
449
|
+
|
|
450
|
+
const firstVisibleLineIdx = viewport.offsetY
|
|
451
|
+
const expectedText = `AAAA${firstVisibleLineIdx}`.substring(0, 4)
|
|
452
|
+
expect(selectedText).toBe(expectedText)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it("should handle multi-line mouse drag with scrolled viewport", async () => {
|
|
456
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
457
|
+
initialValue: Array.from({ length: 30 }, (_, i) => `Line${i}`).join("\n"),
|
|
458
|
+
width: 40,
|
|
459
|
+
height: 5,
|
|
460
|
+
selectable: true,
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
editor.gotoLine(12)
|
|
464
|
+
await renderOnce()
|
|
465
|
+
|
|
466
|
+
const viewport = editor.editorView.getViewport()
|
|
467
|
+
expect(viewport.offsetY).toBeGreaterThan(7)
|
|
468
|
+
|
|
469
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y + 2)
|
|
470
|
+
await renderOnce()
|
|
471
|
+
|
|
472
|
+
expect(editor.hasSelection()).toBe(true)
|
|
473
|
+
const selectedText = editor.getSelectedText()
|
|
474
|
+
|
|
475
|
+
expect(selectedText.startsWith("Line0")).toBe(false)
|
|
476
|
+
expect(selectedText.startsWith("Line1")).toBe(false)
|
|
477
|
+
expect(selectedText.startsWith("Line2")).toBe(false)
|
|
478
|
+
|
|
479
|
+
const line1 = `Line${viewport.offsetY}`
|
|
480
|
+
const line2 = `Line${viewport.offsetY + 1}`
|
|
481
|
+
const line3 = `Line${viewport.offsetY + 2}`
|
|
482
|
+
|
|
483
|
+
expect(selectedText).toContain(line1)
|
|
484
|
+
expect(selectedText).toContain(line2)
|
|
485
|
+
expect(selectedText).toContain(line3.substring(0, 5))
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
describe("Shift+Arrow Key Selection", () => {
|
|
490
|
+
it("should start selection with shift+right", async () => {
|
|
491
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
492
|
+
initialValue: "Hello World",
|
|
493
|
+
width: 40,
|
|
494
|
+
height: 10,
|
|
495
|
+
selectable: true,
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
editor.focus()
|
|
499
|
+
expect(editor.hasSelection()).toBe(false)
|
|
500
|
+
|
|
501
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
502
|
+
|
|
503
|
+
expect(editor.hasSelection()).toBe(true)
|
|
504
|
+
expect(editor.getSelectedText()).toBe("H")
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it("should extend selection with shift+right", async () => {
|
|
508
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
509
|
+
initialValue: "Hello World",
|
|
510
|
+
width: 40,
|
|
511
|
+
height: 10,
|
|
512
|
+
selectable: true,
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
editor.focus()
|
|
516
|
+
|
|
517
|
+
for (let i = 0; i < 5; i++) {
|
|
518
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
expect(editor.hasSelection()).toBe(true)
|
|
522
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
it("should extend a mouse selection with shift+right", async () => {
|
|
526
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
527
|
+
initialValue: "Hello World",
|
|
528
|
+
width: 40,
|
|
529
|
+
height: 10,
|
|
530
|
+
selectable: true,
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
editor.focus()
|
|
534
|
+
|
|
535
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
536
|
+
await renderOnce()
|
|
537
|
+
|
|
538
|
+
expect(editor.hasSelection()).toBe(true)
|
|
539
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
540
|
+
|
|
541
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
542
|
+
await renderOnce()
|
|
543
|
+
|
|
544
|
+
expect(editor.getSelectedText()).toBe("Hello ")
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
it("should handle shift+left selection", async () => {
|
|
548
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
549
|
+
initialValue: "Hello World",
|
|
550
|
+
width: 40,
|
|
551
|
+
height: 10,
|
|
552
|
+
selectable: true,
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
editor.focus()
|
|
556
|
+
const cursor = editor.logicalCursor
|
|
557
|
+
editor.editBuffer.setCursorToLineCol(cursor.row, 9999)
|
|
558
|
+
|
|
559
|
+
for (let i = 0; i < 5; i++) {
|
|
560
|
+
currentMockInput.pressArrow("left", { shift: true })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
expect(editor.hasSelection()).toBe(true)
|
|
564
|
+
expect(editor.getSelectedText()).toBe("World")
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it("should select with shift+down", async () => {
|
|
568
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
569
|
+
initialValue: "Line 1\nLine 2\nLine 3",
|
|
570
|
+
width: 40,
|
|
571
|
+
height: 10,
|
|
572
|
+
selectable: true,
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
editor.focus()
|
|
576
|
+
|
|
577
|
+
currentMockInput.pressArrow("down", { shift: true })
|
|
578
|
+
|
|
579
|
+
expect(editor.hasSelection()).toBe(true)
|
|
580
|
+
const selectedText = editor.getSelectedText()
|
|
581
|
+
expect(selectedText).toBe("Line 1")
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it("should select with shift+up", async () => {
|
|
585
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
586
|
+
initialValue: "Line 1\nLine 2\nLine 3",
|
|
587
|
+
width: 40,
|
|
588
|
+
height: 10,
|
|
589
|
+
selectable: true,
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
editor.focus()
|
|
593
|
+
editor.gotoLine(2)
|
|
594
|
+
|
|
595
|
+
currentMockInput.pressArrow("up", { shift: true })
|
|
596
|
+
|
|
597
|
+
expect(editor.hasSelection()).toBe(true)
|
|
598
|
+
const selectedText = editor.getSelectedText()
|
|
599
|
+
expect(selectedText.includes("Line 2")).toBe(true)
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
it("should select to line start with shift+home", async () => {
|
|
603
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
604
|
+
initialValue: "Hello World",
|
|
605
|
+
width: 40,
|
|
606
|
+
height: 10,
|
|
607
|
+
selectable: true,
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
editor.focus()
|
|
611
|
+
for (let i = 0; i < 6; i++) {
|
|
612
|
+
editor.moveCursorRight()
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
currentMockInput.pressKey("HOME", { shift: true })
|
|
616
|
+
|
|
617
|
+
expect(editor.hasSelection()).toBe(true)
|
|
618
|
+
expect(editor.getSelectedText()).toBe("Hello W")
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
it("should select to line end with shift+end", async () => {
|
|
622
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
623
|
+
initialValue: "Hello World",
|
|
624
|
+
width: 40,
|
|
625
|
+
height: 10,
|
|
626
|
+
selectable: true,
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
editor.focus()
|
|
630
|
+
|
|
631
|
+
currentMockInput.pressKey("END", { shift: true })
|
|
632
|
+
|
|
633
|
+
expect(editor.hasSelection()).toBe(true)
|
|
634
|
+
expect(editor.getSelectedText()).toBe("Hello World")
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
it("should clear selection when moving without shift", async () => {
|
|
638
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
639
|
+
initialValue: "Hello World",
|
|
640
|
+
width: 40,
|
|
641
|
+
height: 10,
|
|
642
|
+
selectable: true,
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
editor.focus()
|
|
646
|
+
|
|
647
|
+
for (let i = 0; i < 5; i++) {
|
|
648
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
expect(editor.hasSelection()).toBe(true)
|
|
652
|
+
|
|
653
|
+
currentMockInput.pressArrow("right")
|
|
654
|
+
|
|
655
|
+
expect(editor.hasSelection()).toBe(false)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
it("should delete selected text with backspace", async () => {
|
|
659
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
660
|
+
initialValue: "Hello World",
|
|
661
|
+
width: 40,
|
|
662
|
+
height: 10,
|
|
663
|
+
selectable: true,
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
editor.focus()
|
|
667
|
+
|
|
668
|
+
for (let i = 0; i < 5; i++) {
|
|
669
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
673
|
+
expect(editor.plainText).toBe("Hello World")
|
|
674
|
+
|
|
675
|
+
currentMockInput.pressBackspace()
|
|
676
|
+
|
|
677
|
+
expect(editor.hasSelection()).toBe(false)
|
|
678
|
+
expect(editor.plainText).toBe(" World")
|
|
679
|
+
expect(editor.logicalCursor.col).toBe(0)
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
it("should delete selected text with delete key", async () => {
|
|
683
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
684
|
+
initialValue: "Hello World!",
|
|
685
|
+
width: 40,
|
|
686
|
+
height: 10,
|
|
687
|
+
selectable: true,
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
editor.focus()
|
|
691
|
+
|
|
692
|
+
const cursor = editor.logicalCursor
|
|
693
|
+
editor.editBuffer.setCursorToLineCol(cursor.row, 9999)
|
|
694
|
+
for (let i = 0; i < 6; i++) {
|
|
695
|
+
currentMockInput.pressArrow("left", { shift: true })
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
expect(editor.getSelectedText()).toBe("World!")
|
|
699
|
+
expect(editor.plainText).toBe("Hello World!")
|
|
700
|
+
|
|
701
|
+
currentMockInput.pressKey("DELETE")
|
|
702
|
+
|
|
703
|
+
expect(editor.hasSelection()).toBe(false)
|
|
704
|
+
expect(editor.plainText).toBe("Hello ")
|
|
705
|
+
expect(editor.logicalCursor.col).toBe(6)
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
it("should delete multi-line selection with backspace", async () => {
|
|
709
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
710
|
+
initialValue: "Line 1\nLine 2\nLine 3",
|
|
711
|
+
width: 40,
|
|
712
|
+
height: 10,
|
|
713
|
+
selectable: true,
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
editor.focus()
|
|
717
|
+
|
|
718
|
+
for (let i = 0; i < 10; i++) {
|
|
719
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const selectedText = editor.getSelectedText()
|
|
723
|
+
expect(editor.plainText).toBe("Line 1\nLine 2\nLine 3")
|
|
724
|
+
|
|
725
|
+
currentMockInput.pressBackspace()
|
|
726
|
+
|
|
727
|
+
expect(editor.hasSelection()).toBe(false)
|
|
728
|
+
expect(editor.plainText).toBe("e 2\nLine 3")
|
|
729
|
+
expect(editor.logicalCursor.col).toBe(0)
|
|
730
|
+
expect(editor.logicalCursor.row).toBe(0)
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
it("should delete entire line when selected with delete", async () => {
|
|
734
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
735
|
+
initialValue: "Line 1\nLine 2\nLine 3",
|
|
736
|
+
width: 40,
|
|
737
|
+
height: 10,
|
|
738
|
+
selectable: true,
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
editor.focus()
|
|
742
|
+
editor.gotoLine(1)
|
|
743
|
+
|
|
744
|
+
currentMockInput.pressArrow("down", { shift: true })
|
|
745
|
+
|
|
746
|
+
const selectedText = editor.getSelectedText()
|
|
747
|
+
expect(selectedText).toBe("Line 2")
|
|
748
|
+
|
|
749
|
+
currentMockInput.pressKey("DELETE")
|
|
750
|
+
|
|
751
|
+
expect(editor.hasSelection()).toBe(false)
|
|
752
|
+
expect(editor.plainText).toBe("Line 1\nLine 3")
|
|
753
|
+
expect(editor.logicalCursor.row).toBe(1)
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
it("should replace selected text when typing", async () => {
|
|
757
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
758
|
+
initialValue: "Hello World",
|
|
759
|
+
width: 40,
|
|
760
|
+
height: 10,
|
|
761
|
+
selectable: true,
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
editor.focus()
|
|
765
|
+
|
|
766
|
+
for (let i = 0; i < 5; i++) {
|
|
767
|
+
currentMockInput.pressArrow("right", { shift: true })
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
771
|
+
|
|
772
|
+
currentMockInput.pressKey("H")
|
|
773
|
+
currentMockInput.pressKey("i")
|
|
774
|
+
|
|
775
|
+
expect(editor.hasSelection()).toBe(false)
|
|
776
|
+
expect(editor.plainText).toBe("Hi World")
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
it("should delete selected text via native deleteSelectedText API", async () => {
|
|
780
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
781
|
+
initialValue: "Hello World",
|
|
782
|
+
width: 40,
|
|
783
|
+
height: 10,
|
|
784
|
+
selectable: true,
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
editor.focus()
|
|
788
|
+
|
|
789
|
+
await currentMouse.drag(editor.x, editor.y, editor.x + 5, editor.y)
|
|
790
|
+
await renderOnce()
|
|
791
|
+
|
|
792
|
+
expect(editor.hasSelection()).toBe(true)
|
|
793
|
+
expect(editor.getSelectedText()).toBe("Hello")
|
|
794
|
+
|
|
795
|
+
editor.editorView.deleteSelectedText()
|
|
796
|
+
currentRenderer.clearSelection()
|
|
797
|
+
await renderOnce()
|
|
798
|
+
|
|
799
|
+
expect(editor.plainText).toBe(" World")
|
|
800
|
+
expect(editor.logicalCursor.row).toBe(0)
|
|
801
|
+
expect(editor.logicalCursor.col).toBe(0)
|
|
802
|
+
expect(editor.editorView.hasSelection()).toBe(false)
|
|
803
|
+
})
|
|
804
|
+
it("should maintain correct selection start when scrolling down with shift+down", async () => {
|
|
805
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
806
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Line ${i}`).join("\n"),
|
|
807
|
+
width: 20,
|
|
808
|
+
height: 5,
|
|
809
|
+
selectable: true,
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
editor.focus()
|
|
813
|
+
|
|
814
|
+
for (let i = 0; i < 8; i++) {
|
|
815
|
+
currentMockInput.pressArrow("down", { shift: true })
|
|
816
|
+
await renderOnce()
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const viewport = editor.editorView.getViewport()
|
|
820
|
+
expect(viewport.offsetY).toBeGreaterThan(0)
|
|
821
|
+
|
|
822
|
+
const sel = editor.getSelection()
|
|
823
|
+
expect(sel).not.toBe(null)
|
|
824
|
+
expect(sel!.start).toBe(0)
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
it("should not start selection in textarea when clicking in text renderable below after scrolling", async () => {
|
|
828
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
829
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Textarea Line ${i}`).join("\n"),
|
|
830
|
+
width: 40,
|
|
831
|
+
height: 5,
|
|
832
|
+
selectable: true,
|
|
833
|
+
top: 0,
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
const textBelow = new TextRenderable(currentRenderer, {
|
|
837
|
+
id: "text-below",
|
|
838
|
+
content: "This is text below the textarea",
|
|
839
|
+
selectable: true,
|
|
840
|
+
top: 5,
|
|
841
|
+
left: 0,
|
|
842
|
+
width: 40,
|
|
843
|
+
height: 1,
|
|
844
|
+
})
|
|
845
|
+
currentRenderer.root.add(textBelow)
|
|
846
|
+
|
|
847
|
+
editor.focus()
|
|
848
|
+
|
|
849
|
+
editor.gotoBufferEnd()
|
|
850
|
+
await renderOnce()
|
|
851
|
+
|
|
852
|
+
const viewport = editor.editorView.getViewport()
|
|
853
|
+
expect(viewport.offsetY).toBeGreaterThan(10)
|
|
854
|
+
|
|
855
|
+
await currentMouse.drag(textBelow.x, textBelow.y, textBelow.x + 10, textBelow.y)
|
|
856
|
+
await renderOnce()
|
|
857
|
+
|
|
858
|
+
expect(editor.hasSelection()).toBe(false)
|
|
859
|
+
expect(editor.getSelectedText()).toBe("")
|
|
860
|
+
|
|
861
|
+
expect(textBelow.hasSelection()).toBe(true)
|
|
862
|
+
expect(textBelow.getSelectedText()).toBe("This is te")
|
|
863
|
+
|
|
864
|
+
textBelow.destroy()
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
it("should maintain selection in both renderables when dragging from text-below up into textarea", async () => {
|
|
868
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
869
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Textarea Line ${i}`).join("\n"),
|
|
870
|
+
width: 40,
|
|
871
|
+
height: 5,
|
|
872
|
+
selectable: true,
|
|
873
|
+
top: 0,
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
const textBelow = new TextRenderable(currentRenderer, {
|
|
877
|
+
id: "text-below",
|
|
878
|
+
content: "This is text below the textarea",
|
|
879
|
+
selectable: true,
|
|
880
|
+
top: 5,
|
|
881
|
+
left: 0,
|
|
882
|
+
width: 40,
|
|
883
|
+
height: 1,
|
|
884
|
+
})
|
|
885
|
+
currentRenderer.root.add(textBelow)
|
|
886
|
+
|
|
887
|
+
editor.focus()
|
|
888
|
+
|
|
889
|
+
editor.gotoBufferEnd()
|
|
890
|
+
await renderOnce()
|
|
891
|
+
|
|
892
|
+
const viewport = editor.editorView.getViewport()
|
|
893
|
+
expect(viewport.offsetY).toBeGreaterThan(10)
|
|
894
|
+
|
|
895
|
+
const startX = textBelow.x + 5
|
|
896
|
+
const startY = textBelow.y
|
|
897
|
+
const endX = editor.x + 15
|
|
898
|
+
const endY = editor.y + 3
|
|
899
|
+
|
|
900
|
+
await currentMouse.drag(startX, startY, endX, endY)
|
|
901
|
+
await renderOnce()
|
|
902
|
+
|
|
903
|
+
expect(textBelow.hasSelection()).toBe(true)
|
|
904
|
+
const textBelowSelection = textBelow.getSelectedText()
|
|
905
|
+
expect(textBelowSelection.length).toBeGreaterThan(0)
|
|
906
|
+
|
|
907
|
+
expect(editor.hasSelection()).toBe(true)
|
|
908
|
+
const textareaSelection = editor.getSelectedText()
|
|
909
|
+
expect(textareaSelection.length).toBeGreaterThan(0)
|
|
910
|
+
|
|
911
|
+
textBelow.destroy()
|
|
912
|
+
})
|
|
913
|
+
|
|
914
|
+
it("should handle cross-renderable selection from bottom-left text to top-right text", async () => {
|
|
915
|
+
const { BoxRenderable } = await import("../Box.js")
|
|
916
|
+
|
|
917
|
+
const bottomText = new TextRenderable(currentRenderer, {
|
|
918
|
+
id: "bottom-instructions",
|
|
919
|
+
content: "Click and drag to select text across any elements",
|
|
920
|
+
left: 5,
|
|
921
|
+
top: 20,
|
|
922
|
+
width: 50,
|
|
923
|
+
height: 1,
|
|
924
|
+
selectable: true,
|
|
925
|
+
})
|
|
926
|
+
currentRenderer.root.add(bottomText)
|
|
927
|
+
|
|
928
|
+
const rightBox = new BoxRenderable(currentRenderer, {
|
|
929
|
+
id: "right-box",
|
|
930
|
+
left: 50,
|
|
931
|
+
top: 5,
|
|
932
|
+
width: 30,
|
|
933
|
+
height: 10,
|
|
934
|
+
padding: 1,
|
|
935
|
+
flexDirection: "column",
|
|
936
|
+
})
|
|
937
|
+
currentRenderer.root.add(rightBox)
|
|
938
|
+
|
|
939
|
+
const codeText1 = new TextRenderable(currentRenderer, {
|
|
940
|
+
id: "code-line-1",
|
|
941
|
+
content: "function handleSelection() {",
|
|
942
|
+
selectable: true,
|
|
943
|
+
})
|
|
944
|
+
rightBox.add(codeText1)
|
|
945
|
+
|
|
946
|
+
const codeText2 = new TextRenderable(currentRenderer, {
|
|
947
|
+
id: "code-line-2",
|
|
948
|
+
content: " const selected = getText()",
|
|
949
|
+
selectable: true,
|
|
950
|
+
})
|
|
951
|
+
rightBox.add(codeText2)
|
|
952
|
+
|
|
953
|
+
const codeText3 = new TextRenderable(currentRenderer, {
|
|
954
|
+
id: "code-line-3",
|
|
955
|
+
content: " console.log(selected)",
|
|
956
|
+
selectable: true,
|
|
957
|
+
})
|
|
958
|
+
rightBox.add(codeText3)
|
|
959
|
+
|
|
960
|
+
const codeText4 = new TextRenderable(currentRenderer, {
|
|
961
|
+
id: "code-line-4",
|
|
962
|
+
content: "}",
|
|
963
|
+
selectable: true,
|
|
964
|
+
})
|
|
965
|
+
rightBox.add(codeText4)
|
|
966
|
+
|
|
967
|
+
await renderOnce()
|
|
968
|
+
|
|
969
|
+
const startX = bottomText.x + 10
|
|
970
|
+
const startY = bottomText.y
|
|
971
|
+
const endX = codeText2.x + 15
|
|
972
|
+
const endY = codeText2.y
|
|
973
|
+
|
|
974
|
+
await currentMouse.drag(startX, startY, endX, endY)
|
|
975
|
+
await renderOnce()
|
|
976
|
+
|
|
977
|
+
expect(bottomText.hasSelection()).toBe(true)
|
|
978
|
+
const bottomSelected = bottomText.getSelectedText()
|
|
979
|
+
expect(bottomSelected).toBe("Click and d")
|
|
980
|
+
|
|
981
|
+
expect(codeText1.hasSelection()).toBe(false)
|
|
982
|
+
|
|
983
|
+
expect(codeText2.hasSelection()).toBe(true)
|
|
984
|
+
const codeText2Selected = codeText2.getSelectedText()
|
|
985
|
+
const codeText2Content = " const selected = getText()"
|
|
986
|
+
expect(codeText2Selected).toBe(codeText2Content.substring(0, 15))
|
|
987
|
+
|
|
988
|
+
bottomText.destroy()
|
|
989
|
+
rightBox.destroy()
|
|
990
|
+
})
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
describe("Selection After Resize", () => {
|
|
994
|
+
it("should maintain selection correctly after resize - same text selected and rendered properly", async () => {
|
|
995
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
996
|
+
|
|
997
|
+
const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
998
|
+
initialValue: Array.from({ length: 30 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`).join("\n"),
|
|
999
|
+
width: 40,
|
|
1000
|
+
height: 10,
|
|
1001
|
+
selectable: true,
|
|
1002
|
+
selectionBg: RGBA.fromValues(0, 1, 0, 1),
|
|
1003
|
+
selectionFg: RGBA.fromValues(0, 0, 0, 1),
|
|
1004
|
+
})
|
|
1005
|
+
|
|
1006
|
+
editor.gotoLine(5)
|
|
1007
|
+
await renderOnce()
|
|
1008
|
+
|
|
1009
|
+
await currentMouse.drag(editor.x + 5, editor.y + 2, editor.x + 10, editor.y + 4)
|
|
1010
|
+
await renderOnce()
|
|
1011
|
+
|
|
1012
|
+
const selectedTextBefore = editor.getSelectedText()
|
|
1013
|
+
const selectionBefore = editor.getSelection()
|
|
1014
|
+
|
|
1015
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1016
|
+
expect(selectedTextBefore).toBeTruthy()
|
|
1017
|
+
|
|
1018
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1019
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1020
|
+
|
|
1021
|
+
const { bg: bgBefore } = buffer.buffers
|
|
1022
|
+
const bufferWidth = buffer.width
|
|
1023
|
+
|
|
1024
|
+
const selectedCellsBefore: Array<{ x: number; y: number }> = []
|
|
1025
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1026
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1027
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1028
|
+
const bgG = bgBefore[bufferIdx * 4 + 1]
|
|
1029
|
+
if (Math.abs(bgG - 1.0) < 0.01) {
|
|
1030
|
+
selectedCellsBefore.push({ x, y })
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
expect(selectedCellsBefore.length).toBeGreaterThan(0)
|
|
1036
|
+
|
|
1037
|
+
editor.width = 50
|
|
1038
|
+
editor.height = 15
|
|
1039
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1040
|
+
await renderOnce()
|
|
1041
|
+
|
|
1042
|
+
const selectedTextAfter = editor.getSelectedText()
|
|
1043
|
+
const selectionAfter = editor.getSelection()
|
|
1044
|
+
|
|
1045
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1046
|
+
expect(selectedTextAfter).toBe(selectedTextBefore)
|
|
1047
|
+
expect(selectionAfter?.start).toBe(selectionBefore?.start)
|
|
1048
|
+
expect(selectionAfter?.end).toBe(selectionBefore?.end)
|
|
1049
|
+
|
|
1050
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1051
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1052
|
+
|
|
1053
|
+
const { bg: bgAfter } = buffer.buffers
|
|
1054
|
+
|
|
1055
|
+
const selectedCellsAfter: Array<{ x: number; y: number }> = []
|
|
1056
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1057
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1058
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1059
|
+
const bgG = bgAfter[bufferIdx * 4 + 1]
|
|
1060
|
+
if (Math.abs(bgG - 1.0) < 0.01) {
|
|
1061
|
+
selectedCellsAfter.push({ x, y })
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
expect(selectedCellsAfter.length).toBeGreaterThan(0)
|
|
1067
|
+
expect(selectedCellsAfter.length).toBe(selectedCellsBefore.length)
|
|
1068
|
+
|
|
1069
|
+
buffer.destroy()
|
|
1070
|
+
editor.destroy()
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
it("should maintain exact same text selected after wrap width changes", async () => {
|
|
1074
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
1075
|
+
|
|
1076
|
+
const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1077
|
+
initialValue: "AAAAA BBBBB CCCCC DDDDD EEEEE FFFFF GGGGG HHHHH",
|
|
1078
|
+
width: 50,
|
|
1079
|
+
height: 10,
|
|
1080
|
+
wrapMode: "word",
|
|
1081
|
+
selectable: true,
|
|
1082
|
+
selectionBg: RGBA.fromValues(1, 0, 1, 1),
|
|
1083
|
+
selectionFg: RGBA.fromValues(1, 1, 1, 1),
|
|
1084
|
+
})
|
|
1085
|
+
|
|
1086
|
+
await renderOnce()
|
|
1087
|
+
|
|
1088
|
+
await currentMouse.drag(editor.x + 6, editor.y, editor.x + 17, editor.y)
|
|
1089
|
+
await renderOnce()
|
|
1090
|
+
|
|
1091
|
+
const selectedTextBefore = editor.getSelectedText()
|
|
1092
|
+
const selectionBefore = editor.getSelection()
|
|
1093
|
+
|
|
1094
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1095
|
+
expect(selectedTextBefore).toBe("BBBBB CCCCC")
|
|
1096
|
+
|
|
1097
|
+
editor.width = 15
|
|
1098
|
+
editor.height = 15
|
|
1099
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1100
|
+
await renderOnce()
|
|
1101
|
+
|
|
1102
|
+
const selectedTextAfterNarrow = editor.getSelectedText()
|
|
1103
|
+
const selectionAfterNarrow = editor.getSelection()
|
|
1104
|
+
|
|
1105
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1106
|
+
expect(selectedTextAfterNarrow).toBe("BBBBB CCCCC")
|
|
1107
|
+
expect(selectionAfterNarrow?.start).toBe(selectionBefore?.start)
|
|
1108
|
+
expect(selectionAfterNarrow?.end).toBe(selectionBefore?.end)
|
|
1109
|
+
|
|
1110
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1111
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1112
|
+
|
|
1113
|
+
const { bg: bgNarrow } = buffer.buffers
|
|
1114
|
+
const bufferWidth = buffer.width
|
|
1115
|
+
|
|
1116
|
+
let selectedCellsNarrow = 0
|
|
1117
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1118
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1119
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1120
|
+
const bgR = bgNarrow[bufferIdx * 4 + 0]
|
|
1121
|
+
const bgB = bgNarrow[bufferIdx * 4 + 2]
|
|
1122
|
+
if (Math.abs(bgR - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) {
|
|
1123
|
+
selectedCellsNarrow++
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
expect(selectedCellsNarrow).toBe(11)
|
|
1129
|
+
|
|
1130
|
+
editor.width = 50
|
|
1131
|
+
editor.height = 10
|
|
1132
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1133
|
+
await renderOnce()
|
|
1134
|
+
|
|
1135
|
+
const selectedTextAfterWide = editor.getSelectedText()
|
|
1136
|
+
const selectionAfterWide = editor.getSelection()
|
|
1137
|
+
|
|
1138
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1139
|
+
expect(selectedTextAfterWide).toBe("BBBBB CCCCC")
|
|
1140
|
+
expect(selectionAfterWide?.start).toBe(selectionBefore?.start)
|
|
1141
|
+
expect(selectionAfterWide?.end).toBe(selectionBefore?.end)
|
|
1142
|
+
|
|
1143
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1144
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1145
|
+
|
|
1146
|
+
const { bg: bgWide } = buffer.buffers
|
|
1147
|
+
|
|
1148
|
+
let selectedCellsWide = 0
|
|
1149
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1150
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1151
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1152
|
+
const bgR = bgWide[bufferIdx * 4 + 0]
|
|
1153
|
+
const bgB = bgWide[bufferIdx * 4 + 2]
|
|
1154
|
+
if (Math.abs(bgR - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) {
|
|
1155
|
+
selectedCellsWide++
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
expect(selectedCellsWide).toBe(11)
|
|
1161
|
+
|
|
1162
|
+
buffer.destroy()
|
|
1163
|
+
editor.destroy()
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
it("should handle resize during active mouse selection drag", async () => {
|
|
1167
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
1168
|
+
|
|
1169
|
+
const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1170
|
+
initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
|
|
1171
|
+
width: 40,
|
|
1172
|
+
height: 10,
|
|
1173
|
+
selectable: true,
|
|
1174
|
+
selectionBg: RGBA.fromValues(0, 1, 1, 1),
|
|
1175
|
+
})
|
|
1176
|
+
|
|
1177
|
+
await renderOnce()
|
|
1178
|
+
|
|
1179
|
+
await currentMouse.pressDown(editor.x + 2, editor.y + 1)
|
|
1180
|
+
await currentMouse.moveTo(editor.x + 8, editor.y + 3)
|
|
1181
|
+
await renderOnce()
|
|
1182
|
+
|
|
1183
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1184
|
+
const selectedBeforeResize = editor.getSelectedText()
|
|
1185
|
+
|
|
1186
|
+
editor.width = 30
|
|
1187
|
+
editor.height = 8
|
|
1188
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1189
|
+
await renderOnce()
|
|
1190
|
+
|
|
1191
|
+
await currentMouse.moveTo(editor.x + 10, editor.y + 2)
|
|
1192
|
+
await renderOnce()
|
|
1193
|
+
|
|
1194
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1195
|
+
|
|
1196
|
+
await currentMouse.release(editor.x + 10, editor.y + 2)
|
|
1197
|
+
await renderOnce()
|
|
1198
|
+
|
|
1199
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1200
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1201
|
+
|
|
1202
|
+
const { bg: bgAfterResize } = buffer.buffers
|
|
1203
|
+
const bufferWidth = buffer.width
|
|
1204
|
+
|
|
1205
|
+
let selectedCellsAfterResize = 0
|
|
1206
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1207
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1208
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1209
|
+
const bgG = bgAfterResize[bufferIdx * 4 + 1]
|
|
1210
|
+
const bgB = bgAfterResize[bufferIdx * 4 + 2]
|
|
1211
|
+
if (Math.abs(bgG - 1.0) < 0.01 && Math.abs(bgB - 1.0) < 0.01) {
|
|
1212
|
+
selectedCellsAfterResize++
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
expect(selectedCellsAfterResize).toBeGreaterThan(0)
|
|
1218
|
+
|
|
1219
|
+
buffer.destroy()
|
|
1220
|
+
editor.destroy()
|
|
1221
|
+
})
|
|
1222
|
+
|
|
1223
|
+
it("should maintain selection correctly when renderable position changes during resize", async () => {
|
|
1224
|
+
const buffer = OptimizedBuffer.create(80, 24, "wcwidth")
|
|
1225
|
+
|
|
1226
|
+
const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1227
|
+
initialValue: Array.from({ length: 20 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`).join("\n"),
|
|
1228
|
+
left: 10,
|
|
1229
|
+
top: 5,
|
|
1230
|
+
width: 40,
|
|
1231
|
+
height: 10,
|
|
1232
|
+
selectable: true,
|
|
1233
|
+
selectionBg: RGBA.fromValues(1, 1, 0, 1),
|
|
1234
|
+
selectionFg: RGBA.fromValues(0, 0, 0, 1),
|
|
1235
|
+
})
|
|
1236
|
+
|
|
1237
|
+
await renderOnce()
|
|
1238
|
+
|
|
1239
|
+
const initialX = editor.x
|
|
1240
|
+
const initialY = editor.y
|
|
1241
|
+
|
|
1242
|
+
await currentMouse.drag(editor.x + 5, editor.y + 2, editor.x + 10, editor.y + 4)
|
|
1243
|
+
await renderOnce()
|
|
1244
|
+
|
|
1245
|
+
const selectedTextBefore = editor.getSelectedText()
|
|
1246
|
+
const selectionBefore = editor.getSelection()
|
|
1247
|
+
|
|
1248
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1249
|
+
expect(selectedTextBefore).toBeTruthy()
|
|
1250
|
+
|
|
1251
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1252
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1253
|
+
|
|
1254
|
+
const { bg: bgBefore } = buffer.buffers
|
|
1255
|
+
const bufferWidth = buffer.width
|
|
1256
|
+
|
|
1257
|
+
const selectedCellsBeforeCount = countSelectedCells(bgBefore, bufferWidth, editor, 1, 1, 0)
|
|
1258
|
+
expect(selectedCellsBeforeCount).toBeGreaterThan(0)
|
|
1259
|
+
|
|
1260
|
+
editor.left = 20
|
|
1261
|
+
editor.top = 10
|
|
1262
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1263
|
+
await renderOnce()
|
|
1264
|
+
|
|
1265
|
+
const newX = editor.x
|
|
1266
|
+
const newY = editor.y
|
|
1267
|
+
|
|
1268
|
+
expect(newX).not.toBe(initialX)
|
|
1269
|
+
expect(newY).not.toBe(initialY)
|
|
1270
|
+
|
|
1271
|
+
const selectedTextAfter = editor.getSelectedText()
|
|
1272
|
+
const selectionAfter = editor.getSelection()
|
|
1273
|
+
|
|
1274
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1275
|
+
expect(selectedTextAfter).toBe(selectedTextBefore)
|
|
1276
|
+
expect(selectionAfter?.start).toBe(selectionBefore?.start)
|
|
1277
|
+
expect(selectionAfter?.end).toBe(selectionBefore?.end)
|
|
1278
|
+
|
|
1279
|
+
buffer.clear(RGBA.fromValues(0, 0, 0, 1))
|
|
1280
|
+
buffer.drawEditorView(editor.editorView, editor.x, editor.y)
|
|
1281
|
+
|
|
1282
|
+
const { bg: bgAfter } = buffer.buffers
|
|
1283
|
+
const selectedCellsAfterCount = countSelectedCells(bgAfter, bufferWidth, editor, 1, 1, 0)
|
|
1284
|
+
|
|
1285
|
+
expect(selectedCellsAfterCount).toBe(selectedCellsBeforeCount)
|
|
1286
|
+
expect(selectedCellsAfterCount).toBeGreaterThan(0)
|
|
1287
|
+
|
|
1288
|
+
buffer.destroy()
|
|
1289
|
+
editor.destroy()
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
it("should keep cursor within textarea bounds after resize causes wrapping with scrolled selection", async () => {
|
|
1293
|
+
const { textarea: editor, root } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1294
|
+
initialValue: Array.from(
|
|
1295
|
+
{ length: 50 },
|
|
1296
|
+
(_, i) =>
|
|
1297
|
+
`This is a long line ${i.toString().padStart(2, "0")} with enough text to cause wrapping when narrow`,
|
|
1298
|
+
).join("\n"),
|
|
1299
|
+
width: 60,
|
|
1300
|
+
height: 10,
|
|
1301
|
+
top: 0,
|
|
1302
|
+
wrapMode: "word",
|
|
1303
|
+
selectable: true,
|
|
1304
|
+
showCursor: true,
|
|
1305
|
+
})
|
|
1306
|
+
|
|
1307
|
+
const textBelow = new TextRenderable(currentRenderer, {
|
|
1308
|
+
id: "text-below",
|
|
1309
|
+
content: "Element below textarea",
|
|
1310
|
+
top: 10,
|
|
1311
|
+
left: 0,
|
|
1312
|
+
})
|
|
1313
|
+
currentRenderer.root.add(textBelow)
|
|
1314
|
+
|
|
1315
|
+
await renderOnce()
|
|
1316
|
+
|
|
1317
|
+
editor.focus()
|
|
1318
|
+
editor.gotoLine(15)
|
|
1319
|
+
await renderOnce()
|
|
1320
|
+
|
|
1321
|
+
await currentMouse.drag(editor.x + 5, editor.y + 3, editor.x + 10, editor.y + 9)
|
|
1322
|
+
await renderOnce()
|
|
1323
|
+
|
|
1324
|
+
const viewportAfterSelection = editor.editorView.getViewport()
|
|
1325
|
+
|
|
1326
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1327
|
+
expect(viewportAfterSelection.offsetY).toBeGreaterThan(0)
|
|
1328
|
+
|
|
1329
|
+
editor.width = 8
|
|
1330
|
+
root.yogaNode.calculateLayout(80, 24)
|
|
1331
|
+
await renderOnce()
|
|
1332
|
+
|
|
1333
|
+
const viewportAfterResize = editor.editorView.getViewport()
|
|
1334
|
+
const cursorAfterResize = editor.visualCursor
|
|
1335
|
+
|
|
1336
|
+
expect(cursorAfterResize.visualRow).toBeGreaterThanOrEqual(0)
|
|
1337
|
+
expect(cursorAfterResize.visualRow).toBeLessThan(editor.height)
|
|
1338
|
+
expect(cursorAfterResize.visualCol).toBeGreaterThanOrEqual(0)
|
|
1339
|
+
expect(cursorAfterResize.visualCol).toBeLessThan(editor.width)
|
|
1340
|
+
|
|
1341
|
+
textBelow.destroy()
|
|
1342
|
+
editor.destroy()
|
|
1343
|
+
})
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
describe("Selection Preserved on Viewport Scroll", () => {
|
|
1347
|
+
it("should preserve selection when scrolling viewport", async () => {
|
|
1348
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1349
|
+
initialValue: Array.from({ length: 50 }, (_, i) => `Line ${i}`).join("\n"),
|
|
1350
|
+
width: 40,
|
|
1351
|
+
height: 10,
|
|
1352
|
+
selectable: true,
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
editor.focus()
|
|
1356
|
+
await renderOnce()
|
|
1357
|
+
|
|
1358
|
+
// Select all text using keyboard (Cmd+Shift+Down)
|
|
1359
|
+
currentMockInput.pressKey("ARROW_DOWN", { shift: true, super: true })
|
|
1360
|
+
await renderOnce()
|
|
1361
|
+
|
|
1362
|
+
const selectionBefore = editor.getSelection()
|
|
1363
|
+
const selectedTextBefore = editor.getSelectedText()
|
|
1364
|
+
|
|
1365
|
+
expect(selectionBefore).not.toBeNull()
|
|
1366
|
+
expect(selectedTextBefore).toContain("Line 0")
|
|
1367
|
+
expect(selectedTextBefore).toContain("Line 49")
|
|
1368
|
+
|
|
1369
|
+
// Start renderer to simulate real app with continuous render loop
|
|
1370
|
+
currentRenderer.start()
|
|
1371
|
+
|
|
1372
|
+
// Scroll up with mouse wheel
|
|
1373
|
+
await currentMouse.scroll(editor.x, editor.y + 1, "up")
|
|
1374
|
+
await Bun.sleep(100)
|
|
1375
|
+
|
|
1376
|
+
const selectionAfter = editor.getSelection()
|
|
1377
|
+
const selectedTextAfter = editor.getSelectedText()
|
|
1378
|
+
|
|
1379
|
+
currentRenderer.pause()
|
|
1380
|
+
|
|
1381
|
+
// Selection should not change when scrolling viewport
|
|
1382
|
+
expect(selectionAfter).not.toBeNull()
|
|
1383
|
+
expect(selectionAfter!.start).toBe(selectionBefore!.start)
|
|
1384
|
+
expect(selectionAfter!.end).toBe(selectionBefore!.end)
|
|
1385
|
+
expect(selectedTextAfter).toBe(selectedTextBefore)
|
|
1386
|
+
|
|
1387
|
+
editor.destroy()
|
|
1388
|
+
})
|
|
1389
|
+
})
|
|
1390
|
+
|
|
1391
|
+
describe("Keyboard Selection with Viewport Scrolling", () => {
|
|
1392
|
+
it("should select to buffer home after shift+end then shift+home when scrolled", async () => {
|
|
1393
|
+
const lines = Array.from({ length: 30 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`)
|
|
1394
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1395
|
+
initialValue: lines.join("\n"),
|
|
1396
|
+
width: 40,
|
|
1397
|
+
height: 6,
|
|
1398
|
+
selectable: true,
|
|
1399
|
+
})
|
|
1400
|
+
|
|
1401
|
+
editor.focus()
|
|
1402
|
+
await renderOnce()
|
|
1403
|
+
|
|
1404
|
+
for (let i = 0; i < 3; i++) {
|
|
1405
|
+
await currentMouse.scroll(editor.x + 2, editor.y + 2, "down")
|
|
1406
|
+
}
|
|
1407
|
+
await renderOnce()
|
|
1408
|
+
|
|
1409
|
+
const viewportAfterScroll = editor.editorView.getViewport()
|
|
1410
|
+
expect(viewportAfterScroll.offsetY).toBeGreaterThan(0)
|
|
1411
|
+
expect(editor.logicalCursor.row).toBeGreaterThan(0)
|
|
1412
|
+
|
|
1413
|
+
currentMockInput.pressKey("END", { shift: true })
|
|
1414
|
+
await renderOnce()
|
|
1415
|
+
|
|
1416
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1417
|
+
|
|
1418
|
+
currentMockInput.pressKey("HOME", { shift: true })
|
|
1419
|
+
await renderOnce()
|
|
1420
|
+
|
|
1421
|
+
const selection = editor.getSelection()
|
|
1422
|
+
expect(selection).not.toBeNull()
|
|
1423
|
+
expect(selection!.start).toBe(0)
|
|
1424
|
+
|
|
1425
|
+
const selectedText = editor.getSelectedText()
|
|
1426
|
+
expect(selectedText.startsWith("Line 00")).toBe(true)
|
|
1427
|
+
expect(selectedText).not.toContain("Line 29")
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1430
|
+
it("should allow shift+end after shift+home from a mid-buffer cursor", async () => {
|
|
1431
|
+
const lines = Array.from({ length: 30 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`)
|
|
1432
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1433
|
+
initialValue: lines.join("\n"),
|
|
1434
|
+
width: 40,
|
|
1435
|
+
height: 6,
|
|
1436
|
+
selectable: true,
|
|
1437
|
+
})
|
|
1438
|
+
|
|
1439
|
+
editor.focus()
|
|
1440
|
+
editor.gotoLine(10)
|
|
1441
|
+
await renderOnce()
|
|
1442
|
+
|
|
1443
|
+
currentMockInput.pressKey("END", { shift: true })
|
|
1444
|
+
await renderOnce()
|
|
1445
|
+
|
|
1446
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1447
|
+
|
|
1448
|
+
currentMockInput.pressKey("HOME", { shift: true })
|
|
1449
|
+
await renderOnce()
|
|
1450
|
+
|
|
1451
|
+
currentMockInput.pressKey("END", { shift: true })
|
|
1452
|
+
await renderOnce()
|
|
1453
|
+
|
|
1454
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1455
|
+
expect(editor.getSelectedText()).toContain("Line 29")
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
it("should select to buffer home with shift+super+up in scrollable textarea", async () => {
|
|
1459
|
+
// Create textarea with content taller than visible area
|
|
1460
|
+
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`)
|
|
1461
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1462
|
+
initialValue: lines.join("\n"),
|
|
1463
|
+
width: 40,
|
|
1464
|
+
height: 10,
|
|
1465
|
+
selectable: true,
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1468
|
+
// Move cursor to middle of content (line 25)
|
|
1469
|
+
editor.focus()
|
|
1470
|
+
editor.gotoLine(25)
|
|
1471
|
+
await renderOnce()
|
|
1472
|
+
|
|
1473
|
+
// Verify viewport has scrolled
|
|
1474
|
+
const viewportBefore = editor.editorView.getViewport()
|
|
1475
|
+
expect(viewportBefore.offsetY).toBeGreaterThan(0)
|
|
1476
|
+
|
|
1477
|
+
// Select to buffer home (shift+super+up)
|
|
1478
|
+
currentMockInput.pressKey("ARROW_UP", { shift: true, super: true })
|
|
1479
|
+
await renderOnce()
|
|
1480
|
+
|
|
1481
|
+
// Should have selection
|
|
1482
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1483
|
+
|
|
1484
|
+
// Selection should include content from line 0 to line 25
|
|
1485
|
+
const selectedText = editor.getSelectedText()
|
|
1486
|
+
expect(selectedText).toContain("Line 00")
|
|
1487
|
+
expect(selectedText).toContain("Line 24")
|
|
1488
|
+
expect(selectedText.split("\n").length).toBeGreaterThanOrEqual(25)
|
|
1489
|
+
|
|
1490
|
+
const viewportAfter = editor.editorView.getViewport()
|
|
1491
|
+
expect(viewportAfter.offsetY).toBe(0)
|
|
1492
|
+
})
|
|
1493
|
+
|
|
1494
|
+
it("should select to buffer end with shift+super+down in scrollable textarea", async () => {
|
|
1495
|
+
// Create textarea with content taller than visible area
|
|
1496
|
+
const lines = Array.from({ length: 50 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`)
|
|
1497
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1498
|
+
initialValue: lines.join("\n"),
|
|
1499
|
+
width: 40,
|
|
1500
|
+
height: 10,
|
|
1501
|
+
selectable: true,
|
|
1502
|
+
})
|
|
1503
|
+
|
|
1504
|
+
// Move cursor to line 20
|
|
1505
|
+
editor.focus()
|
|
1506
|
+
editor.gotoLine(20)
|
|
1507
|
+
await renderOnce()
|
|
1508
|
+
|
|
1509
|
+
const viewportBefore = editor.editorView.getViewport()
|
|
1510
|
+
expect(viewportBefore.offsetY).toBeGreaterThan(0)
|
|
1511
|
+
|
|
1512
|
+
// Select to buffer end (shift+super+down)
|
|
1513
|
+
currentMockInput.pressKey("ARROW_DOWN", { shift: true, super: true })
|
|
1514
|
+
await renderOnce()
|
|
1515
|
+
|
|
1516
|
+
// Should have selection
|
|
1517
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1518
|
+
|
|
1519
|
+
// Selection should include content from line 20 to line 49
|
|
1520
|
+
const selectedText = editor.getSelectedText()
|
|
1521
|
+
expect(selectedText).toContain("Line 20")
|
|
1522
|
+
expect(selectedText).toContain("Line 49")
|
|
1523
|
+
expect(selectedText.split("\n").length).toBeGreaterThanOrEqual(29)
|
|
1524
|
+
|
|
1525
|
+
const viewportAfter = editor.editorView.getViewport()
|
|
1526
|
+
const totalLines = editor.editorView.getTotalVirtualLineCount()
|
|
1527
|
+
const maxOffsetY = Math.max(0, totalLines - viewportBefore.height)
|
|
1528
|
+
expect(viewportAfter.offsetY).toBe(maxOffsetY)
|
|
1529
|
+
})
|
|
1530
|
+
|
|
1531
|
+
it("should handle selection across viewport boundaries correctly", async () => {
|
|
1532
|
+
// Create textarea with content taller than visible area
|
|
1533
|
+
const lines = Array.from({ length: 30 }, (_, i) => `Line ${i.toString().padStart(2, "0")}`)
|
|
1534
|
+
const { textarea: editor } = await createTextareaRenderable(currentRenderer, renderOnce, {
|
|
1535
|
+
initialValue: lines.join("\n"),
|
|
1536
|
+
width: 40,
|
|
1537
|
+
height: 5, // Small viewport
|
|
1538
|
+
selectable: true,
|
|
1539
|
+
})
|
|
1540
|
+
|
|
1541
|
+
// Move cursor to middle (line 15)
|
|
1542
|
+
editor.focus()
|
|
1543
|
+
editor.gotoLine(15)
|
|
1544
|
+
// Move to column 5
|
|
1545
|
+
for (let i = 0; i < 5; i++) {
|
|
1546
|
+
editor.moveCursorRight()
|
|
1547
|
+
}
|
|
1548
|
+
await renderOnce()
|
|
1549
|
+
|
|
1550
|
+
const cursorBefore = editor.editorView.getVisualCursor()
|
|
1551
|
+
expect(cursorBefore.logicalRow).toBe(15)
|
|
1552
|
+
expect(cursorBefore.logicalCol).toBe(5)
|
|
1553
|
+
|
|
1554
|
+
// Select to buffer home
|
|
1555
|
+
currentMockInput.pressKey("ARROW_UP", { shift: true, super: true })
|
|
1556
|
+
await renderOnce()
|
|
1557
|
+
|
|
1558
|
+
expect(editor.hasSelection()).toBe(true)
|
|
1559
|
+
const selectedText = editor.getSelectedText()
|
|
1560
|
+
|
|
1561
|
+
// Should select from (15, 5) to (0, 0)
|
|
1562
|
+
// First line should be complete, last line should be partial
|
|
1563
|
+
expect(selectedText.startsWith("Line 00")).toBe(true)
|
|
1564
|
+
expect(selectedText).toContain("Line 14")
|
|
1565
|
+
})
|
|
1566
|
+
})
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
function countSelectedCells(
|
|
1570
|
+
bg: Float32Array,
|
|
1571
|
+
bufferWidth: number,
|
|
1572
|
+
editor: { x: number; y: number; height: number; width: number },
|
|
1573
|
+
r: number,
|
|
1574
|
+
g: number,
|
|
1575
|
+
b: number,
|
|
1576
|
+
): number {
|
|
1577
|
+
let count = 0
|
|
1578
|
+
for (let y = 0; y < editor.height; y++) {
|
|
1579
|
+
for (let x = 0; x < editor.width; x++) {
|
|
1580
|
+
const bufferIdx = (editor.y + y) * bufferWidth + (editor.x + x)
|
|
1581
|
+
const bgR = bg[bufferIdx * 4 + 0]
|
|
1582
|
+
const bgG = bg[bufferIdx * 4 + 1]
|
|
1583
|
+
const bgB = bg[bufferIdx * 4 + 2]
|
|
1584
|
+
if (Math.abs(bgR - r) < 0.01 && Math.abs(bgG - g) < 0.01 && Math.abs(bgB - b) < 0.01) {
|
|
1585
|
+
count++
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return count
|
|
1590
|
+
}
|