@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,677 @@
|
|
|
1
|
+
//! Output transport backends for `CliRenderer`.
|
|
2
|
+
//!
|
|
3
|
+
//! The renderer's render path writes ANSI bytes into an abstract writer
|
|
4
|
+
//! supplied by an `OutputBackend`. Two variants are available:
|
|
5
|
+
//!
|
|
6
|
+
//! - `BufferedBackend`: writes into per-renderer A/B frame buffers, then
|
|
7
|
+
//! flushes committed bytes to an injected `BufferedOutput`.
|
|
8
|
+
//!
|
|
9
|
+
//! - `FeedBackend`: writes into a `NativeSpanFeed.Stream` whose chunks are
|
|
10
|
+
//! consumed from TypeScript and piped to a user-supplied Writable
|
|
11
|
+
//! (typically an SSH channel).
|
|
12
|
+
//!
|
|
13
|
+
//! The backend is a tagged union. `CliRenderer.render` performs exactly one
|
|
14
|
+
//! `switch` on the backend using `inline else` to pick the right variant's
|
|
15
|
+
//! writer type at compile time — keeping the render path generic over the
|
|
16
|
+
//! writer without scattering backend-specific switches across the codebase.
|
|
17
|
+
|
|
18
|
+
const std = @import("std");
|
|
19
|
+
const Allocator = std.mem.Allocator;
|
|
20
|
+
const NativeSpanFeed = @import("native-span-feed.zig");
|
|
21
|
+
|
|
22
|
+
pub const OUTPUT_BUFFER_SIZE = 1024 * 1024 * 2; // 2 MiB, double-buffered per BufferedBackend for thread handoff
|
|
23
|
+
|
|
24
|
+
pub const WriteStatus = enum(u8) {
|
|
25
|
+
ok = 0,
|
|
26
|
+
skipped = 1,
|
|
27
|
+
failed = 2,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
pub const BufferedWriteFn = *const fn (ctx: *anyopaque, data: []const u8) void;
|
|
31
|
+
|
|
32
|
+
pub const BufferedOutput = struct {
|
|
33
|
+
ctx: *anyopaque,
|
|
34
|
+
write_fn: BufferedWriteFn,
|
|
35
|
+
thread_safe: bool = false,
|
|
36
|
+
|
|
37
|
+
pub fn write(self: BufferedOutput, data: []const u8) void {
|
|
38
|
+
self.write_fn(self.ctx, data);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
pub const StdoutOutput = struct {
|
|
43
|
+
stdoutBuffer: [4096]u8 = undefined,
|
|
44
|
+
|
|
45
|
+
pub fn bufferedOutput(self: *StdoutOutput) BufferedOutput {
|
|
46
|
+
return .{
|
|
47
|
+
.ctx = self,
|
|
48
|
+
.write_fn = write,
|
|
49
|
+
.thread_safe = true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn write(ctx: *anyopaque, data: []const u8) void {
|
|
54
|
+
if (data.len == 0) return;
|
|
55
|
+
|
|
56
|
+
const self: *StdoutOutput = @ptrCast(@alignCast(ctx));
|
|
57
|
+
var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer);
|
|
58
|
+
const w = &stdoutWriter.interface;
|
|
59
|
+
w.writeAll(data) catch {};
|
|
60
|
+
w.flush() catch {};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
pub const MemoryOutput = struct {
|
|
65
|
+
allocator: Allocator,
|
|
66
|
+
bytes: std.ArrayListUnmanaged(u8) = .{},
|
|
67
|
+
|
|
68
|
+
pub fn init(allocator: Allocator) MemoryOutput {
|
|
69
|
+
return .{ .allocator = allocator };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn deinit(self: *MemoryOutput) void {
|
|
73
|
+
self.bytes.deinit(self.allocator);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn bufferedOutput(self: *MemoryOutput) BufferedOutput {
|
|
77
|
+
return .{ .ctx = self, .write_fn = write };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn write(ctx: *anyopaque, data: []const u8) void {
|
|
81
|
+
const self: *MemoryOutput = @ptrCast(@alignCast(ctx));
|
|
82
|
+
self.bytes.appendSlice(self.allocator, data) catch {};
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/// Tagged union dispatching to BufferedBackend or FeedBackend.
|
|
87
|
+
pub const OutputBackend = union(enum) {
|
|
88
|
+
buffered: BufferedBackend,
|
|
89
|
+
feed: FeedBackend,
|
|
90
|
+
|
|
91
|
+
/// Synchronously emit a pre-built byte sequence (setup/shutdown/query).
|
|
92
|
+
pub fn writeOut(self: *OutputBackend, data: []const u8) void {
|
|
93
|
+
switch (self.*) {
|
|
94
|
+
inline else => |*b| b.writeOut(data),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Synchronously emit multiple pre-built byte sequences.
|
|
99
|
+
pub fn writeOutMultiple(self: *OutputBackend, data_slices: []const []const u8) void {
|
|
100
|
+
switch (self.*) {
|
|
101
|
+
inline else => |*b| b.writeOutMultiple(data_slices),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Prepare the backend for a new frame. Feed backends can return skipped
|
|
106
|
+
/// when the durable queue is over its high-water mark or still owns pending
|
|
107
|
+
/// bytes from an earlier frame.
|
|
108
|
+
pub fn prepareFrame(self: *OutputBackend) WriteStatus {
|
|
109
|
+
switch (self.*) {
|
|
110
|
+
inline else => |*b| return b.prepareFrame(),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Non-mutating high-water check. Rendering uses `prepareFrame()` so pending
|
|
115
|
+
/// bytes from earlier writes can be committed before deciding to skip.
|
|
116
|
+
pub fn shouldSkipFrame(self: *OutputBackend) bool {
|
|
117
|
+
switch (self.*) {
|
|
118
|
+
inline else => |*b| return b.shouldSkipFrame(),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pub fn supportsThreading(self: *OutputBackend) bool {
|
|
123
|
+
switch (self.*) {
|
|
124
|
+
inline else => |*b| return b.supportsThreading(),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pub fn setUseThread(self: *OutputBackend, use_thread: bool) void {
|
|
129
|
+
switch (self.*) {
|
|
130
|
+
inline else => |*b| b.setUseThread(use_thread),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub fn isUseThread(self: *OutputBackend) bool {
|
|
135
|
+
switch (self.*) {
|
|
136
|
+
inline else => |*b| return b.isUseThread(),
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Microseconds spent on the last write (populated after endFrame).
|
|
141
|
+
pub fn getLastWriteTimeUs(self: *OutputBackend) ?f64 {
|
|
142
|
+
switch (self.*) {
|
|
143
|
+
inline else => |*b| return b.lastWriteTimeUs,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Write a backend-specific debug dump into `out`. Called from the
|
|
148
|
+
/// `dumpOutputBuffer` helper on `CliRenderer`; keeps backend-specific
|
|
149
|
+
/// formatting internal so the renderer never switches on the tag.
|
|
150
|
+
pub fn dumpTo(self: *OutputBackend, out: anytype) void {
|
|
151
|
+
switch (self.*) {
|
|
152
|
+
inline else => |*b| b.dumpTo(out),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pub fn deinit(self: *OutputBackend) void {
|
|
157
|
+
switch (self.*) {
|
|
158
|
+
inline else => |*b| b.deinit(),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/// Backend that stages frame bytes in per-renderer buffers and flushes them to
|
|
164
|
+
/// an injected byte output when a frame is committed.
|
|
165
|
+
///
|
|
166
|
+
/// Owns the double buffers and optional render-thread state so each renderer
|
|
167
|
+
/// has isolated output storage.
|
|
168
|
+
pub const BufferedBackend = struct {
|
|
169
|
+
const BufferId = enum { A, B };
|
|
170
|
+
|
|
171
|
+
/// Number of consecutive frames that fit in the default buffer before an
|
|
172
|
+
/// oversized buffer is shrunk back to OUTPUT_BUFFER_SIZE. Keeps a workload
|
|
173
|
+
/// of sustained large frames from realloc-churning while still returning
|
|
174
|
+
/// spike memory once frames are consistently small again.
|
|
175
|
+
const SHRINK_AFTER_SMALL_FRAMES = 64;
|
|
176
|
+
|
|
177
|
+
allocator: Allocator,
|
|
178
|
+
output: BufferedOutput,
|
|
179
|
+
ownedStdoutOutput: ?*StdoutOutput = null,
|
|
180
|
+
ownedMemoryOutput: ?*MemoryOutput = null,
|
|
181
|
+
|
|
182
|
+
outputA: []u8,
|
|
183
|
+
outputB: []u8,
|
|
184
|
+
outputLenA: usize = 0,
|
|
185
|
+
outputLenB: usize = 0,
|
|
186
|
+
activeBuffer: BufferId = .A,
|
|
187
|
+
lastCommittedBuffer: BufferId = .A,
|
|
188
|
+
hasCommittedFrame: bool = false,
|
|
189
|
+
/// Set when frame bytes were dropped because a buffer could not grow.
|
|
190
|
+
/// endFrame consumes it and reports the frame as failed so the renderer
|
|
191
|
+
/// forces a full repaint instead of trusting the committed cell diff.
|
|
192
|
+
frameWriteFailed: bool = false,
|
|
193
|
+
smallFrameStreak: u32 = 0,
|
|
194
|
+
|
|
195
|
+
useThread: bool = false,
|
|
196
|
+
renderThread: ?std.Thread = null,
|
|
197
|
+
renderMutex: std.Thread.Mutex = .{},
|
|
198
|
+
renderCondition: std.Thread.Condition = .{},
|
|
199
|
+
renderRequested: bool = false,
|
|
200
|
+
shouldTerminate: bool = false,
|
|
201
|
+
renderInProgress: bool = false,
|
|
202
|
+
|
|
203
|
+
// Handoff buffer for the render thread
|
|
204
|
+
currentOutputBuffer: []u8 = &[_]u8{},
|
|
205
|
+
currentOutputLen: usize = 0,
|
|
206
|
+
|
|
207
|
+
lastWriteTimeUs: ?f64 = null,
|
|
208
|
+
|
|
209
|
+
pub fn create(allocator: Allocator, output: BufferedOutput) !BufferedBackend {
|
|
210
|
+
const a_buf = try allocator.alloc(u8, OUTPUT_BUFFER_SIZE);
|
|
211
|
+
errdefer allocator.free(a_buf);
|
|
212
|
+
const b_buf = try allocator.alloc(u8, OUTPUT_BUFFER_SIZE);
|
|
213
|
+
errdefer allocator.free(b_buf);
|
|
214
|
+
|
|
215
|
+
return BufferedBackend{
|
|
216
|
+
.allocator = allocator,
|
|
217
|
+
.output = output,
|
|
218
|
+
.outputA = a_buf,
|
|
219
|
+
.outputB = b_buf,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
pub fn createStdout(allocator: Allocator) !BufferedBackend {
|
|
224
|
+
const stdoutOutput = try allocator.create(StdoutOutput);
|
|
225
|
+
errdefer allocator.destroy(stdoutOutput);
|
|
226
|
+
stdoutOutput.* = .{};
|
|
227
|
+
|
|
228
|
+
var backend = try BufferedBackend.create(allocator, stdoutOutput.bufferedOutput());
|
|
229
|
+
backend.ownedStdoutOutput = stdoutOutput;
|
|
230
|
+
return backend;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
pub fn createMemory(allocator: Allocator) !BufferedBackend {
|
|
234
|
+
const memoryOutput = try allocator.create(MemoryOutput);
|
|
235
|
+
errdefer allocator.destroy(memoryOutput);
|
|
236
|
+
memoryOutput.* = MemoryOutput.init(allocator);
|
|
237
|
+
|
|
238
|
+
var backend = try BufferedBackend.create(allocator, memoryOutput.bufferedOutput());
|
|
239
|
+
backend.ownedMemoryOutput = memoryOutput;
|
|
240
|
+
return backend;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// Frees with the allocator captured at create time. The buffers may have
|
|
244
|
+
/// been realloc-grown by frame writes, so freeing them with any other
|
|
245
|
+
/// allocator would be undefined behavior.
|
|
246
|
+
pub fn deinit(self: *BufferedBackend) void {
|
|
247
|
+
if (self.renderThread) |thread| {
|
|
248
|
+
self.renderMutex.lock();
|
|
249
|
+
while (self.renderInProgress) {
|
|
250
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
251
|
+
}
|
|
252
|
+
self.shouldTerminate = true;
|
|
253
|
+
// Do NOT set renderRequested — the thread should wake, see
|
|
254
|
+
// shouldTerminate, and exit without a final spurious write of
|
|
255
|
+
// the stale last-frame buffer. Previously setting renderRequested
|
|
256
|
+
// here caused a stale frame to be emitted AFTER the shutdown
|
|
257
|
+
// ANSI sequence had already restored the terminal.
|
|
258
|
+
self.renderCondition.signal();
|
|
259
|
+
self.renderMutex.unlock();
|
|
260
|
+
thread.join();
|
|
261
|
+
self.renderThread = null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
self.allocator.free(self.outputA);
|
|
265
|
+
self.allocator.free(self.outputB);
|
|
266
|
+
if (self.ownedStdoutOutput) |stdoutOutput| {
|
|
267
|
+
self.allocator.destroy(stdoutOutput);
|
|
268
|
+
self.ownedStdoutOutput = null;
|
|
269
|
+
}
|
|
270
|
+
if (self.ownedMemoryOutput) |memoryOutput| {
|
|
271
|
+
memoryOutput.deinit();
|
|
272
|
+
self.allocator.destroy(memoryOutput);
|
|
273
|
+
self.ownedMemoryOutput = null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
pub fn shouldSkipFrame(_: *BufferedBackend) bool {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
pub fn prepareFrame(self: *BufferedBackend) WriteStatus {
|
|
282
|
+
if (!self.useThread) return .ok;
|
|
283
|
+
if (!self.renderMutex.tryLock()) return .skipped;
|
|
284
|
+
defer self.renderMutex.unlock();
|
|
285
|
+
if (self.renderInProgress) return .skipped;
|
|
286
|
+
return .ok;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
pub fn supportsThreading(self: *BufferedBackend) bool {
|
|
290
|
+
return self.output.thread_safe;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
pub fn isUseThread(self: *BufferedBackend) bool {
|
|
294
|
+
return self.useThread;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
pub fn setUseThread(self: *BufferedBackend, use_thread: bool) void {
|
|
298
|
+
if (use_thread and !self.supportsThreading()) return;
|
|
299
|
+
if (self.useThread == use_thread) return;
|
|
300
|
+
|
|
301
|
+
if (use_thread) {
|
|
302
|
+
if (self.renderThread == null) {
|
|
303
|
+
self.renderThread = std.Thread.spawn(.{}, renderThreadFn, .{self}) catch |err| {
|
|
304
|
+
std.log.warn("Failed to spawn render thread: {}, falling back to non-threaded mode", .{err});
|
|
305
|
+
self.useThread = false;
|
|
306
|
+
return;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
if (self.renderThread) |thread| {
|
|
311
|
+
self.renderMutex.lock();
|
|
312
|
+
while (self.renderInProgress) {
|
|
313
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
314
|
+
}
|
|
315
|
+
self.shouldTerminate = true;
|
|
316
|
+
// Wake the thread with a terminate-only signal; do not set
|
|
317
|
+
// renderRequested (that would replay the stale buffer).
|
|
318
|
+
self.renderCondition.signal();
|
|
319
|
+
self.renderMutex.unlock();
|
|
320
|
+
|
|
321
|
+
thread.join();
|
|
322
|
+
self.renderThread = null;
|
|
323
|
+
self.shouldTerminate = false;
|
|
324
|
+
// Reset request/progress flags so a future setUseThread(true)
|
|
325
|
+
// does not wake on a stale request.
|
|
326
|
+
self.renderRequested = false;
|
|
327
|
+
self.renderInProgress = false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
self.useThread = use_thread;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Frame-time writer context. A pointer to the backend so writes know
|
|
335
|
+
/// which active buffer to append to.
|
|
336
|
+
pub const WriterCtx = struct {
|
|
337
|
+
backend: *BufferedBackend,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
pub const Writer = std.io.GenericWriter(WriterCtx, error{BufferFull}, bufferWrite);
|
|
341
|
+
|
|
342
|
+
fn bufferWrite(ctx: WriterCtx, data: []const u8) error{BufferFull}!usize {
|
|
343
|
+
const self = ctx.backend;
|
|
344
|
+
const bufferLen = if (self.activeBuffer == .A)
|
|
345
|
+
&self.outputLenA
|
|
346
|
+
else
|
|
347
|
+
&self.outputLenB;
|
|
348
|
+
const required = bufferLen.* + data.len;
|
|
349
|
+
|
|
350
|
+
const buffer = if (self.activeBuffer == .A) &self.outputA else &self.outputB;
|
|
351
|
+
|
|
352
|
+
if (required > buffer.*.len) {
|
|
353
|
+
const capacity = @max(required, buffer.*.len * 2);
|
|
354
|
+
buffer.* = self.allocator.realloc(buffer.*, capacity) catch {
|
|
355
|
+
self.frameWriteFailed = true;
|
|
356
|
+
return error.BufferFull;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@memcpy(buffer.*[bufferLen.*..][0..data.len], data);
|
|
361
|
+
bufferLen.* += data.len;
|
|
362
|
+
return data.len;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// TODO: std.io.GenericWriter is deprecated but the replacement is much more involved.
|
|
366
|
+
// Migrate when the ecosystem stabilizes.
|
|
367
|
+
pub fn writer(self: *BufferedBackend) Writer {
|
|
368
|
+
return .{ .context = .{ .backend = self } };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
pub fn beginFrame(self: *BufferedBackend) void {
|
|
372
|
+
self.frameWriteFailed = false;
|
|
373
|
+
self.maybeShrinkActiveBuffer();
|
|
374
|
+
if (self.activeBuffer == .A) {
|
|
375
|
+
self.outputLenA = 0;
|
|
376
|
+
} else {
|
|
377
|
+
self.outputLenB = 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Give spike memory back once frames have been consistently small again.
|
|
382
|
+
/// Runs at frame start when the active buffer is exclusively owned by the
|
|
383
|
+
/// producer: in threaded mode the render thread only ever reads the buffer
|
|
384
|
+
/// handed off at the previous endFrame, which is the other one.
|
|
385
|
+
fn maybeShrinkActiveBuffer(self: *BufferedBackend) void {
|
|
386
|
+
if (self.smallFrameStreak < SHRINK_AFTER_SMALL_FRAMES) return;
|
|
387
|
+
const buffer = if (self.activeBuffer == .A) &self.outputA else &self.outputB;
|
|
388
|
+
if (buffer.*.len <= OUTPUT_BUFFER_SIZE) return;
|
|
389
|
+
buffer.* = self.allocator.realloc(buffer.*, OUTPUT_BUFFER_SIZE) catch return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
fn updateSmallFrameStreak(self: *BufferedBackend, frame_len: usize) void {
|
|
393
|
+
if (frame_len <= OUTPUT_BUFFER_SIZE) {
|
|
394
|
+
self.smallFrameStreak +|= 1;
|
|
395
|
+
} else {
|
|
396
|
+
self.smallFrameStreak = 0;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
pub fn endFrame(self: *BufferedBackend) WriteStatus {
|
|
401
|
+
const frame_len = if (self.activeBuffer == .A) self.outputLenA else self.outputLenB;
|
|
402
|
+
|
|
403
|
+
if (self.frameWriteFailed) {
|
|
404
|
+
// Frame bytes were dropped mid-frame. Flushing the truncated ANSI
|
|
405
|
+
// stream could leave the terminal inside an escape sequence, so
|
|
406
|
+
// drop the partial frame entirely and report failure; the renderer
|
|
407
|
+
// reacts by forcing a full repaint on the next frame.
|
|
408
|
+
self.frameWriteFailed = false;
|
|
409
|
+
self.smallFrameStreak = 0;
|
|
410
|
+
return .failed;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
self.updateSmallFrameStreak(frame_len);
|
|
414
|
+
|
|
415
|
+
if (self.useThread and frame_len == 0) {
|
|
416
|
+
self.lastCommittedBuffer = self.activeBuffer;
|
|
417
|
+
self.hasCommittedFrame = true;
|
|
418
|
+
return .ok;
|
|
419
|
+
}
|
|
420
|
+
const writeStart = std.time.microTimestamp();
|
|
421
|
+
const committed_buffer = self.activeBuffer;
|
|
422
|
+
|
|
423
|
+
if (self.useThread) {
|
|
424
|
+
self.renderMutex.lock();
|
|
425
|
+
while (self.renderInProgress) {
|
|
426
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Hand off the just-written buffer to the render thread and flip
|
|
430
|
+
// active to the other one for the next frame.
|
|
431
|
+
if (self.activeBuffer == .A) {
|
|
432
|
+
self.activeBuffer = .B;
|
|
433
|
+
self.currentOutputBuffer = self.outputA;
|
|
434
|
+
self.currentOutputLen = self.outputLenA;
|
|
435
|
+
} else {
|
|
436
|
+
self.activeBuffer = .A;
|
|
437
|
+
self.currentOutputBuffer = self.outputB;
|
|
438
|
+
self.currentOutputLen = self.outputLenB;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
self.renderRequested = true;
|
|
442
|
+
self.renderInProgress = true;
|
|
443
|
+
self.renderCondition.signal();
|
|
444
|
+
self.renderMutex.unlock();
|
|
445
|
+
} else {
|
|
446
|
+
const to_write = if (self.activeBuffer == .A)
|
|
447
|
+
self.outputA[0..self.outputLenA]
|
|
448
|
+
else
|
|
449
|
+
self.outputB[0..self.outputLenB];
|
|
450
|
+
self.output.write(to_write);
|
|
451
|
+
self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
self.lastCommittedBuffer = committed_buffer;
|
|
455
|
+
self.hasCommittedFrame = true;
|
|
456
|
+
return .ok;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fn renderThreadFn(self: *BufferedBackend) void {
|
|
460
|
+
while (true) {
|
|
461
|
+
self.renderMutex.lock();
|
|
462
|
+
while (!self.renderRequested and !self.shouldTerminate) {
|
|
463
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Terminate wins: when shouldTerminate is set, exit without
|
|
467
|
+
// writing even if a render was also requested. This keeps
|
|
468
|
+
// shutdown-ANSI the last thing on the wire.
|
|
469
|
+
if (self.shouldTerminate) {
|
|
470
|
+
self.renderMutex.unlock();
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
self.renderRequested = false;
|
|
475
|
+
|
|
476
|
+
const outputData = self.currentOutputBuffer;
|
|
477
|
+
const outputLen = self.currentOutputLen;
|
|
478
|
+
|
|
479
|
+
const writeStart = std.time.microTimestamp();
|
|
480
|
+
|
|
481
|
+
self.output.write(outputData[0..outputLen]);
|
|
482
|
+
|
|
483
|
+
self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart));
|
|
484
|
+
self.renderInProgress = false;
|
|
485
|
+
self.renderCondition.signal();
|
|
486
|
+
self.renderMutex.unlock();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
pub fn writeOut(self: *BufferedBackend, data: []const u8) void {
|
|
491
|
+
if (data.len == 0) return;
|
|
492
|
+
|
|
493
|
+
if (self.useThread) {
|
|
494
|
+
self.renderMutex.lock();
|
|
495
|
+
while (self.renderInProgress) {
|
|
496
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
497
|
+
}
|
|
498
|
+
self.renderMutex.unlock();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
self.output.write(data);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
pub fn writeOutMultiple(self: *BufferedBackend, data_slices: []const []const u8) void {
|
|
505
|
+
if (self.useThread) {
|
|
506
|
+
self.renderMutex.lock();
|
|
507
|
+
while (self.renderInProgress) {
|
|
508
|
+
self.renderCondition.wait(&self.renderMutex);
|
|
509
|
+
}
|
|
510
|
+
self.renderMutex.unlock();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
var totalLen: usize = 0;
|
|
514
|
+
for (data_slices) |slice| {
|
|
515
|
+
totalLen += slice.len;
|
|
516
|
+
}
|
|
517
|
+
if (totalLen == 0) return;
|
|
518
|
+
|
|
519
|
+
for (data_slices) |slice| {
|
|
520
|
+
self.output.write(slice);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// Write a debug dump of the last rendered output into `out`. The
|
|
525
|
+
/// committed-buffer marker is explicit because non-threaded rendering does
|
|
526
|
+
/// not flip the active buffer after each frame.
|
|
527
|
+
pub fn dumpTo(self: *BufferedBackend, out: anytype) void {
|
|
528
|
+
const last = if (self.hasCommittedFrame) blk: {
|
|
529
|
+
const buf = if (self.lastCommittedBuffer == .A) self.outputA else self.outputB;
|
|
530
|
+
const len = if (self.lastCommittedBuffer == .A) self.outputLenA else self.outputLenB;
|
|
531
|
+
break :blk buf[0..len];
|
|
532
|
+
} else &.{};
|
|
533
|
+
|
|
534
|
+
if (last.len > 0) {
|
|
535
|
+
out.writeAll(last) catch return;
|
|
536
|
+
} else {
|
|
537
|
+
out.writeAll("(no output rendered yet)\n") catch return;
|
|
538
|
+
}
|
|
539
|
+
out.writeAll("\n================\n") catch return;
|
|
540
|
+
out.print("Buffer size: {d} bytes\n", .{last.len}) catch return;
|
|
541
|
+
const active_label: []const u8 = if (self.activeBuffer == .A) "A" else "B";
|
|
542
|
+
const committed_label: []const u8 = if (self.lastCommittedBuffer == .A) "A" else "B";
|
|
543
|
+
out.print("Active buffer: {s}\n", .{active_label}) catch return;
|
|
544
|
+
out.print("Last committed buffer: {s}\n", .{committed_label}) catch return;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
/// Backend that writes to a `NativeSpanFeed.Stream`. The feed owns its own
|
|
549
|
+
/// chunk memory; we hold only a non-owning pointer. The TypeScript side is
|
|
550
|
+
/// responsible for allocating and destroying the feed; this backend simply
|
|
551
|
+
/// writes into it and commits on frame boundaries.
|
|
552
|
+
///
|
|
553
|
+
/// Feed writes are in-memory ring-buffer ops with no I/O, so threading adds
|
|
554
|
+
/// synchronization cost without latency-hiding benefit. Backpressure is
|
|
555
|
+
/// exposed through `prepareFrame`: when the span queue is at its high-water
|
|
556
|
+
/// mark, frames are skipped before diffing while already queued bytes remain
|
|
557
|
+
/// durable and drain in order.
|
|
558
|
+
///
|
|
559
|
+
/// Zig tests that want to exercise the feed path should drain the feed directly.
|
|
560
|
+
pub const FeedBackend = struct {
|
|
561
|
+
feed: *NativeSpanFeed.Stream,
|
|
562
|
+
|
|
563
|
+
/// Set when a frame's write to the feed fails. The backend never discards
|
|
564
|
+
/// feed bytes; failures are reported so the renderer can force a later full
|
|
565
|
+
/// repaint after the durable queue drains or accepts pending bytes.
|
|
566
|
+
frameWriteFailed: bool = false,
|
|
567
|
+
|
|
568
|
+
lastWriteTimeUs: ?f64 = null,
|
|
569
|
+
|
|
570
|
+
pub fn create(feed: *NativeSpanFeed.Stream) FeedBackend {
|
|
571
|
+
return FeedBackend{ .feed = feed };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
pub fn deinit(_: *FeedBackend) void {
|
|
575
|
+
// Feed memory is owned by the TypeScript side. Nothing to free here.
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
pub fn shouldSkipFrame(self: *FeedBackend) bool {
|
|
579
|
+
const stats = self.feed.getStats();
|
|
580
|
+
const cap = self.feed.options.span_queue_capacity;
|
|
581
|
+
return cap > 0 and stats.pending_spans >= cap;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
pub fn prepareFrame(self: *FeedBackend) WriteStatus {
|
|
585
|
+
self.frameWriteFailed = false;
|
|
586
|
+
|
|
587
|
+
if (self.feed.hasPendingBytes()) {
|
|
588
|
+
self.feed.commit() catch return .skipped;
|
|
589
|
+
// Pending bytes belonged to an earlier frame/control write. Queue
|
|
590
|
+
// them first and let the caller retry the new frame after drain.
|
|
591
|
+
return .skipped;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (self.shouldSkipFrame()) return .skipped;
|
|
595
|
+
return .ok;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
pub fn supportsThreading(_: *FeedBackend) bool {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
pub fn setUseThread(_: *FeedBackend, _: bool) void {
|
|
603
|
+
// No-op: feed writes don't benefit from threading.
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
pub fn isUseThread(_: *FeedBackend) bool {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
pub const WriterCtx = struct {
|
|
611
|
+
backend: *FeedBackend,
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
pub const Writer = std.io.GenericWriter(WriterCtx, error{BufferFull}, frameWrite);
|
|
615
|
+
|
|
616
|
+
fn frameWrite(ctx: WriterCtx, data: []const u8) error{BufferFull}!usize {
|
|
617
|
+
const self = ctx.backend;
|
|
618
|
+
self.feed.write(data) catch {
|
|
619
|
+
self.frameWriteFailed = true;
|
|
620
|
+
return error.BufferFull;
|
|
621
|
+
};
|
|
622
|
+
return data.len;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
pub fn writer(self: *FeedBackend) Writer {
|
|
626
|
+
return .{ .context = .{ .backend = self } };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
pub fn beginFrame(self: *FeedBackend) void {
|
|
630
|
+
self.frameWriteFailed = false;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
pub fn endFrame(self: *FeedBackend) WriteStatus {
|
|
634
|
+
const writeStart = std.time.microTimestamp();
|
|
635
|
+
var status: WriteStatus = .ok;
|
|
636
|
+
|
|
637
|
+
if (self.frameWriteFailed) {
|
|
638
|
+
if (self.feed.hasPendingBytes()) {
|
|
639
|
+
self.feed.commit() catch {};
|
|
640
|
+
}
|
|
641
|
+
status = .failed;
|
|
642
|
+
} else {
|
|
643
|
+
self.feed.commit() catch {
|
|
644
|
+
status = .failed;
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart));
|
|
649
|
+
return status;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
pub fn writeOut(self: *FeedBackend, data: []const u8) void {
|
|
653
|
+
if (data.len == 0) return;
|
|
654
|
+
self.feed.write(data) catch return;
|
|
655
|
+
self.feed.commit() catch {};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
pub fn writeOutMultiple(self: *FeedBackend, data_slices: []const []const u8) void {
|
|
659
|
+
var totalLen: usize = 0;
|
|
660
|
+
for (data_slices) |slice| totalLen += slice.len;
|
|
661
|
+
if (totalLen == 0) return;
|
|
662
|
+
|
|
663
|
+
var wrote_any = false;
|
|
664
|
+
for (data_slices) |slice| {
|
|
665
|
+
self.feed.write(slice) catch return;
|
|
666
|
+
wrote_any = true;
|
|
667
|
+
}
|
|
668
|
+
if (wrote_any) self.feed.commit() catch {};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/// Write a debug dump placeholder. FeedBackend has no flat previous-frame
|
|
672
|
+
/// slice — callers wanting feed bytes should drain the NativeSpanFeed.
|
|
673
|
+
pub fn dumpTo(_: *FeedBackend, out: anytype) void {
|
|
674
|
+
out.writeAll("(feed backend — drain spans from the NativeSpanFeed for output)\n") catch return;
|
|
675
|
+
out.writeAll("\n================\n") catch return;
|
|
676
|
+
}
|
|
677
|
+
};
|