@xincli/opentui-core 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- 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/test-tmux-theme-queries.sh +151 -0
- package/dev/thai-debug-test.ts +68 -0
- package/dev/theme-mode.ts +290 -0
- package/docs/development.md +144 -0
- package/package.json +102 -0
- package/prebuilt/aarch64-android/libopentui.so +0 -0
- package/scripts/build-android.ts +226 -0
- package/scripts/build-native-termux.sh +525 -0
- package/scripts/build.ts +530 -0
- package/scripts/dist-test.ts +312 -0
- package/scripts/package-prebuilt.ts +110 -0
- package/scripts/publish.ts +60 -0
- package/scripts/test-node-hook.mjs +16 -0
- package/scripts/test-node.ts +241 -0
- package/scripts/vendor-deps.sh +73 -0
- package/src/NativeSpanFeed.ts +375 -0
- package/src/Renderable.ts +1878 -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 +22 -0
- package/src/audio.ts +479 -0
- package/src/benchmark/attenuation-benchmark.ts +81 -0
- package/src/benchmark/audio-playback-benchmark.ts +344 -0
- package/src/benchmark/box-draw-benchmark.ts +1042 -0
- package/src/benchmark/colormatrix-benchmark.ts +123 -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/layout-benchmark.ts +2547 -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/render-traversal-benchmark.ts +928 -0
- package/src/benchmark/text-buffer-render-benchmark.ts +874 -0
- package/src/benchmark/text-table-benchmark.ts +948 -0
- package/src/buffer.test.ts +292 -0
- package/src/buffer.ts +564 -0
- package/src/console.test.ts +629 -0
- package/src/console.ts +1253 -0
- package/src/edit-buffer.test.ts +1852 -0
- package/src/edit-buffer.ts +417 -0
- package/src/editor-view.test.ts +1056 -0
- package/src/editor-view.ts +290 -0
- package/src/index.ts +25 -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 +60 -0
- package/src/lib/RGBA.ts +343 -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 +63 -0
- package/src/lib/clipboard.ts +41 -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 +324 -0
- package/src/lib/extmarks.test.ts +3547 -0
- package/src/lib/extmarks.ts +861 -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 +22 -0
- package/src/lib/keybinding.internal.test.ts +342 -0
- package/src/lib/keybinding.internal.ts +182 -0
- package/src/lib/objects-in-viewport.test.ts +789 -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 +347 -0
- package/src/lib/parse.keypress-kitty.test.ts +712 -0
- package/src/lib/parse.keypress-kitty.ts +470 -0
- package/src/lib/parse.keypress.test.ts +2020 -0
- package/src/lib/parse.keypress.ts +480 -0
- package/src/lib/parse.mouse.test.ts +552 -0
- package/src/lib/parse.mouse.ts +232 -0
- package/src/lib/paste.ts +18 -0
- package/src/lib/queue.ts +65 -0
- package/src/lib/render-geometry.ts +36 -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 +261 -0
- package/src/lib/singleton.ts +34 -0
- package/src/lib/stdin-parser.test.ts +2439 -0
- package/src/lib/stdin-parser.ts +2007 -0
- package/src/lib/styled-text.ts +178 -0
- package/src/lib/terminal-capability-detection.test.ts +226 -0
- package/src/lib/terminal-capability-detection.ts +98 -0
- package/src/lib/terminal-palette.test.ts +954 -0
- package/src/lib/terminal-palette.ts +441 -0
- package/src/lib/tree-sitter/assets/README.md +119 -0
- package/src/lib/tree-sitter/assets/javascript/highlights.scm +205 -0
- package/src/lib/tree-sitter/assets/javascript/tree-sitter-javascript.wasm +0 -0
- package/src/lib/tree-sitter/assets/markdown/highlights.scm +150 -0
- package/src/lib/tree-sitter/assets/markdown/injections.scm +27 -0
- package/src/lib/tree-sitter/assets/markdown/tree-sitter-markdown.wasm +0 -0
- package/src/lib/tree-sitter/assets/markdown_inline/highlights.scm +115 -0
- package/src/lib/tree-sitter/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
- package/src/lib/tree-sitter/assets/typescript/highlights.scm +604 -0
- package/src/lib/tree-sitter/assets/typescript/tree-sitter-typescript.wasm +0 -0
- package/src/lib/tree-sitter/assets/update.ts +360 -0
- package/src/lib/tree-sitter/assets/zig/highlights.scm +284 -0
- package/src/lib/tree-sitter/assets/zig/tree-sitter-zig.wasm +0 -0
- package/src/lib/tree-sitter/assets.d.ts +9 -0
- package/src/lib/tree-sitter/cache.test.ts +291 -0
- package/src/lib/tree-sitter/client.test.ts +1434 -0
- package/src/lib/tree-sitter/client.ts +847 -0
- package/src/lib/tree-sitter/default-parsers.ts +133 -0
- package/src/lib/tree-sitter/download-utils.ts +148 -0
- package/src/lib/tree-sitter/index.ts +75 -0
- package/src/lib/tree-sitter/parser.worker.ts +1098 -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 +132 -0
- package/src/lib/tree-sitter/update-assets.ts +10 -0
- package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
- package/src/lib/tree-sitter-styled-text.ts +314 -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/native-event-worker-repro.test.ts +48 -0
- package/src/native-event-worker-repro.worker.ts +9 -0
- package/src/native-handle.test.ts +221 -0
- package/src/platform/ffi.test.ts +516 -0
- package/src/platform/ffi.ts +653 -0
- package/src/platform/runtime.test.ts +52 -0
- package/src/platform/runtime.ts +121 -0
- package/src/platform/test.ts +45 -0
- package/src/platform/worker-handler-cleanup.fixture.ts +12 -0
- package/src/platform/worker-onmessage-startup.fixture.ts +10 -0
- package/src/platform/worker-startup.fixture.ts +9 -0
- package/src/platform/worker.node-test.ts +153 -0
- package/src/platform/worker.ts +425 -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 +948 -0
- package/src/post/filters.ts +512 -0
- package/src/post/matrices.ts +288 -0
- package/src/renderables/ASCIIFont.ts +219 -0
- package/src/renderables/Box.test.ts +474 -0
- package/src/renderables/Box.ts +353 -0
- package/src/renderables/Code.test.ts +2388 -0
- package/src/renderables/Code.ts +555 -0
- package/src/renderables/Diff.regression.test.ts +381 -0
- package/src/renderables/Diff.test.ts +3212 -0
- package/src/renderables/Diff.ts +1278 -0
- package/src/renderables/EditBufferRenderable.test.ts +288 -0
- package/src/renderables/EditBufferRenderable.ts +1152 -0
- package/src/renderables/FrameBuffer.ts +47 -0
- package/src/renderables/Input.test.ts +1310 -0
- package/src/renderables/Input.ts +273 -0
- package/src/renderables/LineNumberRenderable.ts +725 -0
- package/src/renderables/Markdown.ts +2144 -0
- package/src/renderables/ScrollBar.ts +423 -0
- package/src/renderables/ScrollBox.ts +903 -0
- package/src/renderables/Select.test.ts +1134 -0
- package/src/renderables/Select.ts +535 -0
- package/src/renderables/Slider.test.ts +456 -0
- package/src/renderables/Slider.ts +342 -0
- package/src/renderables/TabSelect.test.ts +238 -0
- package/src/renderables/TabSelect.ts +447 -0
- package/src/renderables/Text.selection-buffer.test.ts +118 -0
- package/src/renderables/Text.test.ts +2662 -0
- package/src/renderables/Text.ts +147 -0
- package/src/renderables/TextBufferRenderable.ts +504 -0
- package/src/renderables/TextNode.test.ts +1137 -0
- package/src/renderables/TextNode.ts +379 -0
- package/src/renderables/TextTable.test.ts +1411 -0
- package/src/renderables/TextTable.ts +1434 -0
- package/src/renderables/Textarea.ts +425 -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__/Code.test.ts +55 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +827 -0
- package/src/renderables/__tests__/LineNumberRenderable.test.ts +1906 -0
- package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
- package/src/renderables/__tests__/Markdown.code-colors.test.ts +288 -0
- package/src/renderables/__tests__/Markdown.test.ts +4175 -0
- package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
- package/src/renderables/__tests__/Textarea.buffer.test.ts +683 -0
- package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
- package/src/renderables/__tests__/Textarea.editing.test.ts +2120 -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 +3275 -0
- package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
- package/src/renderables/__tests__/Textarea.rendering.test.ts +1927 -0
- package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
- package/src/renderables/__tests__/Textarea.selection.test.ts +1589 -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 +241 -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 +277 -0
- package/src/renderables/index.ts +23 -0
- package/src/renderables/markdown-parser.ts +79 -0
- package/src/renderer-theme-mode.ts +199 -0
- package/src/renderer.ts +4975 -0
- package/src/runtime-plugin-support-configure.ts +63 -0
- package/src/runtime-plugin-support.ts +13 -0
- package/src/runtime-plugin.ts +626 -0
- package/src/specs/terminal-startup.md +64 -0
- package/src/syntax-style.test.ts +841 -0
- package/src/syntax-style.ts +274 -0
- package/src/testing/README.md +219 -0
- package/src/testing/bun-test-node.ts +880 -0
- package/src/testing/capture-spans.test.ts +216 -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 +81 -0
- package/src/testing/spy.ts +13 -0
- package/src/testing/terminal-capabilities.ts +47 -0
- package/src/testing/test-recorder.test.ts +410 -0
- package/src/testing/test-recorder.ts +133 -0
- package/src/testing/test-renderer.ts +375 -0
- package/src/testing/test-renderer.wait.test.ts +278 -0
- package/src/testing/test-streams.ts +31 -0
- package/src/testing.ts +9 -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 +53 -0
- package/src/tests/audio.test.ts +303 -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 +62 -0
- package/src/tests/ffi-borrowed-pointer-callsites.test.ts +153 -0
- package/src/tests/hover-cursor.test.ts +98 -0
- package/src/tests/native-backed-measurement-lifecycle.test.ts +133 -0
- package/src/tests/native-backed-measurement-parity.test.ts +322 -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 +384 -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 +1851 -0
- package/src/tests/renderer.clock.test.ts +270 -0
- package/src/tests/renderer.console-startup.test.ts +2151 -0
- package/src/tests/renderer.control.test.ts +458 -0
- package/src/tests/renderer.core-slot-binding.test.ts +962 -0
- package/src/tests/renderer.cursor.test.ts +26 -0
- package/src/tests/renderer.custom-stdout.test.ts +1112 -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 +329 -0
- package/src/tests/renderer.idle.test.ts +219 -0
- package/src/tests/renderer.input.test.ts +2615 -0
- package/src/tests/renderer.kitty-flags.test.ts +195 -0
- package/src/tests/renderer.lifecycle.test.ts +34 -0
- package/src/tests/renderer.mouse.test.ts +1315 -0
- package/src/tests/renderer.notifications.test.ts +27 -0
- package/src/tests/renderer.palette.test.ts +1199 -0
- package/src/tests/renderer.remote.test.ts +247 -0
- package/src/tests/renderer.render-stats.test.ts +146 -0
- package/src/tests/renderer.scrollback-surface.test.ts +474 -0
- package/src/tests/renderer.selection.test.ts +138 -0
- package/src/tests/renderer.slot-registry.test.ts +684 -0
- package/src/tests/renderer.tracker.test.ts +202 -0
- package/src/tests/renderer.useMouse.test.ts +83 -0
- package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
- package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +48 -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-configure.fixture.ts +31 -0
- package/src/tests/runtime-plugin-support-late-addition.fixture.ts +17 -0
- package/src/tests/runtime-plugin-support.fixture.ts +11 -0
- package/src/tests/runtime-plugin-support.test.ts +54 -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 +369 -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 +1895 -0
- package/src/tests/wrap-resize-perf.test.ts +276 -0
- package/src/tests/yoga-api-coverage.test.ts +409 -0
- package/src/tests/yoga-callback-stress.test.ts +122 -0
- package/src/tests/yoga-ffi.test.ts +117 -0
- package/src/tests/yoga-setters.test.ts +921 -0
- package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +124 -0
- package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +157 -0
- package/src/tests/yoga-upstream/YGErrataTest.test.ts +40 -0
- package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +23 -0
- package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +81 -0
- package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +31 -0
- package/src/tests/yoga-upstream/YGMeasureTest.test.ts +70 -0
- package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +1864 -0
- package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +5829 -0
- package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +2444 -0
- package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +293 -0
- package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +315 -0
- package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +231 -0
- package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +347 -0
- package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +256 -0
- package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +2747 -0
- package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +129 -0
- package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +109 -0
- package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +1097 -0
- package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +4562 -0
- package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +677 -0
- package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +2071 -0
- package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +3303 -0
- package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +582 -0
- package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +2150 -0
- package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +1963 -0
- package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +1446 -0
- package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +354 -0
- package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +1760 -0
- package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +1161 -0
- package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +207 -0
- package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +6338 -0
- package/src/tests/yoga-upstream/tools/MeasureCounter.ts +54 -0
- package/src/tests/yoga-upstream/tools/utils.ts +81 -0
- package/src/text-buffer-view.test.ts +722 -0
- package/src/text-buffer-view.ts +193 -0
- package/src/text-buffer.test.ts +512 -0
- package/src/text-buffer.ts +250 -0
- package/src/types.ts +199 -0
- package/src/utils.ts +88 -0
- package/src/yoga.ts +1146 -0
- package/src/zig/ansi.zig +502 -0
- package/src/zig/audio.zig +1206 -0
- package/src/zig/bench/README.md +50 -0
- package/src/zig/bench/buffer-color-blending_bench.zig +350 -0
- package/src/zig/bench/buffer-draw-box_bench.zig +786 -0
- package/src/zig/bench/buffer-draw-text-buffer_bench.zig +894 -0
- package/src/zig/bench/edit-buffer_bench.zig +476 -0
- package/src/zig/bench/editor-view_bench.zig +169 -0
- package/src/zig/bench/native-span-feed_bench.zig +107 -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 +476 -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 +272 -0
- package/src/zig/bench/utf8_bench.zig +799 -0
- package/src/zig/bench-utils.zig +431 -0
- package/src/zig/bench.zig +223 -0
- package/src/zig/buffer-methods.zig +215 -0
- package/src/zig/buffer.zig +2515 -0
- package/src/zig/build.zig +700 -0
- package/src/zig/build.zig.zon +19 -0
- package/src/zig/edit-buffer.zig +900 -0
- package/src/zig/editor-view.zig +812 -0
- package/src/zig/event-bus.zig +28 -0
- package/src/zig/event-emitter.zig +65 -0
- package/src/zig/file-logger.zig +92 -0
- package/src/zig/grapheme.zig +838 -0
- package/src/zig/handles.zig +267 -0
- package/src/zig/lib.zig +2813 -0
- package/src/zig/link.zig +349 -0
- package/src/zig/logger.zig +43 -0
- package/src/zig/mem-registry.zig +126 -0
- package/src/zig/miniaudio_shim.c +21 -0
- package/src/zig/native-renderable.zig +112 -0
- package/src/zig/native-span-feed-bench-lib.zig +7 -0
- package/src/zig/native-span-feed.zig +747 -0
- package/src/zig/renderer-output.zig +677 -0
- package/src/zig/renderer.zig +2106 -0
- package/src/zig/rope.zig +1220 -0
- package/src/zig/split-scrollback.zig +88 -0
- package/src/zig/syntax-style.zig +174 -0
- package/src/zig/terminal.zig +1652 -0
- package/src/zig/test.zig +82 -0
- package/src/zig/tests/README.md +18 -0
- package/src/zig/tests/ansi_test.zig +18 -0
- package/src/zig/tests/audio_test.zig +439 -0
- package/src/zig/tests/buffer-methods_test.zig +1111 -0
- package/src/zig/tests/buffer_test.zig +2926 -0
- package/src/zig/tests/edit-buffer-history_test.zig +299 -0
- package/src/zig/tests/edit-buffer_test.zig +1688 -0
- package/src/zig/tests/editor-view_test.zig +3454 -0
- package/src/zig/tests/event-emitter_test.zig +249 -0
- package/src/zig/tests/grapheme_test.zig +1329 -0
- package/src/zig/tests/handles_test.zig +123 -0
- package/src/zig/tests/link_test.zig +220 -0
- package/src/zig/tests/mem-registry_test.zig +473 -0
- package/src/zig/tests/memory_leak_regression_test.zig +234 -0
- package/src/zig/tests/native-renderable_test.zig +16 -0
- package/src/zig/tests/native-span-feed_test.zig +1266 -0
- package/src/zig/tests/renderer_test.zig +2604 -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/split-scrollback_test.zig +70 -0
- package/src/zig/tests/syntax-style_test.zig +558 -0
- package/src/zig/tests/terminal_test.zig +1489 -0
- package/src/zig/tests/test-renderer.zig +126 -0
- package/src/zig/tests/text-buffer-drawing_test.zig +3291 -0
- package/src/zig/tests/text-buffer-highlights_test.zig +706 -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 +1056 -0
- package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
- package/src/zig/tests/text-buffer-view_test.zig +3672 -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 +4060 -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/tests/yoga_test.zig +65 -0
- package/src/zig/text-buffer-iterators.zig +496 -0
- package/src/zig/text-buffer-segment.zig +403 -0
- package/src/zig/text-buffer-view.zig +1428 -0
- package/src/zig/text-buffer.zig +1272 -0
- package/src/zig/utf8.zig +1954 -0
- package/src/zig/utils.zig +12 -0
- package/src/zig/uucode-stub.zig +76 -0
- package/src/zig/vendor/miniaudio/miniaudio.h +93468 -0
- package/src/zig/yoga.zig +658 -0
- package/src/zig-structs.ts +381 -0
- package/src/zig.ts +5260 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node-test.json +138 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
|
2
|
+
import { createTestRenderer, type TestRenderer } from "./test-renderer.js"
|
|
3
|
+
import { TextRenderable } from "../renderables/Text.js"
|
|
4
|
+
import { BoxRenderable } from "../renderables/Box.js"
|
|
5
|
+
import { TextAttributes, type CapturedFrame } from "../types.js"
|
|
6
|
+
import { RGBA } from "../lib/index.js"
|
|
7
|
+
import { StyledText } from "../lib/styled-text.js"
|
|
8
|
+
|
|
9
|
+
describe("captureSpans", () => {
|
|
10
|
+
let renderer: TestRenderer
|
|
11
|
+
let renderOnce: () => Promise<void>
|
|
12
|
+
let captureSpans: () => CapturedFrame
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
const setup = await createTestRenderer({ width: 40, height: 10 })
|
|
16
|
+
renderer = setup.renderer
|
|
17
|
+
renderOnce = setup.renderOnce
|
|
18
|
+
captureSpans = setup.captureSpans
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
renderer.destroy()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("returns correct dimensions and line count", async () => {
|
|
26
|
+
await renderOnce()
|
|
27
|
+
const data = captureSpans()
|
|
28
|
+
|
|
29
|
+
expect(data.cols).toBe(40)
|
|
30
|
+
expect(data.rows).toBe(10)
|
|
31
|
+
expect(data.lines.length).toBe(10)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("captures text content in spans", async () => {
|
|
35
|
+
const text = new TextRenderable(renderer, { content: "Hello World" })
|
|
36
|
+
renderer.root.add(text)
|
|
37
|
+
await renderOnce()
|
|
38
|
+
|
|
39
|
+
const data = captureSpans()
|
|
40
|
+
const firstLine = data.lines[0]
|
|
41
|
+
const textContent = firstLine.spans.map((s) => s.text).join("")
|
|
42
|
+
|
|
43
|
+
expect(textContent).toContain("Hello World")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("groups consecutive cells with same styling into single span", async () => {
|
|
47
|
+
const text = new TextRenderable(renderer, { content: "AAAA" })
|
|
48
|
+
renderer.root.add(text)
|
|
49
|
+
await renderOnce()
|
|
50
|
+
|
|
51
|
+
const data = captureSpans()
|
|
52
|
+
const firstLine = data.lines[0]
|
|
53
|
+
const aaaSpan = firstLine.spans.find((s) => s.text.includes("AAAA"))
|
|
54
|
+
|
|
55
|
+
expect(aaaSpan).toBeDefined()
|
|
56
|
+
expect(aaaSpan!.width).toBeGreaterThanOrEqual(4)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("captures foreground color", async () => {
|
|
60
|
+
const text = new TextRenderable(renderer, {
|
|
61
|
+
content: "Red Text",
|
|
62
|
+
fg: RGBA.fromHex("#ff0000"),
|
|
63
|
+
})
|
|
64
|
+
renderer.root.add(text)
|
|
65
|
+
await renderOnce()
|
|
66
|
+
|
|
67
|
+
const data = captureSpans()
|
|
68
|
+
const firstLine = data.lines[0]
|
|
69
|
+
const redSpan = firstLine.spans.find((s) => s.text.includes("Red"))
|
|
70
|
+
|
|
71
|
+
expect(redSpan).toBeDefined()
|
|
72
|
+
expect(redSpan!.fg.r).toBe(1)
|
|
73
|
+
expect(redSpan!.fg.g).toBe(0)
|
|
74
|
+
expect(redSpan!.fg.b).toBe(0)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("captures background color", async () => {
|
|
78
|
+
const box = new BoxRenderable(renderer, {
|
|
79
|
+
width: 10,
|
|
80
|
+
height: 3,
|
|
81
|
+
backgroundColor: RGBA.fromHex("#00ff00"),
|
|
82
|
+
})
|
|
83
|
+
renderer.root.add(box)
|
|
84
|
+
await renderOnce()
|
|
85
|
+
|
|
86
|
+
const data = captureSpans()
|
|
87
|
+
const secondLine = data.lines[1]
|
|
88
|
+
const greenSpan = secondLine.spans.find((s) => s.bg.g === 1 && s.bg.r === 0 && s.bg.b === 0)
|
|
89
|
+
|
|
90
|
+
expect(greenSpan).toBeDefined()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("returns alpha 0 for transparent colors", async () => {
|
|
94
|
+
await renderOnce()
|
|
95
|
+
|
|
96
|
+
const data = captureSpans()
|
|
97
|
+
const firstLine = data.lines[0]
|
|
98
|
+
const transparentSpan = firstLine.spans.find((s) => s.bg.a === 0)
|
|
99
|
+
|
|
100
|
+
expect(transparentSpan).toBeDefined()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("captures text attributes", async () => {
|
|
104
|
+
const text = new TextRenderable(renderer, {
|
|
105
|
+
content: "Styled",
|
|
106
|
+
attributes: TextAttributes.BOLD | TextAttributes.ITALIC | TextAttributes.UNDERLINE | TextAttributes.DIM,
|
|
107
|
+
})
|
|
108
|
+
renderer.root.add(text)
|
|
109
|
+
await renderOnce()
|
|
110
|
+
|
|
111
|
+
const data = captureSpans()
|
|
112
|
+
const firstLine = data.lines[0]
|
|
113
|
+
const styledSpan = firstLine.spans.find((s) => s.text.includes("Styled"))
|
|
114
|
+
|
|
115
|
+
expect(styledSpan).toBeDefined()
|
|
116
|
+
expect(styledSpan!.attributes & TextAttributes.BOLD).toBeTruthy()
|
|
117
|
+
expect(styledSpan!.attributes & TextAttributes.ITALIC).toBeTruthy()
|
|
118
|
+
expect(styledSpan!.attributes & TextAttributes.UNDERLINE).toBeTruthy()
|
|
119
|
+
expect(styledSpan!.attributes & TextAttributes.DIM).toBeTruthy()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("includes cursor position", async () => {
|
|
123
|
+
await renderOnce()
|
|
124
|
+
const data = captureSpans()
|
|
125
|
+
|
|
126
|
+
expect(data.cursor).toEqual([expect.any(Number), expect.any(Number)])
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("splits spans when styling changes", async () => {
|
|
130
|
+
const text1 = new TextRenderable(renderer, {
|
|
131
|
+
content: "AAA",
|
|
132
|
+
fg: RGBA.fromHex("#ff0000"),
|
|
133
|
+
})
|
|
134
|
+
const text2 = new TextRenderable(renderer, {
|
|
135
|
+
content: "BBB",
|
|
136
|
+
fg: RGBA.fromHex("#00ff00"),
|
|
137
|
+
})
|
|
138
|
+
renderer.root.add(text1)
|
|
139
|
+
renderer.root.add(text2)
|
|
140
|
+
await renderOnce()
|
|
141
|
+
|
|
142
|
+
const data = captureSpans()
|
|
143
|
+
const allSpans = data.lines.flatMap((l) => l.spans)
|
|
144
|
+
|
|
145
|
+
expect(allSpans.some((s) => s.fg.r === 1 && s.fg.g === 0)).toBe(true)
|
|
146
|
+
expect(allSpans.some((s) => s.fg.g === 1 && s.fg.r === 0)).toBe(true)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test("preserves tag-only color intent differences in captured spans", async () => {
|
|
150
|
+
const text = new TextRenderable(renderer, {
|
|
151
|
+
content: new StyledText([
|
|
152
|
+
{ __isChunk: true, text: "A", fg: RGBA.fromHex("#aabbcc") },
|
|
153
|
+
{ __isChunk: true, text: "B", fg: RGBA.defaultForeground("#aabbcc") },
|
|
154
|
+
]),
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
renderer.root.add(text)
|
|
158
|
+
await renderOnce()
|
|
159
|
+
|
|
160
|
+
const firstLine = captureSpans().lines[0]
|
|
161
|
+
const contentSpans = firstLine.spans.filter((span) => span.text.trim().length > 0)
|
|
162
|
+
|
|
163
|
+
expect(contentSpans).toHaveLength(2)
|
|
164
|
+
expect(contentSpans[0].text).toBe("A")
|
|
165
|
+
expect(contentSpans[1].text).toBe("B")
|
|
166
|
+
expect(contentSpans[0].fg.intent).toBe("rgb")
|
|
167
|
+
expect(contentSpans[1].fg.intent).toBe("default")
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test("handles box-drawing characters without crashing", async () => {
|
|
171
|
+
const text = new TextRenderable(renderer, {
|
|
172
|
+
content: "├── folder",
|
|
173
|
+
})
|
|
174
|
+
renderer.root.add(text)
|
|
175
|
+
await renderOnce()
|
|
176
|
+
|
|
177
|
+
const data = captureSpans()
|
|
178
|
+
const firstLine = data.lines[0]
|
|
179
|
+
const textContent = firstLine.spans.map((s) => s.text).join("")
|
|
180
|
+
|
|
181
|
+
expect(textContent).toContain("├── folder")
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("handles box borders without crashing", async () => {
|
|
185
|
+
const box = new BoxRenderable(renderer, {
|
|
186
|
+
width: 10,
|
|
187
|
+
height: 4,
|
|
188
|
+
border: true,
|
|
189
|
+
borderStyle: "single",
|
|
190
|
+
borderColor: RGBA.fromHex("#ffffff"),
|
|
191
|
+
})
|
|
192
|
+
renderer.root.add(box)
|
|
193
|
+
await renderOnce()
|
|
194
|
+
|
|
195
|
+
const data = captureSpans()
|
|
196
|
+
expect(data.lines.length).toBe(10)
|
|
197
|
+
|
|
198
|
+
const firstLine = data.lines[0]
|
|
199
|
+
const textContent = firstLine.spans.map((s) => s.text).join("")
|
|
200
|
+
expect(textContent.includes("┌") || textContent.includes("─")).toBe(true)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test("handles multi-width characters correctly", async () => {
|
|
204
|
+
const text = new TextRenderable(renderer, {
|
|
205
|
+
content: "A🌟B",
|
|
206
|
+
})
|
|
207
|
+
renderer.root.add(text)
|
|
208
|
+
await renderOnce()
|
|
209
|
+
|
|
210
|
+
const data = captureSpans()
|
|
211
|
+
const firstLine = data.lines[0]
|
|
212
|
+
const textContent = firstLine.spans.map((s) => s.text).join("")
|
|
213
|
+
|
|
214
|
+
expect(textContent).toContain("A🌟B")
|
|
215
|
+
})
|
|
216
|
+
})
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test"
|
|
2
|
+
import { createMockMouse, MouseButtons } from "./mock-mouse.js"
|
|
3
|
+
import { MouseParser } from "../lib/parse.mouse.js"
|
|
4
|
+
|
|
5
|
+
class MockRenderer {
|
|
6
|
+
public stdin: { emit: (event: string, data: Buffer) => void }
|
|
7
|
+
public emittedData: Buffer[] = []
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.stdin = {
|
|
11
|
+
emit: (event: string, chunk: Buffer) => {
|
|
12
|
+
this.emittedData.push(chunk)
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getEmittedData(): Buffer {
|
|
18
|
+
return Buffer.concat(this.emittedData)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Helper function to parse all events from buffer
|
|
23
|
+
function parseAllEvents(emittedData: Buffer, parser: MouseParser) {
|
|
24
|
+
return parser.parseAllMouseEvents(emittedData)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("mock-mouse + parser integration", () => {
|
|
28
|
+
test("simple click is correctly parsed", async () => {
|
|
29
|
+
const mockRenderer = new MockRenderer()
|
|
30
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
31
|
+
const parser = new MouseParser()
|
|
32
|
+
|
|
33
|
+
await mockMouse.click(10, 5)
|
|
34
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
35
|
+
|
|
36
|
+
expect(parsedEvents).toHaveLength(2)
|
|
37
|
+
expect(parsedEvents[0]).toEqual({
|
|
38
|
+
type: "down",
|
|
39
|
+
button: 0,
|
|
40
|
+
x: 10,
|
|
41
|
+
y: 5,
|
|
42
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
43
|
+
scroll: undefined,
|
|
44
|
+
})
|
|
45
|
+
expect(parsedEvents[1]).toEqual({
|
|
46
|
+
type: "up",
|
|
47
|
+
button: 0,
|
|
48
|
+
x: 10,
|
|
49
|
+
y: 5,
|
|
50
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
51
|
+
scroll: undefined,
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("double click is correctly parsed", async () => {
|
|
56
|
+
const mockRenderer = new MockRenderer()
|
|
57
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
58
|
+
const parser = new MouseParser()
|
|
59
|
+
|
|
60
|
+
await mockMouse.doubleClick(10, 5)
|
|
61
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
62
|
+
|
|
63
|
+
expect(parsedEvents).toHaveLength(4)
|
|
64
|
+
// All events should be at the same position with LEFT button
|
|
65
|
+
parsedEvents.forEach((event) => {
|
|
66
|
+
expect(event.x).toBe(10)
|
|
67
|
+
expect(event.y).toBe(5)
|
|
68
|
+
expect(event.button).toBe(0)
|
|
69
|
+
})
|
|
70
|
+
// Should alternate down/up
|
|
71
|
+
expect(parsedEvents.map((e) => e.type)).toEqual(["down", "up", "down", "up"])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("pressDown and release separately are correctly parsed", async () => {
|
|
75
|
+
const mockRenderer = new MockRenderer()
|
|
76
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
77
|
+
const parser = new MouseParser()
|
|
78
|
+
|
|
79
|
+
await mockMouse.pressDown(10, 5, MouseButtons.MIDDLE)
|
|
80
|
+
await mockMouse.release(10, 5, MouseButtons.MIDDLE)
|
|
81
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
82
|
+
|
|
83
|
+
expect(parsedEvents).toHaveLength(2)
|
|
84
|
+
expect(parsedEvents[0]).toEqual({
|
|
85
|
+
type: "down",
|
|
86
|
+
button: 1, // MIDDLE button
|
|
87
|
+
x: 10,
|
|
88
|
+
y: 5,
|
|
89
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
90
|
+
scroll: undefined,
|
|
91
|
+
})
|
|
92
|
+
expect(parsedEvents[1]).toEqual({
|
|
93
|
+
type: "up",
|
|
94
|
+
button: 1,
|
|
95
|
+
x: 10,
|
|
96
|
+
y: 5,
|
|
97
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
98
|
+
scroll: undefined,
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("different buttons work correctly", async () => {
|
|
103
|
+
const mockRenderer = new MockRenderer()
|
|
104
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
105
|
+
const parser = new MouseParser()
|
|
106
|
+
|
|
107
|
+
// Test RIGHT button
|
|
108
|
+
await mockMouse.click(10, 5, MouseButtons.RIGHT)
|
|
109
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
110
|
+
|
|
111
|
+
expect(parsedEvents).toHaveLength(2)
|
|
112
|
+
parsedEvents.forEach((event) => {
|
|
113
|
+
expect(event.button).toBe(2) // RIGHT button
|
|
114
|
+
expect(event.x).toBe(10)
|
|
115
|
+
expect(event.y).toBe(5)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("all scroll directions are correctly parsed", async () => {
|
|
120
|
+
const mockRenderer = new MockRenderer()
|
|
121
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
122
|
+
const parser = new MouseParser()
|
|
123
|
+
|
|
124
|
+
await mockMouse.scroll(15, 8, "up")
|
|
125
|
+
await mockMouse.scroll(15, 8, "down")
|
|
126
|
+
await mockMouse.scroll(15, 8, "left")
|
|
127
|
+
await mockMouse.scroll(15, 8, "right")
|
|
128
|
+
|
|
129
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
130
|
+
|
|
131
|
+
expect(parsedEvents).toHaveLength(4)
|
|
132
|
+
const expectedDirections: ("up" | "down" | "left" | "right")[] = ["up", "down", "left", "right"]
|
|
133
|
+
const expectedButtons = [0, 1, 2, 0] // Based on parser logic: button & 3, with button=3 becoming 0
|
|
134
|
+
parsedEvents.forEach((event, index) => {
|
|
135
|
+
expect(event.type).toBe("scroll")
|
|
136
|
+
expect(event.button).toBe(expectedButtons[index])
|
|
137
|
+
expect(event.x).toBe(15)
|
|
138
|
+
expect(event.y).toBe(8)
|
|
139
|
+
expect(event.scroll).toEqual({
|
|
140
|
+
direction: expectedDirections[index],
|
|
141
|
+
delta: 1,
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("scroll with modifiers is correctly parsed", async () => {
|
|
147
|
+
const mockRenderer = new MockRenderer()
|
|
148
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
149
|
+
const parser = new MouseParser()
|
|
150
|
+
|
|
151
|
+
await mockMouse.scroll(15, 8, "left", { modifiers: { shift: true } })
|
|
152
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
153
|
+
|
|
154
|
+
expect(parsedEvents).toHaveLength(1)
|
|
155
|
+
expect(parsedEvents[0]).toEqual({
|
|
156
|
+
type: "scroll",
|
|
157
|
+
button: 2, // WHEEL_LEFT (66) & 3 = 2
|
|
158
|
+
x: 15,
|
|
159
|
+
y: 8,
|
|
160
|
+
modifiers: { shift: true, alt: false, ctrl: false },
|
|
161
|
+
scroll: { direction: "left", delta: 1 },
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test("drag events are correctly parsed", async () => {
|
|
166
|
+
const mockRenderer = new MockRenderer()
|
|
167
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
168
|
+
const parser = new MouseParser()
|
|
169
|
+
|
|
170
|
+
await mockMouse.drag(5, 5, 15, 10)
|
|
171
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
172
|
+
|
|
173
|
+
// Should have down, several drag events, and up
|
|
174
|
+
expect(parsedEvents.length).toBeGreaterThan(3)
|
|
175
|
+
expect(parsedEvents[0].type).toBe("down")
|
|
176
|
+
expect(parsedEvents[0].button).toBe(0)
|
|
177
|
+
expect(parsedEvents[0].x).toBe(5)
|
|
178
|
+
expect(parsedEvents[0].y).toBe(5)
|
|
179
|
+
|
|
180
|
+
// Check that intermediate events are drag events
|
|
181
|
+
for (let i = 1; i < parsedEvents.length - 1; i++) {
|
|
182
|
+
expect(parsedEvents[i].type).toBe("drag")
|
|
183
|
+
expect(parsedEvents[i].button).toBe(0)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const lastEvent = parsedEvents[parsedEvents.length - 1]
|
|
187
|
+
expect(lastEvent.type).toBe("up")
|
|
188
|
+
expect(lastEvent.x).toBe(15)
|
|
189
|
+
expect(lastEvent.y).toBe(10)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test("moveTo without button press generates move events", async () => {
|
|
193
|
+
const mockRenderer = new MockRenderer()
|
|
194
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
195
|
+
const parser = new MouseParser()
|
|
196
|
+
|
|
197
|
+
await mockMouse.moveTo(15, 8)
|
|
198
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
199
|
+
|
|
200
|
+
expect(parsedEvents).toHaveLength(1)
|
|
201
|
+
expect(parsedEvents[0]).toEqual({
|
|
202
|
+
type: "move",
|
|
203
|
+
button: 0,
|
|
204
|
+
x: 15,
|
|
205
|
+
y: 8,
|
|
206
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
207
|
+
scroll: undefined,
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test("moveTo with button press generates drag events", async () => {
|
|
212
|
+
const mockRenderer = new MockRenderer()
|
|
213
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
214
|
+
const parser = new MouseParser()
|
|
215
|
+
|
|
216
|
+
await mockMouse.pressDown(5, 5)
|
|
217
|
+
await mockMouse.moveTo(15, 8)
|
|
218
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
219
|
+
|
|
220
|
+
expect(parsedEvents).toHaveLength(2)
|
|
221
|
+
expect(parsedEvents[0].type).toBe("down")
|
|
222
|
+
expect(parsedEvents[1].type).toBe("drag")
|
|
223
|
+
expect(parsedEvents[1].x).toBe(15)
|
|
224
|
+
expect(parsedEvents[1].y).toBe(8)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test("all modifier combinations work", async () => {
|
|
228
|
+
const mockRenderer = new MockRenderer()
|
|
229
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
230
|
+
const parser = new MouseParser()
|
|
231
|
+
|
|
232
|
+
const modifierCombos = [
|
|
233
|
+
{ shift: true },
|
|
234
|
+
{ alt: true },
|
|
235
|
+
{ ctrl: true },
|
|
236
|
+
{ shift: true, alt: true },
|
|
237
|
+
{ shift: true, ctrl: true },
|
|
238
|
+
{ alt: true, ctrl: true },
|
|
239
|
+
{ shift: true, alt: true, ctrl: true },
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
for (const modifiers of modifierCombos) {
|
|
243
|
+
const testRenderer = new MockRenderer()
|
|
244
|
+
const testMouse = createMockMouse(testRenderer as any)
|
|
245
|
+
const testParser = new MouseParser()
|
|
246
|
+
|
|
247
|
+
await testMouse.click(10, 5, MouseButtons.LEFT, { modifiers })
|
|
248
|
+
const parsedEvents = parseAllEvents(testRenderer.getEmittedData(), testParser)
|
|
249
|
+
|
|
250
|
+
expect(parsedEvents).toHaveLength(2)
|
|
251
|
+
parsedEvents.forEach((event) => {
|
|
252
|
+
expect(event.modifiers).toEqual({
|
|
253
|
+
...modifiers,
|
|
254
|
+
shift: modifiers.shift || false,
|
|
255
|
+
alt: modifiers.alt || false,
|
|
256
|
+
ctrl: modifiers.ctrl || false,
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test("drag with different button and modifiers", async () => {
|
|
263
|
+
const mockRenderer = new MockRenderer()
|
|
264
|
+
const mockMouse = createMockMouse(mockRenderer as any)
|
|
265
|
+
const parser = new MouseParser()
|
|
266
|
+
|
|
267
|
+
await mockMouse.drag(5, 5, 15, 10, MouseButtons.RIGHT, { modifiers: { alt: true } })
|
|
268
|
+
const parsedEvents = parseAllEvents(mockRenderer.getEmittedData(), parser)
|
|
269
|
+
|
|
270
|
+
expect(parsedEvents.length).toBeGreaterThan(3)
|
|
271
|
+
parsedEvents.forEach((event) => {
|
|
272
|
+
expect(event.button).toBe(2) // RIGHT button
|
|
273
|
+
expect(event.modifiers.alt).toBe(true)
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Clock, TimerHandle } from "../lib/clock.js"
|
|
2
|
+
|
|
3
|
+
interface ScheduledTimer {
|
|
4
|
+
id: number
|
|
5
|
+
fireAt: number
|
|
6
|
+
order: number
|
|
7
|
+
delayMs: number
|
|
8
|
+
repeat: boolean
|
|
9
|
+
fn: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function compareTimers(left: ScheduledTimer, right: ScheduledTimer): number {
|
|
13
|
+
if (left.fireAt !== right.fireAt) {
|
|
14
|
+
return left.fireAt - right.fireAt
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return left.order - right.order
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ManualClock implements Clock {
|
|
21
|
+
private time = 0
|
|
22
|
+
private nextId = 1
|
|
23
|
+
private nextOrder = 0
|
|
24
|
+
private readonly timers = new Map<number, ScheduledTimer>()
|
|
25
|
+
|
|
26
|
+
public now(): number {
|
|
27
|
+
return this.time
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public setTime(time: number): void {
|
|
31
|
+
const targetTime = Math.floor(time)
|
|
32
|
+
|
|
33
|
+
if (targetTime >= this.time) {
|
|
34
|
+
this.advance(targetTime - this.time)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.time = targetTime
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public setTimeout(fn: () => void, delayMs: number): TimerHandle {
|
|
42
|
+
return this.schedule(fn, delayMs, false)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public clearTimeout(handle: TimerHandle): void {
|
|
46
|
+
this.timers.delete(Number(handle))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public setInterval(fn: () => void, delayMs: number): TimerHandle {
|
|
50
|
+
return this.schedule(fn, delayMs, true)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public clearInterval(handle: TimerHandle): void {
|
|
54
|
+
this.clearTimeout(handle)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public advance(delayMs: number): void {
|
|
58
|
+
const targetTime = this.time + Math.max(0, Math.floor(delayMs))
|
|
59
|
+
|
|
60
|
+
while (true) {
|
|
61
|
+
const nextTimer = this.peekNextTimer()
|
|
62
|
+
if (!nextTimer || nextTimer.fireAt > targetTime) {
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.timers.delete(nextTimer.id)
|
|
67
|
+
this.time = nextTimer.fireAt
|
|
68
|
+
nextTimer.fn()
|
|
69
|
+
|
|
70
|
+
if (nextTimer.repeat && !this.timers.has(nextTimer.id)) {
|
|
71
|
+
this.timers.set(nextTimer.id, {
|
|
72
|
+
...nextTimer,
|
|
73
|
+
fireAt: this.time + nextTimer.delayMs,
|
|
74
|
+
order: this.nextOrder++,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.time = targetTime
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public runAll(): void {
|
|
83
|
+
while (true) {
|
|
84
|
+
const nextTimer = this.peekNextTimer()
|
|
85
|
+
if (!nextTimer) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.advance(nextTimer.fireAt - this.time)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private schedule(fn: () => void, delayMs: number, repeat: boolean): number {
|
|
94
|
+
const id = this.nextId++
|
|
95
|
+
const normalizedDelay = Math.max(0, Math.floor(delayMs))
|
|
96
|
+
this.timers.set(id, {
|
|
97
|
+
id,
|
|
98
|
+
fireAt: this.time + normalizedDelay,
|
|
99
|
+
order: this.nextOrder++,
|
|
100
|
+
delayMs: normalizedDelay,
|
|
101
|
+
repeat,
|
|
102
|
+
fn,
|
|
103
|
+
})
|
|
104
|
+
return id
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private peekNextTimer(): ScheduledTimer | null {
|
|
108
|
+
let nextTimer: ScheduledTimer | null = null
|
|
109
|
+
for (const timer of this.timers.values()) {
|
|
110
|
+
if (!nextTimer || compareTimers(timer, nextTimer) < 0) {
|
|
111
|
+
nextTimer = timer
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return nextTimer
|
|
116
|
+
}
|
|
117
|
+
}
|