@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,41 @@
|
|
|
1
|
+
// OSC 52 clipboard support for terminal applications.
|
|
2
|
+
// Delegates to native Zig implementation for ANSI sequence generation.
|
|
3
|
+
|
|
4
|
+
import type { RendererHandle, RenderLib } from "../zig.js"
|
|
5
|
+
|
|
6
|
+
export enum ClipboardTarget {
|
|
7
|
+
Clipboard = 0,
|
|
8
|
+
Primary = 1,
|
|
9
|
+
Select = 2,
|
|
10
|
+
Secondary = 3,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Clipboard {
|
|
14
|
+
private lib: RenderLib
|
|
15
|
+
private rendererPtr: RendererHandle
|
|
16
|
+
|
|
17
|
+
constructor(lib: RenderLib, rendererPtr: RendererHandle) {
|
|
18
|
+
this.lib = lib
|
|
19
|
+
this.rendererPtr = rendererPtr
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public copyToClipboardOSC52(text: string, target: ClipboardTarget = ClipboardTarget.Clipboard): boolean {
|
|
23
|
+
if (!this.isOsc52Supported()) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
const textUtf8 = this.lib.encoder.encode(text)
|
|
27
|
+
return this.lib.copyToClipboardOSC52(this.rendererPtr, target, textUtf8)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public clearClipboardOSC52(target: ClipboardTarget = ClipboardTarget.Clipboard): boolean {
|
|
31
|
+
if (!this.isOsc52Supported()) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
return this.lib.clearClipboardOSC52(this.rendererPtr, target)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public isOsc52Supported(): boolean {
|
|
38
|
+
const caps = this.lib.getTerminalCapabilities(this.rendererPtr)
|
|
39
|
+
return caps?.osc52_support !== "unsupported"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/lib/clock.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type TimerHandle = ReturnType<typeof globalThis.setTimeout> | number
|
|
2
|
+
|
|
3
|
+
export interface Clock {
|
|
4
|
+
now(): number
|
|
5
|
+
setTimeout(fn: () => void, delayMs: number): TimerHandle
|
|
6
|
+
clearTimeout(handle: TimerHandle): void
|
|
7
|
+
setInterval(fn: () => void, delayMs: number): TimerHandle
|
|
8
|
+
clearInterval(handle: TimerHandle): void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SystemClock implements Clock {
|
|
12
|
+
public now(): number {
|
|
13
|
+
if (!globalThis.performance || typeof globalThis.performance.now !== "function") {
|
|
14
|
+
throw new Error("SystemClock requires globalThis.performance.now()")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return globalThis.performance.now()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public setTimeout(fn: () => void, delayMs: number): TimerHandle {
|
|
21
|
+
return globalThis.setTimeout(fn, delayMs)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public clearTimeout(handle: TimerHandle): void {
|
|
25
|
+
globalThis.clearTimeout(handle as ReturnType<typeof globalThis.setTimeout>)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public setInterval(fn: () => void, delayMs: number): TimerHandle {
|
|
29
|
+
return globalThis.setInterval(fn, delayMs)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public clearInterval(handle: TimerHandle): void {
|
|
33
|
+
globalThis.clearInterval(handle as ReturnType<typeof globalThis.setTimeout>)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { test, expect } from "bun:test"
|
|
2
|
+
import { DataPathsManager } from "./data-paths.js"
|
|
3
|
+
|
|
4
|
+
test("DataPathsManager validates appName", () => {
|
|
5
|
+
const manager = new DataPathsManager()
|
|
6
|
+
|
|
7
|
+
// Valid names should work
|
|
8
|
+
expect(() => {
|
|
9
|
+
manager.appName = "myapp"
|
|
10
|
+
}).not.toThrow()
|
|
11
|
+
|
|
12
|
+
expect(() => {
|
|
13
|
+
manager.appName = "my-app"
|
|
14
|
+
}).not.toThrow()
|
|
15
|
+
|
|
16
|
+
expect(() => {
|
|
17
|
+
manager.appName = "my_app"
|
|
18
|
+
}).not.toThrow()
|
|
19
|
+
|
|
20
|
+
expect(() => {
|
|
21
|
+
manager.appName = "MyApp123"
|
|
22
|
+
}).not.toThrow()
|
|
23
|
+
|
|
24
|
+
// Invalid names should throw
|
|
25
|
+
expect(() => {
|
|
26
|
+
manager.appName = ""
|
|
27
|
+
}).toThrow("Invalid app name")
|
|
28
|
+
|
|
29
|
+
expect(() => {
|
|
30
|
+
manager.appName = " "
|
|
31
|
+
}).toThrow("Invalid app name")
|
|
32
|
+
|
|
33
|
+
expect(() => {
|
|
34
|
+
manager.appName = "app/name"
|
|
35
|
+
}).toThrow("Invalid app name")
|
|
36
|
+
|
|
37
|
+
expect(() => {
|
|
38
|
+
manager.appName = "app\\name"
|
|
39
|
+
}).toThrow("Invalid app name")
|
|
40
|
+
|
|
41
|
+
expect(() => {
|
|
42
|
+
manager.appName = "app<name"
|
|
43
|
+
}).toThrow("Invalid app name")
|
|
44
|
+
|
|
45
|
+
expect(() => {
|
|
46
|
+
manager.appName = "app>name"
|
|
47
|
+
}).toThrow("Invalid app name")
|
|
48
|
+
|
|
49
|
+
expect(() => {
|
|
50
|
+
manager.appName = 'app"name'
|
|
51
|
+
}).toThrow("Invalid app name")
|
|
52
|
+
|
|
53
|
+
expect(() => {
|
|
54
|
+
manager.appName = "app|name"
|
|
55
|
+
}).toThrow("Invalid app name")
|
|
56
|
+
|
|
57
|
+
expect(() => {
|
|
58
|
+
manager.appName = "app?name"
|
|
59
|
+
}).toThrow("Invalid app name")
|
|
60
|
+
|
|
61
|
+
expect(() => {
|
|
62
|
+
manager.appName = "app*name"
|
|
63
|
+
}).toThrow("Invalid app name")
|
|
64
|
+
|
|
65
|
+
expect(() => {
|
|
66
|
+
manager.appName = "CON"
|
|
67
|
+
}).toThrow("Invalid app name")
|
|
68
|
+
|
|
69
|
+
expect(() => {
|
|
70
|
+
manager.appName = "PRN"
|
|
71
|
+
}).toThrow("Invalid app name")
|
|
72
|
+
|
|
73
|
+
expect(() => {
|
|
74
|
+
manager.appName = "app."
|
|
75
|
+
}).toThrow("Invalid app name")
|
|
76
|
+
|
|
77
|
+
expect(() => {
|
|
78
|
+
manager.appName = "app "
|
|
79
|
+
}).toThrow("Invalid app name")
|
|
80
|
+
|
|
81
|
+
expect(() => {
|
|
82
|
+
manager.appName = "."
|
|
83
|
+
}).toThrow("Invalid app name")
|
|
84
|
+
|
|
85
|
+
expect(() => {
|
|
86
|
+
manager.appName = ".."
|
|
87
|
+
}).toThrow("Invalid app name")
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("DataPathsManager constructor uses valid default appName", () => {
|
|
91
|
+
// Should not throw when creating a new instance
|
|
92
|
+
expect(() => {
|
|
93
|
+
new DataPathsManager()
|
|
94
|
+
}).not.toThrow()
|
|
95
|
+
|
|
96
|
+
const manager = new DataPathsManager()
|
|
97
|
+
expect(manager.appName).toBe("opentui")
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("DataPathsManager emits paths:changed event when appName changes", async () => {
|
|
101
|
+
const manager = new DataPathsManager()
|
|
102
|
+
let eventFired = false
|
|
103
|
+
let eventPaths: any = null
|
|
104
|
+
|
|
105
|
+
manager.on("paths:changed", (paths) => {
|
|
106
|
+
eventFired = true
|
|
107
|
+
eventPaths = paths
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const originalAppName = manager.appName
|
|
111
|
+
manager.appName = "test-app-event"
|
|
112
|
+
|
|
113
|
+
expect(eventFired).toBe(true)
|
|
114
|
+
expect(eventPaths).toBeDefined()
|
|
115
|
+
expect(eventPaths.globalDataPath).toContain("test-app-event")
|
|
116
|
+
expect(eventPaths.globalConfigPath).toContain("test-app-event")
|
|
117
|
+
expect(eventPaths.globalConfigFile).toContain("test-app-event")
|
|
118
|
+
expect(eventPaths.localConfigFile).toContain("test-app-event")
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("DataPathsManager does not emit event when appName is set to same value", () => {
|
|
122
|
+
const manager = new DataPathsManager()
|
|
123
|
+
let eventFired = false
|
|
124
|
+
|
|
125
|
+
manager.on("paths:changed", () => {
|
|
126
|
+
eventFired = true
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Set to the same value
|
|
130
|
+
manager.appName = manager.appName
|
|
131
|
+
|
|
132
|
+
expect(eventFired).toBe(false)
|
|
133
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os from "os"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { EventEmitter } from "events"
|
|
4
|
+
import { singleton } from "./singleton.js"
|
|
5
|
+
import { env, registerEnvVar } from "./env.js"
|
|
6
|
+
import { isValidDirectoryName } from "./validate-dir-name.js"
|
|
7
|
+
|
|
8
|
+
// Register environment variables for XDG directories
|
|
9
|
+
registerEnvVar({
|
|
10
|
+
name: "XDG_CONFIG_HOME",
|
|
11
|
+
description: "Base directory for user-specific configuration files",
|
|
12
|
+
type: "string",
|
|
13
|
+
default: "",
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
registerEnvVar({
|
|
17
|
+
name: "XDG_DATA_HOME",
|
|
18
|
+
description: "Base directory for user-specific data files",
|
|
19
|
+
type: "string",
|
|
20
|
+
default: "",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export interface DataPaths {
|
|
24
|
+
globalConfigPath: string
|
|
25
|
+
globalConfigFile: string
|
|
26
|
+
localConfigFile: string
|
|
27
|
+
globalDataPath: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DataPathsEvents {
|
|
31
|
+
"paths:changed": [paths: DataPaths]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class DataPathsManager extends EventEmitter<DataPathsEvents> {
|
|
35
|
+
private _appName: string
|
|
36
|
+
private _globalConfigPath?: string
|
|
37
|
+
private _globalConfigFile?: string
|
|
38
|
+
private _localConfigFile?: string
|
|
39
|
+
private _globalDataPath?: string
|
|
40
|
+
constructor() {
|
|
41
|
+
super()
|
|
42
|
+
this._appName = "opentui"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get appName(): string {
|
|
46
|
+
return this._appName
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set appName(value: string) {
|
|
50
|
+
if (!isValidDirectoryName(value)) {
|
|
51
|
+
throw new Error(`Invalid app name "${value}": must be a valid directory name`)
|
|
52
|
+
}
|
|
53
|
+
if (this._appName !== value) {
|
|
54
|
+
this._appName = value
|
|
55
|
+
this._globalConfigPath = undefined
|
|
56
|
+
this._globalConfigFile = undefined
|
|
57
|
+
this._localConfigFile = undefined
|
|
58
|
+
this._globalDataPath = undefined
|
|
59
|
+
this.emit("paths:changed", this.toObject())
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get globalConfigPath(): string {
|
|
64
|
+
if (this._globalConfigPath === undefined) {
|
|
65
|
+
const homeDir = os.homedir()
|
|
66
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME
|
|
67
|
+
const baseConfigDir = xdgConfigHome || path.join(homeDir, ".config")
|
|
68
|
+
this._globalConfigPath = path.join(baseConfigDir, this._appName)
|
|
69
|
+
}
|
|
70
|
+
return this._globalConfigPath
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get globalConfigFile(): string {
|
|
74
|
+
if (this._globalConfigFile === undefined) {
|
|
75
|
+
this._globalConfigFile = path.join(this.globalConfigPath, "init.ts")
|
|
76
|
+
}
|
|
77
|
+
return this._globalConfigFile
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get localConfigFile(): string {
|
|
81
|
+
if (this._localConfigFile === undefined) {
|
|
82
|
+
this._localConfigFile = path.join(process.cwd(), `.${this._appName}.ts`)
|
|
83
|
+
}
|
|
84
|
+
return this._localConfigFile
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get globalDataPath(): string {
|
|
88
|
+
if (this._globalDataPath === undefined) {
|
|
89
|
+
const homeDir = os.homedir()
|
|
90
|
+
const xdgDataHome = env.XDG_DATA_HOME
|
|
91
|
+
const baseDataDir = xdgDataHome || path.join(homeDir, ".local/share")
|
|
92
|
+
this._globalDataPath = path.join(baseDataDir, this._appName)
|
|
93
|
+
}
|
|
94
|
+
return this._globalDataPath
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
toObject(): DataPaths {
|
|
98
|
+
return {
|
|
99
|
+
globalConfigPath: this.globalConfigPath,
|
|
100
|
+
globalConfigFile: this.globalConfigFile,
|
|
101
|
+
localConfigFile: this.localConfigFile,
|
|
102
|
+
globalDataPath: this.globalDataPath,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getDataPaths(): DataPathsManager {
|
|
108
|
+
return singleton("data-paths-opentui", () => new DataPathsManager())
|
|
109
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A module-level map to store timeout IDs for all debounced functions
|
|
3
|
+
* Structure: Map<scopeId, Map<debounceId, timerId>>
|
|
4
|
+
*/
|
|
5
|
+
const TIMERS_MAP = new Map<string | number, Map<string | number, ReturnType<typeof setTimeout>>>()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Debounce controller that manages debounce instances for a specific scope
|
|
9
|
+
*/
|
|
10
|
+
export class DebounceController {
|
|
11
|
+
constructor(private scopeId: string | number) {
|
|
12
|
+
// Initialize the scope map if it doesn't exist
|
|
13
|
+
if (!TIMERS_MAP.has(this.scopeId)) {
|
|
14
|
+
TIMERS_MAP.set(this.scopeId, new Map())
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Debounces the provided function with the given ID
|
|
20
|
+
*
|
|
21
|
+
* @param id Unique identifier within this scope
|
|
22
|
+
* @param ms Milliseconds to wait before executing
|
|
23
|
+
* @param fn Function to execute
|
|
24
|
+
*/
|
|
25
|
+
debounce<R>(id: string | number, ms: number, fn: () => Promise<R>): Promise<R> {
|
|
26
|
+
const scopeMap = TIMERS_MAP.get(this.scopeId)!
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
// Clear any existing timeout for this ID
|
|
30
|
+
if (scopeMap.has(id)) {
|
|
31
|
+
clearTimeout(scopeMap.get(id))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Set a new timeout
|
|
35
|
+
const timerId = setTimeout(() => {
|
|
36
|
+
try {
|
|
37
|
+
resolve(fn())
|
|
38
|
+
} catch (error) {
|
|
39
|
+
reject(error)
|
|
40
|
+
}
|
|
41
|
+
scopeMap.delete(id)
|
|
42
|
+
}, ms)
|
|
43
|
+
|
|
44
|
+
// Store the new timeout ID
|
|
45
|
+
scopeMap.set(id, timerId)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Clear a specific debounce timer in this scope
|
|
51
|
+
*
|
|
52
|
+
* @param id The debounce ID to clear
|
|
53
|
+
*/
|
|
54
|
+
clearDebounce(id: string | number): void {
|
|
55
|
+
const scopeMap = TIMERS_MAP.get(this.scopeId)
|
|
56
|
+
if (scopeMap && scopeMap.has(id)) {
|
|
57
|
+
clearTimeout(scopeMap.get(id))
|
|
58
|
+
scopeMap.delete(id)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear all debounce timers in this scope
|
|
64
|
+
*/
|
|
65
|
+
clear(): void {
|
|
66
|
+
const scopeMap = TIMERS_MAP.get(this.scopeId)
|
|
67
|
+
if (scopeMap) {
|
|
68
|
+
scopeMap.forEach((timerId) => clearTimeout(timerId))
|
|
69
|
+
scopeMap.clear()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a new debounce controller for a specific scope
|
|
76
|
+
*
|
|
77
|
+
* @param scopeId Unique identifier for this debounce scope
|
|
78
|
+
* @returns A DebounceController for the specified scope
|
|
79
|
+
*/
|
|
80
|
+
export function createDebounce(scopeId: string | number): DebounceController {
|
|
81
|
+
return new DebounceController(scopeId)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Clears all debounce timers for a specific scope
|
|
86
|
+
*
|
|
87
|
+
* @param scopeId The scope identifier
|
|
88
|
+
*/
|
|
89
|
+
export function clearDebounceScope(scopeId: string | number): void {
|
|
90
|
+
const scopeMap = TIMERS_MAP.get(scopeId)
|
|
91
|
+
if (scopeMap) {
|
|
92
|
+
scopeMap.forEach((timerId) => clearTimeout(timerId))
|
|
93
|
+
scopeMap.clear()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Clears all active debounce timers across all scopes
|
|
99
|
+
*/
|
|
100
|
+
export function clearAllDebounces(): void {
|
|
101
|
+
TIMERS_MAP.forEach((scopeMap) => {
|
|
102
|
+
scopeMap.forEach((timerId) => clearTimeout(timerId))
|
|
103
|
+
scopeMap.clear()
|
|
104
|
+
})
|
|
105
|
+
TIMERS_MAP.clear()
|
|
106
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { test, expect, describe } from "bun:test"
|
|
2
|
+
import { detectLinks } from "./detect-links.js"
|
|
3
|
+
import type { TextChunk } from "../text-buffer.js"
|
|
4
|
+
import type { SimpleHighlight } from "./tree-sitter/types.js"
|
|
5
|
+
import { RGBA } from "./RGBA.js"
|
|
6
|
+
|
|
7
|
+
function chunk(text: string): TextChunk {
|
|
8
|
+
return { __isChunk: true, text, fg: RGBA.fromInts(255, 255, 255, 255), attributes: 0 }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("detectLinks", () => {
|
|
12
|
+
test("should set link on markup.link.url chunks", () => {
|
|
13
|
+
const content = "[Click here](https://example.com)"
|
|
14
|
+
const highlights: SimpleHighlight[] = [
|
|
15
|
+
[0, 1, "markup.link"],
|
|
16
|
+
[1, 11, "markup.link.label"],
|
|
17
|
+
[11, 13, "markup.link"],
|
|
18
|
+
[13, 32, "markup.link.url"],
|
|
19
|
+
[32, 33, "markup.link"],
|
|
20
|
+
]
|
|
21
|
+
const chunks = [chunk("["), chunk("Click here"), chunk("]("), chunk("https://example.com"), chunk(")")]
|
|
22
|
+
|
|
23
|
+
const result = detectLinks(chunks, { content, highlights })
|
|
24
|
+
|
|
25
|
+
expect(result.find((c) => c.text === "https://example.com")!.link).toEqual({ url: "https://example.com" })
|
|
26
|
+
expect(result.find((c) => c.text === "Click here")!.link).toEqual({ url: "https://example.com" })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("should set link on string.special.url chunks", () => {
|
|
30
|
+
const content = "// see https://example.com for details"
|
|
31
|
+
const highlights: SimpleHighlight[] = [
|
|
32
|
+
[0, 38, "comment"],
|
|
33
|
+
[7, 26, "string.special.url"],
|
|
34
|
+
]
|
|
35
|
+
const chunks = [chunk("// see "), chunk("https://example.com"), chunk(" for details")]
|
|
36
|
+
|
|
37
|
+
const result = detectLinks(chunks, { content, highlights })
|
|
38
|
+
|
|
39
|
+
expect(result.find((c) => c.text === "https://example.com")!.link).toEqual({ url: "https://example.com" })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("should not set link on non-URL chunks", () => {
|
|
43
|
+
const content = "const x = 42"
|
|
44
|
+
const highlights: SimpleHighlight[] = [
|
|
45
|
+
[0, 5, "keyword"],
|
|
46
|
+
[6, 7, "variable"],
|
|
47
|
+
[10, 12, "number"],
|
|
48
|
+
]
|
|
49
|
+
const chunks = [chunk("const"), chunk(" "), chunk("x"), chunk(" = "), chunk("42")]
|
|
50
|
+
|
|
51
|
+
const result = detectLinks(chunks, { content, highlights })
|
|
52
|
+
|
|
53
|
+
for (const c of result) {
|
|
54
|
+
expect(c.link).toBeUndefined()
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("should return chunks unchanged when no URL scopes exist", () => {
|
|
59
|
+
const content = "hello world"
|
|
60
|
+
const highlights: SimpleHighlight[] = [[0, 5, "keyword"]]
|
|
61
|
+
const chunks = [chunk("hello"), chunk(" world")]
|
|
62
|
+
|
|
63
|
+
const result = detectLinks(chunks, { content, highlights })
|
|
64
|
+
|
|
65
|
+
expect(result).toBe(chunks)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("should detect links when chunks have concealed text", () => {
|
|
69
|
+
// Original content: [Click here](https://example.com)
|
|
70
|
+
// With concealment, `[` and `]` are concealed to empty strings,
|
|
71
|
+
// and `(` and `)` are concealed to empty strings.
|
|
72
|
+
// This means chunk text lengths don't match original byte offsets.
|
|
73
|
+
const content = "[Click here](https://example.com)"
|
|
74
|
+
const highlights: SimpleHighlight[] = [
|
|
75
|
+
[0, 1, "markup.link"], // [
|
|
76
|
+
[1, 11, "markup.link.label"], // Click here
|
|
77
|
+
[11, 13, "markup.link"], // ](
|
|
78
|
+
[13, 32, "markup.link.url"], // https://example.com
|
|
79
|
+
[32, 33, "markup.link"], // )
|
|
80
|
+
]
|
|
81
|
+
// Simulate concealed chunks: `[` -> "", `](` -> " ", `)` -> ""
|
|
82
|
+
// The URL and label chunks remain unchanged.
|
|
83
|
+
const chunks = [
|
|
84
|
+
chunk(""), // concealed `[`
|
|
85
|
+
chunk("Click here"), // label, unchanged
|
|
86
|
+
chunk(" "), // concealed `](`
|
|
87
|
+
chunk("https://example.com"), // URL, unchanged
|
|
88
|
+
chunk(""), // concealed `)`
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
const result = detectLinks(chunks, { content, highlights })
|
|
92
|
+
|
|
93
|
+
// The URL chunk should still get its link despite concealed offsets
|
|
94
|
+
expect(result.find((c) => c.text === "https://example.com")!.link).toEqual({ url: "https://example.com" })
|
|
95
|
+
// The label chunk should also get the link
|
|
96
|
+
expect(result.find((c) => c.text === "Click here")!.link).toEqual({ url: "https://example.com" })
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { TextChunk } from "../text-buffer.js"
|
|
2
|
+
import type { SimpleHighlight } from "./tree-sitter/types.js"
|
|
3
|
+
|
|
4
|
+
const URL_SCOPES = ["markup.link.url", "string.special.url"]
|
|
5
|
+
|
|
6
|
+
export function detectLinks(
|
|
7
|
+
chunks: TextChunk[],
|
|
8
|
+
context: { content: string; highlights: SimpleHighlight[] },
|
|
9
|
+
): TextChunk[] {
|
|
10
|
+
const content = context.content
|
|
11
|
+
const highlights = context.highlights
|
|
12
|
+
|
|
13
|
+
const ranges: Array<{ start: number; end: number; url: string }> = []
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < highlights.length; i++) {
|
|
16
|
+
const [start, end, group] = highlights[i]
|
|
17
|
+
if (!URL_SCOPES.includes(group)) continue
|
|
18
|
+
|
|
19
|
+
const url = content.slice(start, end)
|
|
20
|
+
ranges.push({ start, end, url })
|
|
21
|
+
|
|
22
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
23
|
+
const [labelStart, labelEnd, prev] = highlights[j]
|
|
24
|
+
if (prev === "markup.link.label") {
|
|
25
|
+
ranges.push({ start: labelStart, end: labelEnd, url })
|
|
26
|
+
break
|
|
27
|
+
}
|
|
28
|
+
if (!prev.startsWith("markup.link")) break
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (ranges.length === 0) return chunks
|
|
33
|
+
|
|
34
|
+
// Use content.indexOf to find each chunk's position in the original content.
|
|
35
|
+
// This handles concealed text correctly because concealed chunks are either
|
|
36
|
+
// empty (length 0, skipped) or single-char replacements (length 1, skipped).
|
|
37
|
+
// Non-concealed chunks with length > 1 are exact substrings of content in order.
|
|
38
|
+
let contentPos = 0
|
|
39
|
+
for (const chunk of chunks) {
|
|
40
|
+
if (chunk.text.length <= 1) continue
|
|
41
|
+
|
|
42
|
+
const idx = content.indexOf(chunk.text, contentPos)
|
|
43
|
+
if (idx < 0) continue
|
|
44
|
+
|
|
45
|
+
for (const range of ranges) {
|
|
46
|
+
if (idx < range.end && idx + chunk.text.length > range.start) {
|
|
47
|
+
chunk.link = { url: range.url }
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
contentPos = idx + chunk.text.length
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return chunks
|
|
56
|
+
}
|