@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,153 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { resolveRenderLib } from "../zig.js"
|
|
3
|
+
import { StyledChunkStruct, CursorStyleOptionsStruct } from "../zig-structs.js"
|
|
4
|
+
import { RGBA } from "../lib/RGBA.js"
|
|
5
|
+
import { toArrayBuffer, type Pointer } from "../platform/ffi.js"
|
|
6
|
+
|
|
7
|
+
// Borrowed-pointer contract for styled text, styled placeholders, and cursor
|
|
8
|
+
// options: packed struct buffers must reach the FFI symbol as object values so
|
|
9
|
+
// the backend can borrow them for the synchronous call. Passing a pre-resolved
|
|
10
|
+
// address instead reintroduces the Node use-after-free from issue #1212.
|
|
11
|
+
|
|
12
|
+
const lib = resolveRenderLib()
|
|
13
|
+
const symbols = (lib as any).opentui.symbols as Record<string, (...args: any[]) => any>
|
|
14
|
+
|
|
15
|
+
function withStubbedSymbol(name: string, fn: (calls: any[][]) => void): void {
|
|
16
|
+
const calls: any[][] = []
|
|
17
|
+
const original = symbols[name]
|
|
18
|
+
symbols[name] = (...args: any[]) => {
|
|
19
|
+
calls.push(args)
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
fn(calls)
|
|
23
|
+
} finally {
|
|
24
|
+
symbols[name] = original
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function forceGc(): Promise<void> {
|
|
29
|
+
if (typeof Bun !== "undefined") {
|
|
30
|
+
Bun.gc(true)
|
|
31
|
+
}
|
|
32
|
+
;(globalThis as any).gc?.()
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fieldOffset(struct: { layoutByName: Map<string, { offset: number }> }, name: string): number {
|
|
37
|
+
const field = struct.layoutByName.get(name)
|
|
38
|
+
if (!field) {
|
|
39
|
+
throw new Error(`Missing struct field: ${name}`)
|
|
40
|
+
}
|
|
41
|
+
return field.offset
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readPackedColor(packed: ArrayBuffer, offset: number): number[] {
|
|
45
|
+
// 64-bit StyledChunk/CursorStyleOptions layout; matches the supported
|
|
46
|
+
// x64/arm64 native targets.
|
|
47
|
+
const address = new DataView(packed).getBigUint64(offset, true)
|
|
48
|
+
expect(address).not.toBe(0n)
|
|
49
|
+
return [...new Uint16Array(toArrayBuffer(address as unknown as Pointer, 0, 8).slice(0))]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("borrowed pointer call sites", () => {
|
|
53
|
+
test("textBufferSetStyledText passes the packed chunk buffer as an object value", () => {
|
|
54
|
+
withStubbedSymbol("textBufferSetStyledText", (calls) => {
|
|
55
|
+
const chunks = [
|
|
56
|
+
{ text: "hello", fg: RGBA.fromValues(1, 0, 0, 1) },
|
|
57
|
+
{ text: "world", bg: RGBA.fromValues(0, 0, 1, 1) },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
lib.textBufferSetStyledText(0 as any, chunks)
|
|
61
|
+
|
|
62
|
+
expect(calls).toHaveLength(1)
|
|
63
|
+
expect(calls[0]![1]).toBeInstanceOf(ArrayBuffer)
|
|
64
|
+
expect((calls[0]![1] as ArrayBuffer).byteLength).toBe(StyledChunkStruct.size * chunks.length)
|
|
65
|
+
expect(calls[0]![2]).toBe(chunks.length)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("editorViewSetPlaceholderStyledText passes the packed chunk buffer as an object value", () => {
|
|
70
|
+
withStubbedSymbol("editorViewSetPlaceholderStyledText", (calls) => {
|
|
71
|
+
lib.editorViewSetPlaceholderStyledText(0 as any, [{ text: "placeholder", fg: RGBA.fromValues(0, 1, 0, 1) }])
|
|
72
|
+
lib.editorViewSetPlaceholderStyledText(0 as any, [{ text: "" }])
|
|
73
|
+
|
|
74
|
+
expect(calls).toHaveLength(2)
|
|
75
|
+
expect(calls[0]![1]).toBeInstanceOf(ArrayBuffer)
|
|
76
|
+
expect((calls[0]![1] as ArrayBuffer).byteLength).toBe(StyledChunkStruct.size)
|
|
77
|
+
expect(calls[1]![1]).toBeNull()
|
|
78
|
+
expect(calls[1]![2]).toBe(0)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("setCursorStyleOptions passes the packed options buffer as an object value", () => {
|
|
83
|
+
withStubbedSymbol("setCursorStyleOptions", (calls) => {
|
|
84
|
+
lib.setCursorStyleOptions(0 as any, { style: "block", blinking: true, color: RGBA.fromValues(1, 1, 0, 1) })
|
|
85
|
+
|
|
86
|
+
expect(calls).toHaveLength(1)
|
|
87
|
+
expect(calls[0]![1]).toBeInstanceOf(ArrayBuffer)
|
|
88
|
+
expect((calls[0]![1] as ArrayBuffer).byteLength).toBe(CursorStyleOptionsStruct.size)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe("packed color owner retention", () => {
|
|
94
|
+
test("styled chunk fg and bg colors stay readable after GC of transient chunks", async () => {
|
|
95
|
+
const fgOffset = fieldOffset(StyledChunkStruct, "fg")
|
|
96
|
+
const bgOffset = fieldOffset(StyledChunkStruct, "bg")
|
|
97
|
+
|
|
98
|
+
const packTransientChunks = (count: number) => {
|
|
99
|
+
const chunks = []
|
|
100
|
+
const expected = []
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
const fg = RGBA.fromValues((i % 16) / 15, 0, 1, 1)
|
|
103
|
+
const bg = RGBA.fromValues(0, (i % 16) / 15, 0, 1)
|
|
104
|
+
chunks.push({ text: `chunk-${i}`, fg, bg })
|
|
105
|
+
expected.push({ fg: [...fg.buffer], bg: [...bg.buffer] })
|
|
106
|
+
}
|
|
107
|
+
// The chunk objects and their RGBA instances are unreachable after this
|
|
108
|
+
// returns; only the packed buffer may keep the color memory alive.
|
|
109
|
+
return { packed: StyledChunkStruct.packList(chunks), expected }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const count = 16
|
|
113
|
+
const { packed, expected } = packTransientChunks(count)
|
|
114
|
+
|
|
115
|
+
for (let round = 0; round < 20; round++) {
|
|
116
|
+
const churn = []
|
|
117
|
+
for (let i = 0; i < 2048; i++) {
|
|
118
|
+
churn.push(new Uint16Array(4).fill(round))
|
|
119
|
+
}
|
|
120
|
+
await forceGc()
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < count; i++) {
|
|
123
|
+
const base = i * StyledChunkStruct.size
|
|
124
|
+
expect(readPackedColor(packed, base + fgOffset)).toEqual(expected[i]!.fg)
|
|
125
|
+
expect(readPackedColor(packed, base + bgOffset)).toEqual(expected[i]!.bg)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test("cursor style color stays readable after GC of the transient RGBA", async () => {
|
|
131
|
+
const colorOffset = fieldOffset(CursorStyleOptionsStruct, "color")
|
|
132
|
+
|
|
133
|
+
const packTransientColor = () => {
|
|
134
|
+
const color = RGBA.fromValues(0.5, 0.25, 0.75, 1)
|
|
135
|
+
return {
|
|
136
|
+
packed: CursorStyleOptionsStruct.pack({ style: 255, blinking: 255, color, cursor: 255 }),
|
|
137
|
+
expected: [...color.buffer],
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { packed, expected } = packTransientColor()
|
|
142
|
+
|
|
143
|
+
for (let round = 0; round < 20; round++) {
|
|
144
|
+
const churn = []
|
|
145
|
+
for (let i = 0; i < 2048; i++) {
|
|
146
|
+
churn.push(new Uint16Array(4).fill(round))
|
|
147
|
+
}
|
|
148
|
+
await forceGc()
|
|
149
|
+
|
|
150
|
+
expect(readPackedColor(packed, colorOffset)).toEqual(expected)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, afterEach } from "bun:test"
|
|
2
|
+
import { createTestRenderer, MouseButtons, type MockMouse, type TestRenderer } from "../testing.js"
|
|
3
|
+
import { BoxRenderable } from "../renderables/index.js"
|
|
4
|
+
import type { MousePointerStyle } from "../types.js"
|
|
5
|
+
|
|
6
|
+
describe("mouse pointer style", () => {
|
|
7
|
+
let renderer: TestRenderer
|
|
8
|
+
let mockMouse: MockMouse
|
|
9
|
+
let renderOnce: () => Promise<void>
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
;({ renderer, mockMouse, renderOnce } = await createTestRenderer({ width: 40, height: 20 }))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
renderer.destroy()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("setMousePointer sets style", async () => {
|
|
20
|
+
renderer.setMousePointer("pointer")
|
|
21
|
+
expect((renderer as any)._currentMousePointerStyle).toBe("pointer")
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("setMousePointer with 'default' clears style", async () => {
|
|
25
|
+
renderer.setMousePointer("pointer")
|
|
26
|
+
renderer.setMousePointer("default")
|
|
27
|
+
expect((renderer as any)._currentMousePointerStyle).toBe("default")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("setMousePointer supports all style types", async () => {
|
|
31
|
+
const styles: MousePointerStyle[] = ["default", "pointer", "text", "crosshair", "move", "not-allowed"]
|
|
32
|
+
for (const style of styles) {
|
|
33
|
+
renderer.setMousePointer(style)
|
|
34
|
+
expect((renderer as any)._currentMousePointerStyle).toBe(style)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test("onMouseOver callback can set mouse pointer", async () => {
|
|
39
|
+
let pointerSet = false
|
|
40
|
+
const box = new BoxRenderable(renderer, {
|
|
41
|
+
position: "absolute",
|
|
42
|
+
left: 5,
|
|
43
|
+
top: 5,
|
|
44
|
+
width: 10,
|
|
45
|
+
height: 5,
|
|
46
|
+
onMouseOver() {
|
|
47
|
+
this.ctx.setMousePointer("pointer")
|
|
48
|
+
pointerSet = true
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
renderer.root.add(box)
|
|
52
|
+
await renderOnce()
|
|
53
|
+
|
|
54
|
+
await mockMouse.moveTo(10, 7)
|
|
55
|
+
await renderOnce()
|
|
56
|
+
|
|
57
|
+
expect(pointerSet).toBe(true)
|
|
58
|
+
expect((renderer as any)._currentMousePointerStyle).toBe("pointer")
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("onMouseOut callback can reset mouse pointer", async () => {
|
|
62
|
+
let pointerReset = false
|
|
63
|
+
const box = new BoxRenderable(renderer, {
|
|
64
|
+
position: "absolute",
|
|
65
|
+
left: 5,
|
|
66
|
+
top: 5,
|
|
67
|
+
width: 10,
|
|
68
|
+
height: 5,
|
|
69
|
+
onMouseOver() {
|
|
70
|
+
this.ctx.setMousePointer("pointer")
|
|
71
|
+
},
|
|
72
|
+
onMouseOut() {
|
|
73
|
+
this.ctx.setMousePointer("default")
|
|
74
|
+
pointerReset = true
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
renderer.root.add(box)
|
|
78
|
+
await renderOnce()
|
|
79
|
+
|
|
80
|
+
// Move into box
|
|
81
|
+
await mockMouse.moveTo(10, 7)
|
|
82
|
+
await renderOnce()
|
|
83
|
+
expect((renderer as any)._currentMousePointerStyle).toBe("pointer")
|
|
84
|
+
|
|
85
|
+
// Move out of box
|
|
86
|
+
await mockMouse.moveTo(1, 1)
|
|
87
|
+
await renderOnce()
|
|
88
|
+
|
|
89
|
+
expect(pointerReset).toBe(true)
|
|
90
|
+
expect((renderer as any)._currentMousePointerStyle).toBe("default")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("pointer resets on renderer destroy", async () => {
|
|
94
|
+
renderer.setMousePointer("pointer")
|
|
95
|
+
renderer.destroy()
|
|
96
|
+
// After destroy, the reset is called internally - just verify no error
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { BoxRenderable } from "../renderables/Box.js"
|
|
3
|
+
import { TextareaRenderable } from "../renderables/Textarea.js"
|
|
4
|
+
import { TextRenderable } from "../renderables/Text.js"
|
|
5
|
+
import { createTestRenderer, type TestRenderer } from "../testing/test-renderer.js"
|
|
6
|
+
|
|
7
|
+
// Native-backed measurement wires renderables to native state (measure targets,
|
|
8
|
+
// Yoga measure funcs, handles). These tests lock the lifecycle behavior:
|
|
9
|
+
// destroying renderables must detach cleanly while the rest of the tree keeps
|
|
10
|
+
// measuring, including under create/layout/destroy churn.
|
|
11
|
+
|
|
12
|
+
let renderer: TestRenderer
|
|
13
|
+
let renderOnce: () => Promise<void>
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
;({ renderer, renderOnce } = await createTestRenderer({ width: 80, height: 30 }))
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
renderer.destroy()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function maybeCollectGarbage(): void {
|
|
24
|
+
const bun = (globalThis as { Bun?: { gc?: (force?: boolean) => void } }).Bun
|
|
25
|
+
bun?.gc?.(false)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function expectSize(
|
|
29
|
+
renderable: TextRenderable | TextareaRenderable,
|
|
30
|
+
expected: { width: number; height: number },
|
|
31
|
+
): void {
|
|
32
|
+
expect(renderable.width).toBeCloseTo(expected.width, 5)
|
|
33
|
+
expect(renderable.height).toBeCloseTo(expected.height, 5)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("native-backed measurement lifecycle", () => {
|
|
37
|
+
test("destroying a text renderable keeps sibling measurement working", async () => {
|
|
38
|
+
const parent = new BoxRenderable(renderer, { width: 40, flexDirection: "column", alignItems: "flex-start" })
|
|
39
|
+
const first = new TextRenderable(renderer, { content: "AAAAA", wrapMode: "none", alignSelf: "flex-start" })
|
|
40
|
+
const second = new TextRenderable(renderer, { content: "BBBBBBBBBB", wrapMode: "none", alignSelf: "flex-start" })
|
|
41
|
+
parent.add(first)
|
|
42
|
+
parent.add(second)
|
|
43
|
+
renderer.root.add(parent)
|
|
44
|
+
await renderOnce()
|
|
45
|
+
|
|
46
|
+
expectSize(first, { width: 5, height: 1 })
|
|
47
|
+
expectSize(second, { width: 10, height: 1 })
|
|
48
|
+
|
|
49
|
+
first.destroy()
|
|
50
|
+
await renderOnce()
|
|
51
|
+
expectSize(second, { width: 10, height: 1 })
|
|
52
|
+
|
|
53
|
+
second.content = "CCC"
|
|
54
|
+
await renderOnce()
|
|
55
|
+
expectSize(second, { width: 3, height: 1 })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("destroying a textarea keeps sibling measurement working", async () => {
|
|
59
|
+
const parent = new BoxRenderable(renderer, { width: 40, flexDirection: "column", alignItems: "flex-start" })
|
|
60
|
+
const first = new TextareaRenderable(renderer, { initialValue: "AAAAA", wrapMode: "none", alignSelf: "flex-start" })
|
|
61
|
+
const second = new TextareaRenderable(renderer, {
|
|
62
|
+
initialValue: "BBBBBBBBBB",
|
|
63
|
+
wrapMode: "none",
|
|
64
|
+
alignSelf: "flex-start",
|
|
65
|
+
})
|
|
66
|
+
parent.add(first)
|
|
67
|
+
parent.add(second)
|
|
68
|
+
renderer.root.add(parent)
|
|
69
|
+
await renderOnce()
|
|
70
|
+
|
|
71
|
+
expectSize(first, { width: 5, height: 1 })
|
|
72
|
+
expectSize(second, { width: 10, height: 1 })
|
|
73
|
+
|
|
74
|
+
first.destroy()
|
|
75
|
+
await renderOnce()
|
|
76
|
+
expectSize(second, { width: 10, height: 1 })
|
|
77
|
+
|
|
78
|
+
second.setText("CCC")
|
|
79
|
+
await renderOnce()
|
|
80
|
+
expectSize(second, { width: 3, height: 1 })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("survives create/layout/destroy churn of native-backed renderables", async () => {
|
|
84
|
+
for (let round = 0; round < 20; round++) {
|
|
85
|
+
const container = new BoxRenderable(renderer, { flexDirection: "column", alignItems: "flex-start" })
|
|
86
|
+
renderer.root.add(container)
|
|
87
|
+
|
|
88
|
+
const texts: TextRenderable[] = []
|
|
89
|
+
const textareas: TextareaRenderable[] = []
|
|
90
|
+
for (let index = 0; index < 16; index++) {
|
|
91
|
+
const width = 1 + ((round + index) % 20)
|
|
92
|
+
const text = new TextRenderable(renderer, {
|
|
93
|
+
content: "X".repeat(width),
|
|
94
|
+
wrapMode: "none",
|
|
95
|
+
alignSelf: "flex-start",
|
|
96
|
+
})
|
|
97
|
+
texts.push(text)
|
|
98
|
+
container.add(text)
|
|
99
|
+
}
|
|
100
|
+
for (let index = 0; index < 6; index++) {
|
|
101
|
+
const width = 1 + ((round + index) % 15)
|
|
102
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
103
|
+
initialValue: "Y".repeat(width),
|
|
104
|
+
wrapMode: "none",
|
|
105
|
+
alignSelf: "flex-start",
|
|
106
|
+
})
|
|
107
|
+
textareas.push(textarea)
|
|
108
|
+
container.add(textarea)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await renderOnce()
|
|
112
|
+
|
|
113
|
+
for (const [index, text] of texts.entries()) {
|
|
114
|
+
expectSize(text, { width: 1 + ((round + index) % 20), height: 1 })
|
|
115
|
+
}
|
|
116
|
+
for (const [index, textarea] of textareas.entries()) {
|
|
117
|
+
expectSize(textarea, { width: 1 + ((round + index) % 15), height: 1 })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Alternate destroy orders: children-first and recursive subtree destroy.
|
|
121
|
+
if (round % 2 === 0) {
|
|
122
|
+
for (const text of texts) text.destroy()
|
|
123
|
+
for (const textarea of textareas) textarea.destroy()
|
|
124
|
+
container.destroy()
|
|
125
|
+
} else {
|
|
126
|
+
container.destroyRecursively()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (round % 5 === 0) maybeCollectGarbage()
|
|
130
|
+
await renderOnce()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { BoxRenderable } from "../renderables/Box.js"
|
|
3
|
+
import { TextareaRenderable } from "../renderables/Textarea.js"
|
|
4
|
+
import { TextRenderable } from "../renderables/Text.js"
|
|
5
|
+
import { createTestRenderer, type TestRenderer } from "../testing/test-renderer.js"
|
|
6
|
+
|
|
7
|
+
// These tests lock the measurement behavior of the old TypeScript Yoga measure
|
|
8
|
+
// functions at the renderable/Yoga boundary, now that TextRenderable and
|
|
9
|
+
// TextareaRenderable use native-backed measurement. All expectations are
|
|
10
|
+
// hardcoded so the tests stay valid across implementation refactors.
|
|
11
|
+
|
|
12
|
+
const WORD_WRAP_CONTENT = "Hello wonderful world from OpenTUI"
|
|
13
|
+
const PLACEHOLDER_CONTENT = "Placeholder text that is longer than content"
|
|
14
|
+
|
|
15
|
+
let renderer: TestRenderer
|
|
16
|
+
let renderOnce: () => Promise<void>
|
|
17
|
+
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
;({ renderer, renderOnce } = await createTestRenderer({ width: 80, height: 30 }))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
renderer.destroy()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function expectCloseToLayout(actual: number, expected: number): void {
|
|
27
|
+
expect(actual).toBeCloseTo(expected, 5)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function expectLayout(
|
|
31
|
+
renderable: TextRenderable | TextareaRenderable,
|
|
32
|
+
expected: { width: number; height: number },
|
|
33
|
+
): void {
|
|
34
|
+
expectCloseToLayout(renderable.width, expected.width)
|
|
35
|
+
expectCloseToLayout(renderable.height, expected.height)
|
|
36
|
+
|
|
37
|
+
const layout = renderable.getLayoutNode().getComputedLayout()
|
|
38
|
+
expectCloseToLayout(layout.width, expected.width)
|
|
39
|
+
expectCloseToLayout(layout.height, expected.height)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function renderInConstrainedColumn(renderable: TextRenderable | TextareaRenderable): Promise<void> {
|
|
43
|
+
renderer.root.add(renderable)
|
|
44
|
+
await renderOnce()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function renderInParent(
|
|
48
|
+
renderable: TextRenderable | TextareaRenderable,
|
|
49
|
+
options: { width: number; height?: number },
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const parent = new BoxRenderable(renderer, {
|
|
52
|
+
width: options.width,
|
|
53
|
+
...(options.height === undefined ? {} : { height: options.height }),
|
|
54
|
+
alignItems: "flex-start",
|
|
55
|
+
})
|
|
56
|
+
parent.add(renderable)
|
|
57
|
+
renderer.root.add(parent)
|
|
58
|
+
await renderOnce()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("native-backed measurement parity", () => {
|
|
62
|
+
describe("TextRenderable", () => {
|
|
63
|
+
test("matches native char-wrap measurement with relative AtMost clamping", async () => {
|
|
64
|
+
const text = new TextRenderable(renderer, {
|
|
65
|
+
content: "ABCDEFGHIJKLMNOPQRST",
|
|
66
|
+
wrapMode: "char",
|
|
67
|
+
alignSelf: "flex-start",
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
await renderInConstrainedColumn(text)
|
|
71
|
+
|
|
72
|
+
expectLayout(text, { width: 20, height: 1 })
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test("matches native word-wrap measurement with relative AtMost clamping", async () => {
|
|
76
|
+
const text = new TextRenderable(renderer, {
|
|
77
|
+
content: WORD_WRAP_CONTENT,
|
|
78
|
+
wrapMode: "word",
|
|
79
|
+
alignSelf: "flex-start",
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await renderInConstrainedColumn(text)
|
|
83
|
+
|
|
84
|
+
expectLayout(text, { width: WORD_WRAP_CONTENT.length, height: 1 })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test("matches native no-wrap measurement", async () => {
|
|
88
|
+
const text = new TextRenderable(renderer, {
|
|
89
|
+
content: "Short\nAVeryLongLineHere\nMedium",
|
|
90
|
+
wrapMode: "none",
|
|
91
|
+
alignSelf: "flex-start",
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await renderInConstrainedColumn(text)
|
|
95
|
+
|
|
96
|
+
expectLayout(text, { width: "AVeryLongLineHere".length, height: 3 })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("clamps relative AtMost measurement to a narrower parent", async () => {
|
|
100
|
+
const text = new TextRenderable(renderer, {
|
|
101
|
+
content: "ABCDEFGHIJKLMNOPQRST",
|
|
102
|
+
wrapMode: "none",
|
|
103
|
+
alignSelf: "flex-start",
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
await renderInParent(text, { width: 10 })
|
|
107
|
+
|
|
108
|
+
expectLayout(text, { width: 10, height: 1 })
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("stretches relative text to the root width by default", async () => {
|
|
112
|
+
const text = new TextRenderable(renderer, {
|
|
113
|
+
content: "Short",
|
|
114
|
+
wrapMode: "char",
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
await renderInConstrainedColumn(text)
|
|
118
|
+
|
|
119
|
+
expectLayout(text, { width: 80, height: 1 })
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("preserves minimum Yoga measurement size for empty content", async () => {
|
|
123
|
+
const text = new TextRenderable(renderer, {
|
|
124
|
+
content: "",
|
|
125
|
+
wrapMode: "char",
|
|
126
|
+
alignSelf: "flex-start",
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await renderInConstrainedColumn(text)
|
|
130
|
+
|
|
131
|
+
expectLayout(text, { width: 1, height: 1 })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("does not apply relative AtMost clamping for absolute-positioned text", async () => {
|
|
135
|
+
const text = new TextRenderable(renderer, {
|
|
136
|
+
content: "ABCDEFGHIJKLMNOPQRST",
|
|
137
|
+
wrapMode: "none",
|
|
138
|
+
position: "absolute",
|
|
139
|
+
left: 0,
|
|
140
|
+
top: 0,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
await renderInConstrainedColumn(text)
|
|
144
|
+
|
|
145
|
+
expectLayout(text, { width: 20, height: 1 })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("recomputes measurement after content changes", async () => {
|
|
149
|
+
const text = new TextRenderable(renderer, {
|
|
150
|
+
content: "Short",
|
|
151
|
+
wrapMode: "char",
|
|
152
|
+
alignSelf: "flex-start",
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
await renderInConstrainedColumn(text)
|
|
156
|
+
expectLayout(text, { width: 5, height: 1 })
|
|
157
|
+
|
|
158
|
+
text.content = "ABCDEFGHIJKLMNOPQRST"
|
|
159
|
+
await renderOnce()
|
|
160
|
+
|
|
161
|
+
expectLayout(text, { width: 20, height: 1 })
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("applies and removes AtMost clamping when position changes at runtime", async () => {
|
|
165
|
+
const text = new TextRenderable(renderer, {
|
|
166
|
+
content: "ABCDEFGHIJKLMNOPQRST",
|
|
167
|
+
wrapMode: "none",
|
|
168
|
+
alignSelf: "flex-start",
|
|
169
|
+
left: 0,
|
|
170
|
+
top: 0,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
await renderInParent(text, { width: 10 })
|
|
174
|
+
expectLayout(text, { width: 10, height: 1 })
|
|
175
|
+
|
|
176
|
+
text.position = "absolute"
|
|
177
|
+
await renderOnce()
|
|
178
|
+
expectLayout(text, { width: 20, height: 1 })
|
|
179
|
+
|
|
180
|
+
text.position = "relative"
|
|
181
|
+
await renderOnce()
|
|
182
|
+
expectLayout(text, { width: 10, height: 1 })
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe("TextareaRenderable", () => {
|
|
187
|
+
test("matches native char-wrap editor measurement with relative AtMost clamping", async () => {
|
|
188
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
189
|
+
initialValue: "ABCDEFGHIJKLMNOPQRST",
|
|
190
|
+
wrapMode: "char",
|
|
191
|
+
alignSelf: "flex-start",
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
await renderInConstrainedColumn(textarea)
|
|
195
|
+
|
|
196
|
+
expectLayout(textarea, { width: 20, height: 1 })
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test("matches native word-wrap editor measurement with relative AtMost clamping", async () => {
|
|
200
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
201
|
+
initialValue: WORD_WRAP_CONTENT,
|
|
202
|
+
wrapMode: "word",
|
|
203
|
+
alignSelf: "flex-start",
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await renderInConstrainedColumn(textarea)
|
|
207
|
+
|
|
208
|
+
expectLayout(textarea, { width: WORD_WRAP_CONTENT.length, height: 1 })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test("matches native no-wrap editor measurement", async () => {
|
|
212
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
213
|
+
initialValue: "Short\nAVeryLongLineHere\nMedium",
|
|
214
|
+
wrapMode: "none",
|
|
215
|
+
alignSelf: "flex-start",
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
await renderInConstrainedColumn(textarea)
|
|
219
|
+
|
|
220
|
+
expectLayout(textarea, { width: "AVeryLongLineHere".length, height: 3 })
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test("clamps relative editor AtMost measurement to a narrower parent", async () => {
|
|
224
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
225
|
+
initialValue: "ABCDEFGHIJKLMNOPQRST",
|
|
226
|
+
wrapMode: "none",
|
|
227
|
+
alignSelf: "flex-start",
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
await renderInParent(textarea, { width: 10 })
|
|
231
|
+
|
|
232
|
+
expectLayout(textarea, { width: 10, height: 1 })
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("stretches relative editors to the root width by default", async () => {
|
|
236
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
237
|
+
initialValue: "Short",
|
|
238
|
+
wrapMode: "char",
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
await renderInConstrainedColumn(textarea)
|
|
242
|
+
|
|
243
|
+
expectLayout(textarea, { width: 80, height: 1 })
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test("preserves minimum Yoga measurement size for an empty editor", async () => {
|
|
247
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
248
|
+
initialValue: "",
|
|
249
|
+
wrapMode: "char",
|
|
250
|
+
alignSelf: "flex-start",
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await renderInConstrainedColumn(textarea)
|
|
254
|
+
|
|
255
|
+
expectLayout(textarea, { width: 1, height: 1 })
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test("does not apply relative AtMost clamping for absolute-positioned editors", async () => {
|
|
259
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
260
|
+
initialValue: "ABCDEFGHIJKLMNOPQRST",
|
|
261
|
+
wrapMode: "none",
|
|
262
|
+
position: "absolute",
|
|
263
|
+
left: 0,
|
|
264
|
+
top: 0,
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
await renderInConstrainedColumn(textarea)
|
|
268
|
+
|
|
269
|
+
expectLayout(textarea, { width: 20, height: 1 })
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test("recomputes editor measurement after text changes", async () => {
|
|
273
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
274
|
+
initialValue: "Short",
|
|
275
|
+
wrapMode: "char",
|
|
276
|
+
alignSelf: "flex-start",
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
await renderInConstrainedColumn(textarea)
|
|
280
|
+
expectLayout(textarea, { width: 5, height: 1 })
|
|
281
|
+
|
|
282
|
+
textarea.setText("ABCDEFGHIJKLMNOPQRST")
|
|
283
|
+
await renderOnce()
|
|
284
|
+
|
|
285
|
+
expectLayout(textarea, { width: 20, height: 1 })
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test("captures current placeholder measurement behavior", async () => {
|
|
289
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
290
|
+
initialValue: "",
|
|
291
|
+
placeholder: PLACEHOLDER_CONTENT,
|
|
292
|
+
wrapMode: "char",
|
|
293
|
+
alignSelf: "flex-start",
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await renderInConstrainedColumn(textarea)
|
|
297
|
+
|
|
298
|
+
expectLayout(textarea, { width: PLACEHOLDER_CONTENT.length, height: 1 })
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test("applies and removes AtMost clamping when position changes at runtime", async () => {
|
|
302
|
+
const textarea = new TextareaRenderable(renderer, {
|
|
303
|
+
initialValue: "ABCDEFGHIJKLMNOPQRST",
|
|
304
|
+
wrapMode: "none",
|
|
305
|
+
alignSelf: "flex-start",
|
|
306
|
+
left: 0,
|
|
307
|
+
top: 0,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
await renderInParent(textarea, { width: 10 })
|
|
311
|
+
expectLayout(textarea, { width: 10, height: 1 })
|
|
312
|
+
|
|
313
|
+
textarea.position = "absolute"
|
|
314
|
+
await renderOnce()
|
|
315
|
+
expectLayout(textarea, { width: 20, height: 1 })
|
|
316
|
+
|
|
317
|
+
textarea.position = "relative"
|
|
318
|
+
await renderOnce()
|
|
319
|
+
expectLayout(textarea, { width: 10, height: 1 })
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
})
|