@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,2144 @@
|
|
|
1
|
+
import { Renderable, type RenderableOptions } from "../Renderable.js"
|
|
2
|
+
import { type RenderContext } from "../types.js"
|
|
3
|
+
import { SyntaxStyle, type StyleDefinition } from "../syntax-style.js"
|
|
4
|
+
import type { TextChunk } from "../text-buffer.js"
|
|
5
|
+
import { createTextAttributes } from "../utils.js"
|
|
6
|
+
import type { BorderStyle } from "../lib/border.js"
|
|
7
|
+
import { RGBA, parseColor, type ColorInput } from "../lib/RGBA.js"
|
|
8
|
+
import { Lexer, type MarkedToken, type Token, type Tokens } from "marked"
|
|
9
|
+
import { CodeRenderable, type OnChunksCallback } from "./Code.js"
|
|
10
|
+
import { BoxRenderable } from "./Box.js"
|
|
11
|
+
import { StyledText } from "../lib/styled-text.js"
|
|
12
|
+
import { TextRenderable } from "./Text.js"
|
|
13
|
+
import {
|
|
14
|
+
TextTableRenderable,
|
|
15
|
+
type TextTableCellContent,
|
|
16
|
+
type TextTableColumnFitter,
|
|
17
|
+
type TextTableColumnWidthMode,
|
|
18
|
+
type TextTableContent,
|
|
19
|
+
} from "./TextTable.js"
|
|
20
|
+
import type { TreeSitterClient } from "../lib/tree-sitter/index.js"
|
|
21
|
+
import { infoStringToFiletype } from "../lib/tree-sitter/resolve-ft.js"
|
|
22
|
+
import { parseMarkdownIncremental, type ParseState } from "./markdown-parser.js"
|
|
23
|
+
import type { OptimizedBuffer } from "../buffer.js"
|
|
24
|
+
import { detectLinks } from "../lib/detect-links.js"
|
|
25
|
+
|
|
26
|
+
export type MarkdownTableStyle = "grid" | "columns"
|
|
27
|
+
|
|
28
|
+
export interface MarkdownTableOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Visual style preset for markdown tables.
|
|
31
|
+
* - "grid": boxed table with visible borders.
|
|
32
|
+
* - "columns": borderless columns optimized for separated block output.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to "columns" in `internalBlockMode: "top-level"`, otherwise "grid".
|
|
35
|
+
*/
|
|
36
|
+
style?: MarkdownTableStyle
|
|
37
|
+
/**
|
|
38
|
+
* Strategy for sizing table columns.
|
|
39
|
+
* - "content": columns fit to intrinsic content width.
|
|
40
|
+
* - "full": columns expand to fill available width.
|
|
41
|
+
*/
|
|
42
|
+
widthMode?: TextTableColumnWidthMode
|
|
43
|
+
/**
|
|
44
|
+
* Column fitting method when shrinking constrained tables.
|
|
45
|
+
*/
|
|
46
|
+
columnFitter?: TextTableColumnFitter
|
|
47
|
+
/**
|
|
48
|
+
* Wrapping strategy for table cell content.
|
|
49
|
+
*/
|
|
50
|
+
wrapMode?: "none" | "char" | "word"
|
|
51
|
+
/**
|
|
52
|
+
* Padding applied on all sides of each table cell.
|
|
53
|
+
*/
|
|
54
|
+
cellPadding?: number
|
|
55
|
+
/**
|
|
56
|
+
* Horizontal padding applied on the left and right of each table cell.
|
|
57
|
+
*/
|
|
58
|
+
cellPaddingX?: number
|
|
59
|
+
/**
|
|
60
|
+
* Vertical padding applied above and below each table cell.
|
|
61
|
+
*/
|
|
62
|
+
cellPaddingY?: number
|
|
63
|
+
/**
|
|
64
|
+
* Enables/disables table border rendering.
|
|
65
|
+
*/
|
|
66
|
+
borders?: boolean
|
|
67
|
+
/**
|
|
68
|
+
* Overrides outer border visibility. Defaults to `borders`.
|
|
69
|
+
*/
|
|
70
|
+
outerBorder?: boolean
|
|
71
|
+
/**
|
|
72
|
+
* Border style for markdown tables.
|
|
73
|
+
*/
|
|
74
|
+
borderStyle?: BorderStyle
|
|
75
|
+
/**
|
|
76
|
+
* Border color for markdown tables. Defaults to conceal style color.
|
|
77
|
+
*/
|
|
78
|
+
borderColor?: ColorInput
|
|
79
|
+
/**
|
|
80
|
+
* Enables/disables selection support on markdown tables.
|
|
81
|
+
*/
|
|
82
|
+
selectable?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MarkdownOptions extends RenderableOptions<MarkdownRenderable> {
|
|
86
|
+
content?: string
|
|
87
|
+
syntaxStyle: SyntaxStyle
|
|
88
|
+
fg?: ColorInput
|
|
89
|
+
bg?: ColorInput
|
|
90
|
+
/** Controls concealment for markdown syntax markers in markdown text blocks. */
|
|
91
|
+
conceal?: boolean
|
|
92
|
+
/** Controls concealment inside fenced code blocks rendered by CodeRenderable. */
|
|
93
|
+
concealCode?: boolean
|
|
94
|
+
treeSitterClient?: TreeSitterClient
|
|
95
|
+
/**
|
|
96
|
+
* Enable streaming mode for incremental content updates.
|
|
97
|
+
*
|
|
98
|
+
* Semantics:
|
|
99
|
+
* - The trailing markdown block stays unstable while streaming is enabled.
|
|
100
|
+
* - Tables render all rows produced by the markdown parser (including trailing rows).
|
|
101
|
+
* - Incomplete table rows are normalized by the parser and rendered with empty cells
|
|
102
|
+
* where data is missing.
|
|
103
|
+
*
|
|
104
|
+
* Expectations:
|
|
105
|
+
* - Keep this true while chunks are still being appended.
|
|
106
|
+
* - Set this to false once streaming is complete to finalize trailing token parsing.
|
|
107
|
+
*/
|
|
108
|
+
streaming?: boolean
|
|
109
|
+
/**
|
|
110
|
+
* Options for internally rendered markdown tables.
|
|
111
|
+
*/
|
|
112
|
+
tableOptions?: MarkdownTableOptions
|
|
113
|
+
/**
|
|
114
|
+
* Custom node renderer. Return a Renderable to override default rendering,
|
|
115
|
+
* or undefined/null to use default rendering.
|
|
116
|
+
*/
|
|
117
|
+
renderNode?: (token: Token, context: RenderNodeContext) => Renderable | undefined | null
|
|
118
|
+
/**
|
|
119
|
+
* Internal only.
|
|
120
|
+
* - "coalesced": combine ordinary markdown into larger render blocks.
|
|
121
|
+
* - "top-level": preserve top-level markdown blocks as separate render blocks.
|
|
122
|
+
*/
|
|
123
|
+
internalBlockMode?: "coalesced" | "top-level"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface RenderNodeContext {
|
|
127
|
+
syntaxStyle: SyntaxStyle
|
|
128
|
+
conceal: boolean
|
|
129
|
+
concealCode: boolean
|
|
130
|
+
treeSitterClient?: TreeSitterClient
|
|
131
|
+
/** Creates default renderable for this token */
|
|
132
|
+
defaultRender: () => Renderable | null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type MarkdownCodeBlockRenderer = (
|
|
136
|
+
token: Tokens.Code,
|
|
137
|
+
context: RenderNodeContext,
|
|
138
|
+
) => Renderable | undefined | null
|
|
139
|
+
|
|
140
|
+
export type MarkdownCodeBlockRendererMap =
|
|
141
|
+
| ReadonlyMap<string, MarkdownCodeBlockRenderer>
|
|
142
|
+
| Readonly<Record<string, MarkdownCodeBlockRenderer>>
|
|
143
|
+
|
|
144
|
+
type MarkdownRenderNode = NonNullable<MarkdownOptions["renderNode"]> & {
|
|
145
|
+
codeBlockOnly?: boolean
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function normalizeMarkdownCodeBlockRenderers(
|
|
149
|
+
renderers: MarkdownCodeBlockRendererMap,
|
|
150
|
+
): ReadonlyMap<string, MarkdownCodeBlockRenderer> {
|
|
151
|
+
const rendererMap = new Map<string, MarkdownCodeBlockRenderer>()
|
|
152
|
+
const maybeMap = renderers as Partial<ReadonlyMap<string, MarkdownCodeBlockRenderer>>
|
|
153
|
+
|
|
154
|
+
if (typeof maybeMap.forEach === "function") {
|
|
155
|
+
maybeMap.forEach((renderer, language) => {
|
|
156
|
+
rendererMap.set(language, renderer)
|
|
157
|
+
})
|
|
158
|
+
return rendererMap
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const rendererRecord = renderers as Readonly<Record<string, MarkdownCodeBlockRenderer>>
|
|
162
|
+
for (const [language, renderer] of Object.entries(rendererRecord)) {
|
|
163
|
+
rendererMap.set(language, renderer)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return rendererMap
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function createMarkdownCodeBlockRenderer(
|
|
170
|
+
renderers: MarkdownCodeBlockRendererMap,
|
|
171
|
+
): MarkdownOptions["renderNode"] {
|
|
172
|
+
const rendererMap = normalizeMarkdownCodeBlockRenderers(renderers)
|
|
173
|
+
|
|
174
|
+
const renderNode: MarkdownRenderNode = (token, context) => {
|
|
175
|
+
if (token.type !== "code") {
|
|
176
|
+
return undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const language = infoStringToFiletype(token.lang ?? "")
|
|
180
|
+
if (!language) return undefined
|
|
181
|
+
|
|
182
|
+
return rendererMap.get(language)?.(token as Tokens.Code, context)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
renderNode.codeBlockOnly = true
|
|
186
|
+
return renderNode
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface TableContentCache {
|
|
190
|
+
content: TextTableContent
|
|
191
|
+
cellKeys: Uint32Array[]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface CustomRenderableResult {
|
|
195
|
+
renderable?: Renderable
|
|
196
|
+
tableContentCache?: TableContentCache
|
|
197
|
+
tracksInterBlockMargin: boolean
|
|
198
|
+
canUpdateInPlace: boolean
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface CustomRenderDefaultResult {
|
|
202
|
+
renderable: Renderable | null | undefined
|
|
203
|
+
tableContentCache?: TableContentCache
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface RenderNodeResult {
|
|
207
|
+
renderable?: Renderable
|
|
208
|
+
defaultResult?: CustomRenderDefaultResult
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface ResolvedTableRenderableOptions {
|
|
212
|
+
columnWidthMode: TextTableColumnWidthMode
|
|
213
|
+
columnFitter: TextTableColumnFitter
|
|
214
|
+
wrapMode: "none" | "char" | "word"
|
|
215
|
+
cellPadding: number
|
|
216
|
+
cellPaddingX: number
|
|
217
|
+
cellPaddingY: number
|
|
218
|
+
columnGap: number
|
|
219
|
+
border: boolean
|
|
220
|
+
outerBorder: boolean
|
|
221
|
+
showBorders: boolean
|
|
222
|
+
borderStyle: BorderStyle
|
|
223
|
+
borderColor: ColorInput
|
|
224
|
+
selectable: boolean
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const TRAILING_MARKDOWN_BLOCK_BREAKS_RE = /(?:\r?\n){2,}$/
|
|
228
|
+
const TRAILING_MARKDOWN_BLOCK_NEWLINES_RE = /(?:\r?\n)+$/
|
|
229
|
+
|
|
230
|
+
function colorsEqual(left?: RGBA, right?: RGBA): boolean {
|
|
231
|
+
if (!left || !right) return left === right
|
|
232
|
+
return left.equals(right)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface BlockState {
|
|
236
|
+
token: MarkedToken
|
|
237
|
+
tokenRaw: string // Cache raw for comparison
|
|
238
|
+
marginTop?: number
|
|
239
|
+
renderable: Renderable
|
|
240
|
+
tableContentCache?: TableContentCache
|
|
241
|
+
tracksInterBlockMargin?: boolean
|
|
242
|
+
/** Whether built-in reconciliation can update this renderable without replacing it. */
|
|
243
|
+
canUpdateInPlace: boolean
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export type { ParseState }
|
|
247
|
+
|
|
248
|
+
interface MarkdownRenderBlock {
|
|
249
|
+
token: MarkedToken
|
|
250
|
+
sourceTokenEnd: number
|
|
251
|
+
marginTop: number
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface ListItemRenderInput {
|
|
255
|
+
item: Tokens.ListItem
|
|
256
|
+
marker: string
|
|
257
|
+
markerWidth: number
|
|
258
|
+
id: string
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export class MarkdownRenderable extends Renderable {
|
|
262
|
+
private _content: string = ""
|
|
263
|
+
private _syntaxStyle: SyntaxStyle
|
|
264
|
+
private _fg?: RGBA
|
|
265
|
+
private _bg?: RGBA
|
|
266
|
+
private _conceal: boolean
|
|
267
|
+
private _concealCode: boolean
|
|
268
|
+
private _treeSitterClient?: TreeSitterClient
|
|
269
|
+
private _tableOptions?: MarkdownTableOptions
|
|
270
|
+
private _renderNode?: MarkdownOptions["renderNode"]
|
|
271
|
+
private _internalBlockMode: "coalesced" | "top-level"
|
|
272
|
+
|
|
273
|
+
_parseState: ParseState | null = null
|
|
274
|
+
private _streaming: boolean = false
|
|
275
|
+
_blockStates: BlockState[] = []
|
|
276
|
+
_stableBlockCount = 0
|
|
277
|
+
private _styleDirty: boolean = false
|
|
278
|
+
private _linkifyMarkdownChunks: OnChunksCallback = (chunks, context) =>
|
|
279
|
+
detectLinks(chunks, {
|
|
280
|
+
content: context.content,
|
|
281
|
+
highlights: context.highlights,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
protected _contentDefaultOptions = {
|
|
285
|
+
content: "",
|
|
286
|
+
conceal: true,
|
|
287
|
+
concealCode: false,
|
|
288
|
+
streaming: false,
|
|
289
|
+
internalBlockMode: "coalesced",
|
|
290
|
+
} satisfies Partial<MarkdownOptions>
|
|
291
|
+
|
|
292
|
+
constructor(ctx: RenderContext, options: MarkdownOptions) {
|
|
293
|
+
super(ctx, {
|
|
294
|
+
...options,
|
|
295
|
+
flexDirection: "column",
|
|
296
|
+
flexShrink: options.flexShrink ?? 0,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
this._syntaxStyle = options.syntaxStyle
|
|
300
|
+
this._fg = options.fg ? parseColor(options.fg) : undefined
|
|
301
|
+
this._bg = options.bg ? parseColor(options.bg) : undefined
|
|
302
|
+
this._conceal = options.conceal ?? this._contentDefaultOptions.conceal
|
|
303
|
+
this._concealCode = options.concealCode ?? this._contentDefaultOptions.concealCode
|
|
304
|
+
this._content = options.content ?? this._contentDefaultOptions.content
|
|
305
|
+
this._treeSitterClient = options.treeSitterClient
|
|
306
|
+
this._tableOptions = options.tableOptions
|
|
307
|
+
this._renderNode = options.renderNode
|
|
308
|
+
this._streaming = options.streaming ?? this._contentDefaultOptions.streaming
|
|
309
|
+
this._internalBlockMode = options.internalBlockMode ?? this._contentDefaultOptions.internalBlockMode
|
|
310
|
+
|
|
311
|
+
this.updateBlocks()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
get content(): string {
|
|
315
|
+
return this._content
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
set content(value: string) {
|
|
319
|
+
if (this.isDestroyed) return
|
|
320
|
+
if (this._content !== value) {
|
|
321
|
+
this._content = value
|
|
322
|
+
this.updateBlocks()
|
|
323
|
+
this.requestRender()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
get syntaxStyle(): SyntaxStyle {
|
|
328
|
+
return this._syntaxStyle
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
set syntaxStyle(value: SyntaxStyle) {
|
|
332
|
+
if (this._syntaxStyle !== value) {
|
|
333
|
+
this._syntaxStyle = value
|
|
334
|
+
// Mark dirty - actual re-render happens in renderSelf
|
|
335
|
+
this._styleDirty = true
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
get fg(): RGBA | undefined {
|
|
340
|
+
return this._fg
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
set fg(value: ColorInput | undefined) {
|
|
344
|
+
const next = value ? parseColor(value) : undefined
|
|
345
|
+
if (!colorsEqual(this._fg, next)) {
|
|
346
|
+
this._fg = next
|
|
347
|
+
this._styleDirty = true
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
get bg(): RGBA | undefined {
|
|
352
|
+
return this._bg
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
set bg(value: ColorInput | undefined) {
|
|
356
|
+
const next = value ? parseColor(value) : undefined
|
|
357
|
+
if (!colorsEqual(this._bg, next)) {
|
|
358
|
+
this._bg = next
|
|
359
|
+
this._styleDirty = true
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get conceal(): boolean {
|
|
364
|
+
return this._conceal
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
set conceal(value: boolean) {
|
|
368
|
+
if (this._conceal !== value) {
|
|
369
|
+
this._conceal = value
|
|
370
|
+
// Mark dirty - actual re-render happens in renderSelf
|
|
371
|
+
this._styleDirty = true
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
get concealCode(): boolean {
|
|
376
|
+
return this._concealCode
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
set concealCode(value: boolean) {
|
|
380
|
+
if (this._concealCode !== value) {
|
|
381
|
+
this._concealCode = value
|
|
382
|
+
// Mark dirty - actual re-render happens in renderSelf
|
|
383
|
+
this._styleDirty = true
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
get streaming(): boolean {
|
|
388
|
+
return this._streaming
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
set streaming(value: boolean) {
|
|
392
|
+
if (this.isDestroyed) return
|
|
393
|
+
if (this._streaming !== value) {
|
|
394
|
+
this._streaming = value
|
|
395
|
+
this.updateBlocks(true)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
get tableOptions(): MarkdownTableOptions | undefined {
|
|
400
|
+
return this._tableOptions
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
set tableOptions(value: MarkdownTableOptions | undefined) {
|
|
404
|
+
this._tableOptions = value
|
|
405
|
+
this.applyTableOptionsToBlocks()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
get renderNode(): MarkdownOptions["renderNode"] | undefined {
|
|
409
|
+
return this._renderNode
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
set renderNode(value: MarkdownOptions["renderNode"] | undefined) {
|
|
413
|
+
if (this._renderNode === value) return
|
|
414
|
+
this._renderNode = value
|
|
415
|
+
this.clearBlockStates()
|
|
416
|
+
this._parseState = null
|
|
417
|
+
this.updateBlocks(true)
|
|
418
|
+
this.requestRender()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
get internalBlockMode(): "coalesced" | "top-level" {
|
|
422
|
+
return this._internalBlockMode
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
set internalBlockMode(value: "coalesced" | "top-level") {
|
|
426
|
+
if (this._internalBlockMode === value) return
|
|
427
|
+
this._internalBlockMode = value
|
|
428
|
+
this.updateBlocks(true)
|
|
429
|
+
this.requestRender()
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private getStyle(group: string): StyleDefinition | undefined {
|
|
433
|
+
// The solid reconciler applies props via setters in JSX declaration order.
|
|
434
|
+
// If `content` is set before `syntaxStyle`, updateBlocks() runs before
|
|
435
|
+
// _syntaxStyle is initialized.
|
|
436
|
+
if (!this._syntaxStyle) return undefined
|
|
437
|
+
let style = this._syntaxStyle.getStyle(group)
|
|
438
|
+
if (!style && group.includes(".")) {
|
|
439
|
+
const baseName = group.split(".")[0]
|
|
440
|
+
style = this._syntaxStyle.getStyle(baseName)
|
|
441
|
+
}
|
|
442
|
+
return style
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private createChunk(text: string, group: string, link?: { url: string }): TextChunk {
|
|
446
|
+
const style = this.getStyle(group) || this.getStyle("default")
|
|
447
|
+
return {
|
|
448
|
+
__isChunk: true,
|
|
449
|
+
text,
|
|
450
|
+
fg: style?.fg,
|
|
451
|
+
bg: style?.bg,
|
|
452
|
+
attributes: style
|
|
453
|
+
? createTextAttributes({
|
|
454
|
+
bold: style.bold,
|
|
455
|
+
italic: style.italic,
|
|
456
|
+
underline: style.underline,
|
|
457
|
+
dim: style.dim,
|
|
458
|
+
})
|
|
459
|
+
: 0,
|
|
460
|
+
link,
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private createDefaultChunk(text: string): TextChunk {
|
|
465
|
+
return this.createChunk(text, "default")
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private createInitialStyledText(token: MarkedToken): StyledText | undefined {
|
|
469
|
+
if (!this._streaming) return undefined
|
|
470
|
+
|
|
471
|
+
const chunks: TextChunk[] = []
|
|
472
|
+
if ("tokens" in token && Array.isArray(token.tokens)) {
|
|
473
|
+
this.renderInlineContent(token.tokens, chunks)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (chunks.length === 0 && "text" in token && typeof token.text === "string") {
|
|
477
|
+
this.renderInlineContent(Lexer.lexInline(token.text), chunks)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return chunks.length > 0 ? new StyledText(chunks) : undefined
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private renderInlineContent(tokens: Token[], chunks: TextChunk[]): void {
|
|
484
|
+
for (const token of tokens) {
|
|
485
|
+
this.renderInlineToken(token as MarkedToken, chunks)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private renderInlineToken(token: MarkedToken, chunks: TextChunk[]): void {
|
|
490
|
+
switch (token.type) {
|
|
491
|
+
case "text":
|
|
492
|
+
chunks.push(this.createDefaultChunk(token.text))
|
|
493
|
+
break
|
|
494
|
+
|
|
495
|
+
case "escape":
|
|
496
|
+
chunks.push(this.createDefaultChunk(token.text))
|
|
497
|
+
break
|
|
498
|
+
|
|
499
|
+
case "codespan":
|
|
500
|
+
if (this._conceal) {
|
|
501
|
+
chunks.push(this.createChunk(token.text, "markup.raw"))
|
|
502
|
+
} else {
|
|
503
|
+
chunks.push(this.createChunk("`", "markup.raw"))
|
|
504
|
+
chunks.push(this.createChunk(token.text, "markup.raw"))
|
|
505
|
+
chunks.push(this.createChunk("`", "markup.raw"))
|
|
506
|
+
}
|
|
507
|
+
break
|
|
508
|
+
|
|
509
|
+
case "strong":
|
|
510
|
+
if (!this._conceal) {
|
|
511
|
+
chunks.push(this.createChunk("**", "markup.strong"))
|
|
512
|
+
}
|
|
513
|
+
for (const child of token.tokens) {
|
|
514
|
+
this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.strong")
|
|
515
|
+
}
|
|
516
|
+
if (!this._conceal) {
|
|
517
|
+
chunks.push(this.createChunk("**", "markup.strong"))
|
|
518
|
+
}
|
|
519
|
+
break
|
|
520
|
+
|
|
521
|
+
case "em":
|
|
522
|
+
if (!this._conceal) {
|
|
523
|
+
chunks.push(this.createChunk("*", "markup.italic"))
|
|
524
|
+
}
|
|
525
|
+
for (const child of token.tokens) {
|
|
526
|
+
this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.italic")
|
|
527
|
+
}
|
|
528
|
+
if (!this._conceal) {
|
|
529
|
+
chunks.push(this.createChunk("*", "markup.italic"))
|
|
530
|
+
}
|
|
531
|
+
break
|
|
532
|
+
|
|
533
|
+
case "del":
|
|
534
|
+
if (!this._conceal) {
|
|
535
|
+
chunks.push(this.createChunk("~~", "markup.strikethrough"))
|
|
536
|
+
}
|
|
537
|
+
for (const child of token.tokens) {
|
|
538
|
+
this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.strikethrough")
|
|
539
|
+
}
|
|
540
|
+
if (!this._conceal) {
|
|
541
|
+
chunks.push(this.createChunk("~~", "markup.strikethrough"))
|
|
542
|
+
}
|
|
543
|
+
break
|
|
544
|
+
|
|
545
|
+
case "link": {
|
|
546
|
+
const linkHref = { url: token.href }
|
|
547
|
+
if (this._conceal) {
|
|
548
|
+
for (const child of token.tokens) {
|
|
549
|
+
this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.link.label", linkHref)
|
|
550
|
+
}
|
|
551
|
+
chunks.push(this.createChunk(" (", "markup.link", linkHref))
|
|
552
|
+
chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
|
|
553
|
+
chunks.push(this.createChunk(")", "markup.link", linkHref))
|
|
554
|
+
} else {
|
|
555
|
+
chunks.push(this.createChunk("[", "markup.link", linkHref))
|
|
556
|
+
for (const child of token.tokens) {
|
|
557
|
+
this.renderInlineTokenWithStyle(child as MarkedToken, chunks, "markup.link.label", linkHref)
|
|
558
|
+
}
|
|
559
|
+
chunks.push(this.createChunk("](", "markup.link", linkHref))
|
|
560
|
+
chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
|
|
561
|
+
chunks.push(this.createChunk(")", "markup.link", linkHref))
|
|
562
|
+
}
|
|
563
|
+
break
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
case "image": {
|
|
567
|
+
const imageHref = { url: token.href }
|
|
568
|
+
if (this._conceal) {
|
|
569
|
+
chunks.push(this.createChunk(token.text || "image", "markup.link.label", imageHref))
|
|
570
|
+
} else {
|
|
571
|
+
chunks.push(this.createChunk(")
|
|
574
|
+
chunks.push(this.createChunk(token.href, "markup.link.url", imageHref))
|
|
575
|
+
chunks.push(this.createChunk(")", "markup.link", imageHref))
|
|
576
|
+
}
|
|
577
|
+
break
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
case "br":
|
|
581
|
+
chunks.push(this.createDefaultChunk("\n"))
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
default:
|
|
585
|
+
if ("tokens" in token && Array.isArray(token.tokens)) {
|
|
586
|
+
this.renderInlineContent(token.tokens, chunks)
|
|
587
|
+
} else if ("text" in token && typeof token.text === "string") {
|
|
588
|
+
chunks.push(this.createDefaultChunk(token.text))
|
|
589
|
+
}
|
|
590
|
+
break
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private renderInlineTokenWithStyle(
|
|
595
|
+
token: MarkedToken,
|
|
596
|
+
chunks: TextChunk[],
|
|
597
|
+
styleGroup: string,
|
|
598
|
+
link?: { url: string },
|
|
599
|
+
): void {
|
|
600
|
+
switch (token.type) {
|
|
601
|
+
case "text":
|
|
602
|
+
chunks.push(this.createChunk(token.text, styleGroup, link))
|
|
603
|
+
break
|
|
604
|
+
|
|
605
|
+
case "escape":
|
|
606
|
+
chunks.push(this.createChunk(token.text, styleGroup, link))
|
|
607
|
+
break
|
|
608
|
+
|
|
609
|
+
case "codespan":
|
|
610
|
+
if (this._conceal) {
|
|
611
|
+
chunks.push(this.createChunk(token.text, "markup.raw", link))
|
|
612
|
+
} else {
|
|
613
|
+
chunks.push(this.createChunk("`", "markup.raw", link))
|
|
614
|
+
chunks.push(this.createChunk(token.text, "markup.raw", link))
|
|
615
|
+
chunks.push(this.createChunk("`", "markup.raw", link))
|
|
616
|
+
}
|
|
617
|
+
break
|
|
618
|
+
|
|
619
|
+
default:
|
|
620
|
+
this.renderInlineToken(token, chunks)
|
|
621
|
+
break
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private applyMargins(renderable: Renderable, marginTop: number, marginBottom: number): void {
|
|
626
|
+
renderable.marginTop = marginTop
|
|
627
|
+
renderable.marginBottom = marginBottom
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private createMarkdownCodeRenderable(
|
|
631
|
+
content: string,
|
|
632
|
+
id: string,
|
|
633
|
+
marginBottom: number = 0,
|
|
634
|
+
onChunks: OnChunksCallback = this._linkifyMarkdownChunks,
|
|
635
|
+
baseHighlight?: string,
|
|
636
|
+
initialStyledText?: StyledText,
|
|
637
|
+
): CodeRenderable {
|
|
638
|
+
return new CodeRenderable(this.ctx, {
|
|
639
|
+
id,
|
|
640
|
+
content,
|
|
641
|
+
filetype: "markdown",
|
|
642
|
+
syntaxStyle: this._syntaxStyle,
|
|
643
|
+
fg: this._fg,
|
|
644
|
+
bg: this._bg,
|
|
645
|
+
conceal: this._conceal,
|
|
646
|
+
drawUnstyledText: initialStyledText !== undefined,
|
|
647
|
+
streaming: true,
|
|
648
|
+
initialStyledText,
|
|
649
|
+
baseHighlight,
|
|
650
|
+
onChunks,
|
|
651
|
+
treeSitterClient: this._treeSitterClient,
|
|
652
|
+
width: "100%",
|
|
653
|
+
marginBottom,
|
|
654
|
+
})
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private getBlockquoteContent(token: MarkedToken): string {
|
|
658
|
+
return "text" in token && typeof token.text === "string" && token.text ? token.text : " "
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private getBlockquoteBorderColor(): ColorInput {
|
|
662
|
+
return this.getStyle("conceal")?.fg ?? this.getStyle("default")?.fg ?? this._fg ?? "#FFFFFF"
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private createBlockquoteRenderable(token: MarkedToken, id: string, marginBottom: number = 0): BoxRenderable {
|
|
666
|
+
const renderable = new BoxRenderable(this.ctx, {
|
|
667
|
+
id,
|
|
668
|
+
width: "100%",
|
|
669
|
+
border: ["left"],
|
|
670
|
+
borderColor: this.getBlockquoteBorderColor(),
|
|
671
|
+
paddingLeft: 1,
|
|
672
|
+
flexShrink: 0,
|
|
673
|
+
marginBottom,
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
renderable.add(
|
|
677
|
+
this.createMarkdownCodeRenderable(
|
|
678
|
+
this.getBlockquoteContent(token),
|
|
679
|
+
`${id}-content`,
|
|
680
|
+
0,
|
|
681
|
+
this._linkifyMarkdownChunks,
|
|
682
|
+
"markup.quote",
|
|
683
|
+
),
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
return renderable
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private createListRenderable(token: Tokens.List, id: string, marginBottom: number = 0): BoxRenderable {
|
|
690
|
+
const list = new BoxRenderable(this.ctx, {
|
|
691
|
+
id,
|
|
692
|
+
width: "100%",
|
|
693
|
+
flexDirection: "column",
|
|
694
|
+
flexShrink: 0,
|
|
695
|
+
marginBottom,
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
for (const item of this.getListItemInputs(token, id)) {
|
|
699
|
+
list.add(this.createListItemRenderable(item))
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return list
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private getListItemInputs(token: Tokens.List, id: string): ListItemRenderInput[] {
|
|
706
|
+
const items = token.items ?? []
|
|
707
|
+
const start = token.start === "" || token.start === undefined || token.start === null ? 1 : Number(token.start)
|
|
708
|
+
const markerWidth = Math.max(1, ...items.map((_, index) => (token.ordered ? `${start + index}.` : "-").length))
|
|
709
|
+
|
|
710
|
+
return items.map((item, index) => ({
|
|
711
|
+
item,
|
|
712
|
+
marker: token.ordered ? `${start + index}.` : "-",
|
|
713
|
+
markerWidth,
|
|
714
|
+
id: `${id}-item-${index}`,
|
|
715
|
+
}))
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private applyListRenderable(
|
|
719
|
+
renderable: Renderable,
|
|
720
|
+
token: Tokens.List,
|
|
721
|
+
previousToken: Tokens.List | undefined,
|
|
722
|
+
id: string,
|
|
723
|
+
marginBottom: number = 0,
|
|
724
|
+
): boolean {
|
|
725
|
+
if (!(renderable instanceof BoxRenderable)) return false
|
|
726
|
+
|
|
727
|
+
renderable.marginBottom = marginBottom
|
|
728
|
+
|
|
729
|
+
const inputs = this.getListItemInputs(token, id)
|
|
730
|
+
const previousItems = previousToken?.items ?? []
|
|
731
|
+
const rows = renderable.getChildren()
|
|
732
|
+
|
|
733
|
+
for (let index = 0; index < inputs.length; index += 1) {
|
|
734
|
+
const input = inputs[index]
|
|
735
|
+
const existing = rows[index]
|
|
736
|
+
|
|
737
|
+
if (existing instanceof BoxRenderable && this.applyListItemRenderable(existing, input, previousItems[index])) {
|
|
738
|
+
continue
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
existing?.destroyRecursively()
|
|
742
|
+
renderable.add(this.createListItemRenderable(input), index)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
for (let index = rows.length - 1; index >= inputs.length; index -= 1) {
|
|
746
|
+
rows[index]?.destroyRecursively()
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return true
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private createListItemRenderable(input: ListItemRenderInput): BoxRenderable {
|
|
753
|
+
const row = new BoxRenderable(this.ctx, {
|
|
754
|
+
id: input.id,
|
|
755
|
+
width: "100%",
|
|
756
|
+
flexDirection: "row",
|
|
757
|
+
flexShrink: 0,
|
|
758
|
+
marginBottom: /\n[ \t]*\n$/.test(input.item.raw) ? 1 : 0,
|
|
759
|
+
})
|
|
760
|
+
row.add(
|
|
761
|
+
new TextRenderable(this.ctx, {
|
|
762
|
+
id: `${input.id}-marker`,
|
|
763
|
+
content: new StyledText([this.createChunk(input.marker.padStart(input.markerWidth) + " ", "markup.list")]),
|
|
764
|
+
width: input.markerWidth + 1,
|
|
765
|
+
flexShrink: 0,
|
|
766
|
+
}),
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
const content = new BoxRenderable(this.ctx, {
|
|
770
|
+
id: `${input.id}-content`,
|
|
771
|
+
flexDirection: "column",
|
|
772
|
+
flexGrow: 1,
|
|
773
|
+
flexShrink: 1,
|
|
774
|
+
})
|
|
775
|
+
row.add(content)
|
|
776
|
+
|
|
777
|
+
let pendingMarginTop = 0
|
|
778
|
+
for (let index = 0; index < input.item.tokens.length; index += 1) {
|
|
779
|
+
const child = input.item.tokens[index] as MarkedToken | undefined
|
|
780
|
+
if (!child) continue
|
|
781
|
+
if (child.type === "checkbox") continue
|
|
782
|
+
if (child.type === "space") {
|
|
783
|
+
pendingMarginTop = Math.max(pendingMarginTop, 1)
|
|
784
|
+
continue
|
|
785
|
+
}
|
|
786
|
+
const renderable = this.createListChildRenderable(child, `${input.id}-child-${index}`)
|
|
787
|
+
if (!renderable) continue
|
|
788
|
+
renderable.marginTop = child.type === "list" ? 0 : pendingMarginTop
|
|
789
|
+
pendingMarginTop = 0
|
|
790
|
+
content.add(renderable)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return row
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
private applyListItemRenderable(
|
|
797
|
+
row: BoxRenderable,
|
|
798
|
+
input: ListItemRenderInput,
|
|
799
|
+
previousItem: Tokens.ListItem | undefined,
|
|
800
|
+
): boolean {
|
|
801
|
+
this.applyListItemMarker(row, input)
|
|
802
|
+
|
|
803
|
+
const content = row.getChildren()[1]
|
|
804
|
+
if (!(content instanceof BoxRenderable)) return false
|
|
805
|
+
|
|
806
|
+
if (previousItem && previousItem.raw === input.item.raw) {
|
|
807
|
+
return true
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return this.applyListItemChildren(content, input.item, previousItem, input.id)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private applyListItemChildren(
|
|
814
|
+
content: BoxRenderable,
|
|
815
|
+
item: Tokens.ListItem,
|
|
816
|
+
previousItem: Tokens.ListItem | undefined,
|
|
817
|
+
id: string,
|
|
818
|
+
): boolean {
|
|
819
|
+
const previousTokens = previousItem ? this.getRenderableListItemTokens(previousItem) : []
|
|
820
|
+
const children = content.getChildren()
|
|
821
|
+
let childIndex = 0
|
|
822
|
+
let pendingMarginTop = 0
|
|
823
|
+
|
|
824
|
+
for (let tokenIndex = 0; tokenIndex < item.tokens.length; tokenIndex += 1) {
|
|
825
|
+
const token = item.tokens[tokenIndex] as MarkedToken | undefined
|
|
826
|
+
if (!token) continue
|
|
827
|
+
if (token.type === "checkbox") continue
|
|
828
|
+
if (token.type === "space") {
|
|
829
|
+
pendingMarginTop = Math.max(pendingMarginTop, 1)
|
|
830
|
+
continue
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const existing = children[childIndex]
|
|
834
|
+
const childId = `${id}-child-${tokenIndex}`
|
|
835
|
+
|
|
836
|
+
const marginTop = token.type === "list" ? 0 : pendingMarginTop
|
|
837
|
+
pendingMarginTop = 0
|
|
838
|
+
|
|
839
|
+
if (!existing) {
|
|
840
|
+
const renderable = this.createListChildRenderable(token, childId)
|
|
841
|
+
if (!renderable) return false
|
|
842
|
+
renderable.marginTop = marginTop
|
|
843
|
+
content.add(renderable, childIndex)
|
|
844
|
+
childIndex += 1
|
|
845
|
+
continue
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (!this.applyListChildRenderable(existing, token, previousTokens[childIndex], childId)) {
|
|
849
|
+
return false
|
|
850
|
+
}
|
|
851
|
+
existing.marginTop = marginTop
|
|
852
|
+
childIndex += 1
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
this.destroyListItemChildrenAfter(content, childIndex)
|
|
856
|
+
return true
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private getRenderableListItemTokens(item: Tokens.ListItem): MarkedToken[] {
|
|
860
|
+
const tokens: MarkedToken[] = []
|
|
861
|
+
|
|
862
|
+
for (const token of item.tokens as MarkedToken[]) {
|
|
863
|
+
if (token.type === "checkbox" || token.type === "space") continue
|
|
864
|
+
tokens.push(token)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return tokens
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
private applyListChildRenderable(
|
|
871
|
+
renderable: Renderable,
|
|
872
|
+
token: MarkedToken,
|
|
873
|
+
previousToken: MarkedToken | undefined,
|
|
874
|
+
id: string,
|
|
875
|
+
): boolean {
|
|
876
|
+
if ((token.type === "text" || token.type === "paragraph") && renderable instanceof CodeRenderable) {
|
|
877
|
+
this.applyMarkdownCodeRenderable(renderable, this.normalizeScrollbackMarkdownBlockRaw(token.raw), 0)
|
|
878
|
+
return true
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (token.type === "list" && renderable instanceof BoxRenderable) {
|
|
882
|
+
return this.applyListRenderable(renderable, token as Tokens.List, previousToken as Tokens.List | undefined, id)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (token.type === "code" && renderable instanceof CodeRenderable) {
|
|
886
|
+
this.applyCodeBlockRenderable(renderable, token as Tokens.Code, 0)
|
|
887
|
+
return true
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return previousToken?.raw === token.raw
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private destroyListItemChildrenAfter(content: BoxRenderable, index: number): void {
|
|
894
|
+
const children = content.getChildren()
|
|
895
|
+
for (let i = children.length - 1; i >= index; i -= 1) {
|
|
896
|
+
children[i]?.destroyRecursively()
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
private applyListItemMarker(row: BoxRenderable, input: ListItemRenderInput): void {
|
|
901
|
+
const marker = row.getChildren()[0]
|
|
902
|
+
if (!(marker instanceof TextRenderable)) return
|
|
903
|
+
const marginBottom = /\n[ \t]*\n$/.test(input.item.raw) ? 1 : 0
|
|
904
|
+
const markerWidth = input.markerWidth + 1
|
|
905
|
+
const markerText = input.marker.padStart(input.markerWidth) + " "
|
|
906
|
+
|
|
907
|
+
if (row.marginBottom !== marginBottom) row.marginBottom = marginBottom
|
|
908
|
+
if (marker.width !== markerWidth) marker.width = markerWidth
|
|
909
|
+
if (marker.chunks[0]?.text !== markerText) {
|
|
910
|
+
marker.content = new StyledText([this.createChunk(markerText, "markup.list")])
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
private createListChildRenderable(token: MarkedToken, id: string): Renderable | null {
|
|
915
|
+
if (token.type === "text" || token.type === "paragraph") {
|
|
916
|
+
return this.createMarkdownCodeRenderable(
|
|
917
|
+
this.normalizeScrollbackMarkdownBlockRaw(token.raw),
|
|
918
|
+
id,
|
|
919
|
+
0,
|
|
920
|
+
this._linkifyMarkdownChunks,
|
|
921
|
+
undefined,
|
|
922
|
+
this.createInitialStyledText(token),
|
|
923
|
+
)
|
|
924
|
+
}
|
|
925
|
+
if (token.type === "list") return this.createListRenderable(token as Tokens.List, id)
|
|
926
|
+
if (token.type === "code") return this.createCodeRenderable(token as Tokens.Code, id)
|
|
927
|
+
if (token.type === "blockquote") return this.createBlockquoteRenderable(token, id)
|
|
928
|
+
if (token.type === "hr") return this.createHorizontalRuleRenderable(id)
|
|
929
|
+
if (token.type === "table") return this.createTableBlock(token as Tokens.Table, id).renderable
|
|
930
|
+
return token.raw
|
|
931
|
+
? this.createMarkdownCodeRenderable(
|
|
932
|
+
token.raw,
|
|
933
|
+
id,
|
|
934
|
+
0,
|
|
935
|
+
this._linkifyMarkdownChunks,
|
|
936
|
+
undefined,
|
|
937
|
+
this.createInitialStyledText(token),
|
|
938
|
+
)
|
|
939
|
+
: null
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private createHorizontalRuleRenderable(id: string, marginBottom: number = 0): BoxRenderable {
|
|
943
|
+
return new BoxRenderable(this.ctx, {
|
|
944
|
+
id,
|
|
945
|
+
width: "100%",
|
|
946
|
+
height: 1,
|
|
947
|
+
border: ["top"],
|
|
948
|
+
borderColor: this.getStyle("conceal")?.fg ?? this._fg ?? "#888888",
|
|
949
|
+
flexShrink: 0,
|
|
950
|
+
marginBottom,
|
|
951
|
+
})
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private createCodeRenderable(token: Tokens.Code, id: string, marginBottom: number = 0): Renderable {
|
|
955
|
+
return new CodeRenderable(this.ctx, {
|
|
956
|
+
id,
|
|
957
|
+
content: token.text,
|
|
958
|
+
filetype: infoStringToFiletype(token.lang ?? ""),
|
|
959
|
+
syntaxStyle: this._syntaxStyle,
|
|
960
|
+
fg: this._fg,
|
|
961
|
+
bg: this._bg,
|
|
962
|
+
conceal: this._concealCode,
|
|
963
|
+
drawUnstyledText: !this._streaming,
|
|
964
|
+
streaming: this._streaming,
|
|
965
|
+
treeSitterClient: this._treeSitterClient,
|
|
966
|
+
width: "100%",
|
|
967
|
+
marginBottom,
|
|
968
|
+
})
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private applyMarkdownCodeRenderable(
|
|
972
|
+
renderable: CodeRenderable,
|
|
973
|
+
content: string,
|
|
974
|
+
marginBottom: number,
|
|
975
|
+
baseHighlight?: string,
|
|
976
|
+
initialStyledText?: StyledText,
|
|
977
|
+
): void {
|
|
978
|
+
renderable.initialStyledText = initialStyledText
|
|
979
|
+
renderable.filetype = "markdown"
|
|
980
|
+
renderable.syntaxStyle = this._syntaxStyle
|
|
981
|
+
renderable.fg = this._fg
|
|
982
|
+
renderable.bg = this._bg
|
|
983
|
+
renderable.conceal = this._conceal
|
|
984
|
+
renderable.drawUnstyledText = initialStyledText !== undefined
|
|
985
|
+
renderable.streaming = true
|
|
986
|
+
renderable.baseHighlight = baseHighlight
|
|
987
|
+
renderable.content = content
|
|
988
|
+
renderable.marginBottom = marginBottom
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
private applyBlockquoteRenderable(renderable: Renderable, token: MarkedToken, marginBottom: number): void {
|
|
992
|
+
if (!(renderable instanceof BoxRenderable)) return
|
|
993
|
+
|
|
994
|
+
renderable.borderColor = this.getBlockquoteBorderColor()
|
|
995
|
+
renderable.marginBottom = marginBottom
|
|
996
|
+
|
|
997
|
+
const child = renderable.getChildren()[0]
|
|
998
|
+
if (child instanceof CodeRenderable) {
|
|
999
|
+
this.applyMarkdownCodeRenderable(child, this.getBlockquoteContent(token), 0, "markup.quote")
|
|
1000
|
+
return
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
for (const existing of renderable.getChildren()) {
|
|
1004
|
+
existing.destroyRecursively()
|
|
1005
|
+
}
|
|
1006
|
+
renderable.add(
|
|
1007
|
+
this.createMarkdownCodeRenderable(
|
|
1008
|
+
this.getBlockquoteContent(token),
|
|
1009
|
+
`${renderable.id}-content`,
|
|
1010
|
+
0,
|
|
1011
|
+
this._linkifyMarkdownChunks,
|
|
1012
|
+
"markup.quote",
|
|
1013
|
+
),
|
|
1014
|
+
)
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
private applyCodeBlockRenderable(renderable: Renderable, token: Tokens.Code, marginBottom: number): void {
|
|
1018
|
+
if (!(renderable instanceof CodeRenderable)) return
|
|
1019
|
+
|
|
1020
|
+
renderable.filetype = infoStringToFiletype(token.lang ?? "")
|
|
1021
|
+
renderable.syntaxStyle = this._syntaxStyle
|
|
1022
|
+
renderable.fg = this._fg
|
|
1023
|
+
renderable.bg = this._bg
|
|
1024
|
+
renderable.conceal = this._concealCode
|
|
1025
|
+
renderable.drawUnstyledText = !this._streaming
|
|
1026
|
+
renderable.streaming = this._streaming
|
|
1027
|
+
renderable.content = token.text
|
|
1028
|
+
renderable.marginBottom = marginBottom
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
private shouldRenderSeparately(token: MarkedToken): boolean {
|
|
1032
|
+
return token.type === "code" || token.type === "table" || token.type === "blockquote" || token.type === "hr"
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private getInterBlockMargin(token: MarkedToken, nextToken: MarkedToken | undefined): number {
|
|
1036
|
+
if (!nextToken) return 0
|
|
1037
|
+
if (this.shouldRenderSeparately(token)) return 1
|
|
1038
|
+
if (!this.shouldRenderSeparately(nextToken)) return 0
|
|
1039
|
+
return TRAILING_MARKDOWN_BLOCK_NEWLINES_RE.test(token.raw) ? 0 : 1
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
private applyInterBlockMargin(state: BlockState, token: MarkedToken, nextToken: MarkedToken | undefined): void {
|
|
1043
|
+
if (state.tracksInterBlockMargin === false) return
|
|
1044
|
+
state.renderable.marginBottom = this.getInterBlockMargin(token, nextToken)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
private createMarkdownBlockToken(raw: string): MarkedToken {
|
|
1048
|
+
return {
|
|
1049
|
+
type: "paragraph",
|
|
1050
|
+
raw,
|
|
1051
|
+
text: raw,
|
|
1052
|
+
tokens: [],
|
|
1053
|
+
} as MarkedToken
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
private normalizeMarkdownBlockRaw(raw: string): string {
|
|
1057
|
+
return raw.replace(TRAILING_MARKDOWN_BLOCK_BREAKS_RE, "\n")
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
private normalizeScrollbackMarkdownBlockRaw(raw: string): string {
|
|
1061
|
+
return raw.replace(TRAILING_MARKDOWN_BLOCK_NEWLINES_RE, "")
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private isCodeBlockOnlyRenderer(): boolean {
|
|
1065
|
+
return (this._renderNode as MarkdownRenderNode | undefined)?.codeBlockOnly === true
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private buildRenderableTokens(tokens: MarkedToken[]): MarkedToken[] {
|
|
1069
|
+
if (this._renderNode && !this.isCodeBlockOnlyRenderer()) {
|
|
1070
|
+
return tokens.filter((token) => token.type !== "space")
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const renderTokens: MarkedToken[] = []
|
|
1074
|
+
let markdownRaw = ""
|
|
1075
|
+
|
|
1076
|
+
const flushMarkdownRaw = (): void => {
|
|
1077
|
+
if (markdownRaw.length === 0) return
|
|
1078
|
+
const normalizedRaw = this.normalizeMarkdownBlockRaw(markdownRaw)
|
|
1079
|
+
if (normalizedRaw.length > 0) {
|
|
1080
|
+
renderTokens.push(this.createMarkdownBlockToken(normalizedRaw))
|
|
1081
|
+
}
|
|
1082
|
+
markdownRaw = ""
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1086
|
+
const token = tokens[i]
|
|
1087
|
+
|
|
1088
|
+
if (token.type === "space") {
|
|
1089
|
+
if (markdownRaw.length === 0) {
|
|
1090
|
+
continue
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let nextIndex = i + 1
|
|
1094
|
+
while (nextIndex < tokens.length && tokens[nextIndex].type === "space") {
|
|
1095
|
+
nextIndex += 1
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const nextToken = tokens[nextIndex]
|
|
1099
|
+
if (nextToken && !this.shouldRenderSeparately(nextToken)) {
|
|
1100
|
+
markdownRaw += token.raw
|
|
1101
|
+
}
|
|
1102
|
+
continue
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (this.shouldRenderSeparately(token)) {
|
|
1106
|
+
flushMarkdownRaw()
|
|
1107
|
+
renderTokens.push(token)
|
|
1108
|
+
continue
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
markdownRaw += token.raw
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
flushMarkdownRaw()
|
|
1115
|
+
|
|
1116
|
+
return renderTokens
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private buildTopLevelRenderBlocks(tokens: MarkedToken[]): MarkdownRenderBlock[] {
|
|
1120
|
+
const blocks: MarkdownRenderBlock[] = []
|
|
1121
|
+
let gapBefore = ""
|
|
1122
|
+
|
|
1123
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1124
|
+
const token = tokens[i]
|
|
1125
|
+
if (token.type === "space") {
|
|
1126
|
+
gapBefore += token.raw
|
|
1127
|
+
continue
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const prev = blocks[blocks.length - 1]
|
|
1131
|
+
const marginTop = prev && this.shouldAddTopLevelMargin(prev.token, token, gapBefore) ? 1 : 0
|
|
1132
|
+
|
|
1133
|
+
blocks.push({
|
|
1134
|
+
token,
|
|
1135
|
+
sourceTokenEnd: i + 1,
|
|
1136
|
+
marginTop,
|
|
1137
|
+
})
|
|
1138
|
+
gapBefore = ""
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return blocks
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private shouldAddTopLevelMargin(prev: MarkedToken, current: MarkedToken, gapBefore: string): boolean {
|
|
1145
|
+
if (this.isSeparatedTopLevelBlock(prev) || this.isSeparatedTopLevelBlock(current)) return true
|
|
1146
|
+
if (prev.type !== "paragraph" || current.type !== "paragraph") return false
|
|
1147
|
+
return TRAILING_MARKDOWN_BLOCK_BREAKS_RE.test(prev.raw + gapBefore)
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private isSeparatedTopLevelBlock(token: MarkedToken): boolean {
|
|
1151
|
+
return token.type === "heading" || token.type === "list" || this.shouldRenderSeparately(token)
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
private getTableRowsToRender(table: Tokens.Table): Tokens.TableCell[][] {
|
|
1155
|
+
return table.rows
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
private hashString(value: string, seed: number): number {
|
|
1159
|
+
let hash = seed >>> 0
|
|
1160
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1161
|
+
hash ^= value.charCodeAt(i)
|
|
1162
|
+
hash = Math.imul(hash, 16777619)
|
|
1163
|
+
}
|
|
1164
|
+
return hash >>> 0
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
private hashTableToken(token: MarkedToken, seed: number, depth: number = 0): number {
|
|
1168
|
+
let hash = this.hashString(token.type, seed)
|
|
1169
|
+
|
|
1170
|
+
if ("raw" in token && typeof token.raw === "string") {
|
|
1171
|
+
return this.hashString(token.raw, hash)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if ("text" in token && typeof token.text === "string") {
|
|
1175
|
+
hash = this.hashString(token.text, hash)
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (depth < 2 && "tokens" in token && Array.isArray(token.tokens)) {
|
|
1179
|
+
for (const child of token.tokens) {
|
|
1180
|
+
hash = this.hashTableToken(child as MarkedToken, hash, depth + 1)
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return hash >>> 0
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private getTableCellKey(cell: Tokens.TableCell | undefined, isHeader: boolean): number {
|
|
1188
|
+
const seed = isHeader ? 2902232141 : 1371922141
|
|
1189
|
+
if (!cell) {
|
|
1190
|
+
return seed
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (typeof cell.text === "string") {
|
|
1194
|
+
return this.hashString(cell.text, seed)
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (Array.isArray(cell.tokens) && cell.tokens.length > 0) {
|
|
1198
|
+
let hash = seed ^ cell.tokens.length
|
|
1199
|
+
for (const token of cell.tokens) {
|
|
1200
|
+
hash = this.hashTableToken(token as MarkedToken, hash)
|
|
1201
|
+
}
|
|
1202
|
+
return hash >>> 0
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return (seed ^ 2654435769) >>> 0
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
private createTableDataCellChunks(cell: Tokens.TableCell | undefined): TextChunk[] {
|
|
1209
|
+
const chunks: TextChunk[] = []
|
|
1210
|
+
if (cell) {
|
|
1211
|
+
this.renderInlineContent(cell.tokens, chunks)
|
|
1212
|
+
}
|
|
1213
|
+
return chunks.length > 0 ? chunks : [this.createDefaultChunk(" ")]
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
private createTableHeaderCellChunks(cell: Tokens.TableCell): TextChunk[] {
|
|
1217
|
+
const chunks: TextChunk[] = []
|
|
1218
|
+
this.renderInlineContent(cell.tokens, chunks)
|
|
1219
|
+
|
|
1220
|
+
const baseChunks = chunks.length > 0 ? chunks : [this.createDefaultChunk(" ")]
|
|
1221
|
+
const headingStyle = this.getStyle("markup.heading") || this.getStyle("default")
|
|
1222
|
+
if (!headingStyle) {
|
|
1223
|
+
return baseChunks
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const headingAttributes = createTextAttributes({
|
|
1227
|
+
bold: headingStyle.bold,
|
|
1228
|
+
italic: headingStyle.italic,
|
|
1229
|
+
underline: headingStyle.underline,
|
|
1230
|
+
dim: headingStyle.dim,
|
|
1231
|
+
})
|
|
1232
|
+
|
|
1233
|
+
return baseChunks.map((chunk) => ({
|
|
1234
|
+
...chunk,
|
|
1235
|
+
fg: headingStyle.fg ?? chunk.fg,
|
|
1236
|
+
bg: headingStyle.bg ?? chunk.bg,
|
|
1237
|
+
attributes: headingAttributes,
|
|
1238
|
+
}))
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
private buildTableContentCache(
|
|
1242
|
+
table: Tokens.Table,
|
|
1243
|
+
previous?: TableContentCache,
|
|
1244
|
+
forceRegenerate: boolean = false,
|
|
1245
|
+
): { cache: TableContentCache | null; changed: boolean } {
|
|
1246
|
+
const colCount = table.header.length
|
|
1247
|
+
const rowsToRender = this.getTableRowsToRender(table)
|
|
1248
|
+
if (colCount === 0 || rowsToRender.length === 0) {
|
|
1249
|
+
return { cache: null, changed: previous !== undefined }
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const content: TextTableContent = []
|
|
1253
|
+
const cellKeys: Uint32Array[] = []
|
|
1254
|
+
const totalRows = rowsToRender.length + 1
|
|
1255
|
+
|
|
1256
|
+
let changed = forceRegenerate || !previous
|
|
1257
|
+
|
|
1258
|
+
for (let rowIndex = 0; rowIndex < totalRows; rowIndex += 1) {
|
|
1259
|
+
const rowContent: TextTableCellContent[] = []
|
|
1260
|
+
const rowKeys = new Uint32Array(colCount)
|
|
1261
|
+
|
|
1262
|
+
for (let colIndex = 0; colIndex < colCount; colIndex += 1) {
|
|
1263
|
+
const isHeader = rowIndex === 0
|
|
1264
|
+
const cell = isHeader ? table.header[colIndex] : rowsToRender[rowIndex - 1]?.[colIndex]
|
|
1265
|
+
const cellKey = this.getTableCellKey(cell, isHeader)
|
|
1266
|
+
rowKeys[colIndex] = cellKey
|
|
1267
|
+
|
|
1268
|
+
const previousCellKey = previous?.cellKeys[rowIndex]?.[colIndex]
|
|
1269
|
+
const previousCellContent = previous?.content[rowIndex]?.[colIndex]
|
|
1270
|
+
|
|
1271
|
+
if (!forceRegenerate && previousCellKey === cellKey && Array.isArray(previousCellContent)) {
|
|
1272
|
+
rowContent.push(previousCellContent)
|
|
1273
|
+
continue
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
changed = true
|
|
1277
|
+
rowContent.push(
|
|
1278
|
+
isHeader ? this.createTableHeaderCellChunks(table.header[colIndex]) : this.createTableDataCellChunks(cell),
|
|
1279
|
+
)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
content.push(rowContent)
|
|
1283
|
+
cellKeys.push(rowKeys)
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
if (previous && !changed) {
|
|
1287
|
+
if (previous.content.length !== content.length) {
|
|
1288
|
+
changed = true
|
|
1289
|
+
} else {
|
|
1290
|
+
for (let rowIndex = 0; rowIndex < content.length; rowIndex += 1) {
|
|
1291
|
+
if ((previous.content[rowIndex]?.length ?? 0) !== content[rowIndex].length) {
|
|
1292
|
+
changed = true
|
|
1293
|
+
break
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
cache: {
|
|
1301
|
+
content,
|
|
1302
|
+
cellKeys,
|
|
1303
|
+
},
|
|
1304
|
+
changed,
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
private resolveTableStyle(options: MarkdownTableOptions | undefined = this._tableOptions): MarkdownTableStyle {
|
|
1309
|
+
if (options?.style === "columns") {
|
|
1310
|
+
return "columns"
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (options?.style === "grid") {
|
|
1314
|
+
return "grid"
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
return this._internalBlockMode === "top-level" ? "columns" : "grid"
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
private usesBorderlessColumnSpacing(options: MarkdownTableOptions | undefined = this._tableOptions): boolean {
|
|
1321
|
+
const style = this.resolveTableStyle(options)
|
|
1322
|
+
const borders = options?.borders ?? style === "grid"
|
|
1323
|
+
|
|
1324
|
+
return style === "columns" && !borders
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
private resolveTableRenderableOptions(): ResolvedTableRenderableOptions {
|
|
1328
|
+
const style = this.resolveTableStyle()
|
|
1329
|
+
const borders = this._tableOptions?.borders ?? style === "grid"
|
|
1330
|
+
|
|
1331
|
+
return {
|
|
1332
|
+
columnWidthMode: this._tableOptions?.widthMode ?? (style === "columns" ? "content" : "full"),
|
|
1333
|
+
columnFitter: this._tableOptions?.columnFitter ?? "proportional",
|
|
1334
|
+
wrapMode: this._tableOptions?.wrapMode ?? "word",
|
|
1335
|
+
cellPadding: this._tableOptions?.cellPadding ?? 0,
|
|
1336
|
+
cellPaddingX: this._tableOptions?.cellPaddingX ?? this._tableOptions?.cellPadding ?? 0,
|
|
1337
|
+
cellPaddingY: this._tableOptions?.cellPaddingY ?? this._tableOptions?.cellPadding ?? 0,
|
|
1338
|
+
columnGap: this.usesBorderlessColumnSpacing() ? 2 : 0,
|
|
1339
|
+
border: borders,
|
|
1340
|
+
outerBorder: this._tableOptions?.outerBorder ?? borders,
|
|
1341
|
+
showBorders: borders,
|
|
1342
|
+
borderStyle: this._tableOptions?.borderStyle ?? "single",
|
|
1343
|
+
borderColor: this._tableOptions?.borderColor ?? this.getStyle("conceal")?.fg ?? "#888888",
|
|
1344
|
+
selectable: this._tableOptions?.selectable ?? true,
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
private applyTableRenderableOptions(
|
|
1349
|
+
tableRenderable: TextTableRenderable,
|
|
1350
|
+
options: ResolvedTableRenderableOptions,
|
|
1351
|
+
): void {
|
|
1352
|
+
tableRenderable.columnWidthMode = options.columnWidthMode
|
|
1353
|
+
tableRenderable.columnFitter = options.columnFitter
|
|
1354
|
+
tableRenderable.wrapMode = options.wrapMode
|
|
1355
|
+
tableRenderable.cellPaddingX = options.cellPaddingX
|
|
1356
|
+
tableRenderable.cellPaddingY = options.cellPaddingY
|
|
1357
|
+
tableRenderable.columnGap = options.columnGap
|
|
1358
|
+
tableRenderable.border = options.border
|
|
1359
|
+
tableRenderable.outerBorder = options.outerBorder
|
|
1360
|
+
tableRenderable.showBorders = options.showBorders
|
|
1361
|
+
tableRenderable.borderStyle = options.borderStyle
|
|
1362
|
+
tableRenderable.borderColor = options.borderColor
|
|
1363
|
+
tableRenderable.selectable = options.selectable
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
private applyTableOptionsToBlocks(): void {
|
|
1367
|
+
const options = this.resolveTableRenderableOptions()
|
|
1368
|
+
let updated = false
|
|
1369
|
+
|
|
1370
|
+
for (const state of this._blockStates) {
|
|
1371
|
+
if (state.renderable instanceof TextTableRenderable) {
|
|
1372
|
+
this.applyTableRenderableOptions(state.renderable, options)
|
|
1373
|
+
updated = true
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (updated) {
|
|
1378
|
+
this.requestRender()
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
private createTextTableRenderable(
|
|
1383
|
+
content: TextTableContent,
|
|
1384
|
+
id: string,
|
|
1385
|
+
marginBottom: number = 0,
|
|
1386
|
+
): TextTableRenderable {
|
|
1387
|
+
const options = this.resolveTableRenderableOptions()
|
|
1388
|
+
return new TextTableRenderable(this.ctx, {
|
|
1389
|
+
id,
|
|
1390
|
+
content,
|
|
1391
|
+
width: "100%",
|
|
1392
|
+
marginBottom,
|
|
1393
|
+
columnWidthMode: options.columnWidthMode,
|
|
1394
|
+
columnFitter: options.columnFitter,
|
|
1395
|
+
wrapMode: options.wrapMode,
|
|
1396
|
+
cellPadding: options.cellPadding,
|
|
1397
|
+
cellPaddingX: options.cellPaddingX,
|
|
1398
|
+
cellPaddingY: options.cellPaddingY,
|
|
1399
|
+
columnGap: options.columnGap,
|
|
1400
|
+
border: options.border,
|
|
1401
|
+
outerBorder: options.outerBorder,
|
|
1402
|
+
showBorders: options.showBorders,
|
|
1403
|
+
borderStyle: options.borderStyle,
|
|
1404
|
+
borderColor: options.borderColor,
|
|
1405
|
+
selectable: options.selectable,
|
|
1406
|
+
})
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
private createTableBlock(
|
|
1410
|
+
table: Tokens.Table,
|
|
1411
|
+
id: string,
|
|
1412
|
+
marginBottom: number = 0,
|
|
1413
|
+
previousCache?: TableContentCache,
|
|
1414
|
+
forceRegenerate: boolean = false,
|
|
1415
|
+
): { renderable: Renderable; tableContentCache?: TableContentCache } {
|
|
1416
|
+
const { cache } = this.buildTableContentCache(table, previousCache, forceRegenerate)
|
|
1417
|
+
|
|
1418
|
+
if (!cache) {
|
|
1419
|
+
return {
|
|
1420
|
+
renderable: this.createMarkdownCodeRenderable(table.raw, id, marginBottom),
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
return {
|
|
1425
|
+
renderable: this.createTextTableRenderable(cache.content, id, marginBottom),
|
|
1426
|
+
tableContentCache: cache,
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
private getStableBlockCount(blocks: MarkdownRenderBlock[], stableTokenCount: number): number {
|
|
1431
|
+
if (this._internalBlockMode !== "top-level") {
|
|
1432
|
+
return 0
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
let stableBlockCount = 0
|
|
1436
|
+
for (const block of blocks) {
|
|
1437
|
+
if (block.sourceTokenEnd <= stableTokenCount) {
|
|
1438
|
+
stableBlockCount += 1
|
|
1439
|
+
continue
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
break
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
return stableBlockCount
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
private syncTopLevelBlockState(
|
|
1449
|
+
state: BlockState,
|
|
1450
|
+
block: MarkdownRenderBlock,
|
|
1451
|
+
tableContentCache: TableContentCache | undefined = state.tableContentCache,
|
|
1452
|
+
): void {
|
|
1453
|
+
state.token = block.token
|
|
1454
|
+
state.tokenRaw = block.token.raw
|
|
1455
|
+
state.marginTop = block.marginTop
|
|
1456
|
+
state.tableContentCache = tableContentCache
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
private getTopLevelBlockRaw(token: MarkedToken): string | undefined {
|
|
1460
|
+
if (!token.raw) {
|
|
1461
|
+
return undefined
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
return this.shouldRenderSeparately(token) ? token.raw : this.normalizeScrollbackMarkdownBlockRaw(token.raw)
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
private createTopLevelDefaultRenderable(
|
|
1468
|
+
block: MarkdownRenderBlock,
|
|
1469
|
+
index: number,
|
|
1470
|
+
): { renderable: Renderable | undefined; tableContentCache?: TableContentCache; canUpdateInPlace: boolean } {
|
|
1471
|
+
const { token, marginTop } = block
|
|
1472
|
+
const id = `${this.id}-block-${index}`
|
|
1473
|
+
|
|
1474
|
+
if (token.type === "code") {
|
|
1475
|
+
const renderable = this.createCodeRenderable(token, id)
|
|
1476
|
+
renderable.marginTop = marginTop
|
|
1477
|
+
return { renderable, canUpdateInPlace: true }
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (token.type === "table") {
|
|
1481
|
+
const next = this.createTableBlock(token, id)
|
|
1482
|
+
next.renderable.marginTop = marginTop
|
|
1483
|
+
return { ...next, canUpdateInPlace: true }
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (token.type === "blockquote") {
|
|
1487
|
+
const renderable = this.createBlockquoteRenderable(token, id)
|
|
1488
|
+
renderable.marginTop = marginTop
|
|
1489
|
+
return { renderable, canUpdateInPlace: true }
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (token.type === "list") {
|
|
1493
|
+
const renderable = this.createListRenderable(token, id)
|
|
1494
|
+
renderable.marginTop = marginTop
|
|
1495
|
+
return { renderable, canUpdateInPlace: true }
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
if (token.type === "hr") {
|
|
1499
|
+
const renderable = this.createHorizontalRuleRenderable(id)
|
|
1500
|
+
renderable.marginTop = marginTop
|
|
1501
|
+
return { renderable, canUpdateInPlace: true }
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const markdownRaw = this.getTopLevelBlockRaw(token)
|
|
1505
|
+
if (!markdownRaw) {
|
|
1506
|
+
return { renderable: undefined, canUpdateInPlace: true }
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const renderable = this.createMarkdownCodeRenderable(
|
|
1510
|
+
markdownRaw,
|
|
1511
|
+
id,
|
|
1512
|
+
0,
|
|
1513
|
+
this._linkifyMarkdownChunks,
|
|
1514
|
+
undefined,
|
|
1515
|
+
this.createInitialStyledText(token),
|
|
1516
|
+
)
|
|
1517
|
+
renderable.marginTop = marginTop
|
|
1518
|
+
return { renderable, canUpdateInPlace: true }
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
private createTopLevelRenderable(
|
|
1522
|
+
block: MarkdownRenderBlock,
|
|
1523
|
+
index: number,
|
|
1524
|
+
): { renderable: Renderable | undefined; tableContentCache?: TableContentCache; canUpdateInPlace: boolean } {
|
|
1525
|
+
if (!this._renderNode) {
|
|
1526
|
+
return this.createTopLevelDefaultRenderable(block, index)
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const custom = this.createTopLevelCustomRenderable(block, index)
|
|
1530
|
+
if (!custom.renderable) return this.createTopLevelDefaultRenderable(block, index)
|
|
1531
|
+
|
|
1532
|
+
const marginTop =
|
|
1533
|
+
typeof custom.renderable.marginTop === "number"
|
|
1534
|
+
? Math.max(custom.renderable.marginTop, block.marginTop)
|
|
1535
|
+
: block.marginTop
|
|
1536
|
+
this.applyMargins(custom.renderable, marginTop, 0)
|
|
1537
|
+
|
|
1538
|
+
return {
|
|
1539
|
+
renderable: custom.renderable,
|
|
1540
|
+
tableContentCache: custom.tableContentCache,
|
|
1541
|
+
canUpdateInPlace: custom.canUpdateInPlace,
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
private createDefaultRenderable(token: MarkedToken, index: number, nextToken?: MarkedToken): Renderable | null {
|
|
1546
|
+
const id = `${this.id}-block-${index}`
|
|
1547
|
+
const marginBottom = this.getInterBlockMargin(token, nextToken)
|
|
1548
|
+
|
|
1549
|
+
if (token.type === "code") {
|
|
1550
|
+
return this.createCodeRenderable(token, id, marginBottom)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
if (token.type === "blockquote") {
|
|
1554
|
+
return this.createBlockquoteRenderable(token, id, marginBottom)
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
if (token.type === "list") {
|
|
1558
|
+
return this.createListRenderable(token as Tokens.List, id, marginBottom)
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
if (token.type === "hr") {
|
|
1562
|
+
return this.createHorizontalRuleRenderable(id, marginBottom)
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (token.type === "table") {
|
|
1566
|
+
return this.createTableBlock(token, id, marginBottom).renderable
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
if (token.type === "space") {
|
|
1570
|
+
return null
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (!token.raw) {
|
|
1574
|
+
return null
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
return this.createMarkdownCodeRenderable(
|
|
1578
|
+
token.raw,
|
|
1579
|
+
id,
|
|
1580
|
+
marginBottom,
|
|
1581
|
+
this._linkifyMarkdownChunks,
|
|
1582
|
+
undefined,
|
|
1583
|
+
this.createInitialStyledText(token),
|
|
1584
|
+
)
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
private createCustomRenderable(
|
|
1588
|
+
token: MarkedToken,
|
|
1589
|
+
index: number,
|
|
1590
|
+
nextToken: MarkedToken | undefined,
|
|
1591
|
+
): CustomRenderableResult {
|
|
1592
|
+
const custom = this.renderCustomNode(token, () => {
|
|
1593
|
+
return { renderable: this.createDefaultRenderable(token, index, nextToken) }
|
|
1594
|
+
})
|
|
1595
|
+
if (!custom.renderable) {
|
|
1596
|
+
return { tracksInterBlockMargin: true, canUpdateInPlace: true }
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const canUpdateInPlace = custom.renderable === custom.defaultResult?.renderable
|
|
1600
|
+
|
|
1601
|
+
return {
|
|
1602
|
+
renderable: custom.renderable,
|
|
1603
|
+
tracksInterBlockMargin: canUpdateInPlace,
|
|
1604
|
+
canUpdateInPlace,
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
private createTopLevelCustomRenderable(block: MarkdownRenderBlock, index: number): CustomRenderableResult {
|
|
1609
|
+
const custom = this.renderCustomNode(block.token, () => {
|
|
1610
|
+
return this.createTopLevelDefaultRenderable(block, index)
|
|
1611
|
+
})
|
|
1612
|
+
if (!custom.renderable) {
|
|
1613
|
+
return { tracksInterBlockMargin: true, canUpdateInPlace: true }
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const canUpdateInPlace = custom.renderable === custom.defaultResult?.renderable
|
|
1617
|
+
|
|
1618
|
+
return {
|
|
1619
|
+
renderable: custom.renderable,
|
|
1620
|
+
tableContentCache: canUpdateInPlace ? custom.defaultResult?.tableContentCache : undefined,
|
|
1621
|
+
tracksInterBlockMargin: canUpdateInPlace,
|
|
1622
|
+
canUpdateInPlace,
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
private renderCustomNode(token: MarkedToken, createDefault: () => CustomRenderDefaultResult): RenderNodeResult {
|
|
1627
|
+
if (!this._renderNode) return {}
|
|
1628
|
+
|
|
1629
|
+
let defaultResult: CustomRenderDefaultResult | undefined
|
|
1630
|
+
const custom = this._renderNode(token, {
|
|
1631
|
+
syntaxStyle: this._syntaxStyle,
|
|
1632
|
+
conceal: this._conceal,
|
|
1633
|
+
concealCode: this._concealCode,
|
|
1634
|
+
treeSitterClient: this._treeSitterClient,
|
|
1635
|
+
defaultRender: () => {
|
|
1636
|
+
defaultResult = createDefault()
|
|
1637
|
+
return defaultResult.renderable ?? null
|
|
1638
|
+
},
|
|
1639
|
+
})
|
|
1640
|
+
|
|
1641
|
+
this.destroyUnusedDefaultRenderable(defaultResult?.renderable, custom ?? undefined)
|
|
1642
|
+
|
|
1643
|
+
return custom ? { renderable: custom, defaultResult } : {}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
private destroyUnusedDefaultRenderable(renderable: Renderable | null | undefined, usedRenderable?: Renderable): void {
|
|
1647
|
+
if (!renderable || renderable === usedRenderable || renderable.parent) return
|
|
1648
|
+
renderable.destroyRecursively()
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
private updateBlockRenderable(
|
|
1652
|
+
state: BlockState,
|
|
1653
|
+
token: MarkedToken,
|
|
1654
|
+
index: number,
|
|
1655
|
+
nextToken: MarkedToken | undefined,
|
|
1656
|
+
forceListRefresh: boolean = false,
|
|
1657
|
+
): void {
|
|
1658
|
+
const marginBottom = this.getInterBlockMargin(token, nextToken)
|
|
1659
|
+
|
|
1660
|
+
if (token.type === "code") {
|
|
1661
|
+
this.applyCodeBlockRenderable(state.renderable, token as Tokens.Code, marginBottom)
|
|
1662
|
+
return
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (token.type === "blockquote") {
|
|
1666
|
+
this.applyBlockquoteRenderable(state.renderable, token, marginBottom)
|
|
1667
|
+
return
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (token.type === "list") {
|
|
1671
|
+
if (
|
|
1672
|
+
!this.applyListRenderable(
|
|
1673
|
+
state.renderable,
|
|
1674
|
+
token as Tokens.List,
|
|
1675
|
+
forceListRefresh ? undefined : (state.token as Tokens.List),
|
|
1676
|
+
`${this.id}-block-${index}`,
|
|
1677
|
+
marginBottom,
|
|
1678
|
+
)
|
|
1679
|
+
) {
|
|
1680
|
+
state.renderable.destroyRecursively()
|
|
1681
|
+
state.renderable = this.createListRenderable(token as Tokens.List, `${this.id}-block-${index}`, marginBottom)
|
|
1682
|
+
this.add(state.renderable, index)
|
|
1683
|
+
}
|
|
1684
|
+
return
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
if (token.type === "hr") {
|
|
1688
|
+
state.renderable.marginBottom = marginBottom
|
|
1689
|
+
return
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (token.type === "table") {
|
|
1693
|
+
const tableToken = token as Tokens.Table
|
|
1694
|
+
const { cache, changed } = this.buildTableContentCache(tableToken, state.tableContentCache)
|
|
1695
|
+
|
|
1696
|
+
if (!cache) {
|
|
1697
|
+
if (state.renderable instanceof CodeRenderable) {
|
|
1698
|
+
this.applyMarkdownCodeRenderable(state.renderable, tableToken.raw, marginBottom)
|
|
1699
|
+
state.tableContentCache = undefined
|
|
1700
|
+
return
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
state.renderable.destroyRecursively()
|
|
1704
|
+
const fallbackRenderable = this.createMarkdownCodeRenderable(
|
|
1705
|
+
tableToken.raw,
|
|
1706
|
+
`${this.id}-block-${index}`,
|
|
1707
|
+
marginBottom,
|
|
1708
|
+
)
|
|
1709
|
+
this.add(fallbackRenderable, index)
|
|
1710
|
+
state.renderable = fallbackRenderable
|
|
1711
|
+
state.tableContentCache = undefined
|
|
1712
|
+
return
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (state.renderable instanceof TextTableRenderable) {
|
|
1716
|
+
if (changed) {
|
|
1717
|
+
state.renderable.content = cache.content
|
|
1718
|
+
}
|
|
1719
|
+
this.applyTableRenderableOptions(state.renderable, this.resolveTableRenderableOptions())
|
|
1720
|
+
state.renderable.marginBottom = marginBottom
|
|
1721
|
+
state.tableContentCache = cache
|
|
1722
|
+
return
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
state.renderable.destroyRecursively()
|
|
1726
|
+
const tableRenderable = this.createTextTableRenderable(cache.content, `${this.id}-block-${index}`, marginBottom)
|
|
1727
|
+
this.add(tableRenderable, index)
|
|
1728
|
+
state.renderable = tableRenderable
|
|
1729
|
+
state.tableContentCache = cache
|
|
1730
|
+
return
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (state.renderable instanceof CodeRenderable) {
|
|
1734
|
+
this.applyMarkdownCodeRenderable(
|
|
1735
|
+
state.renderable,
|
|
1736
|
+
this.getTopLevelBlockRaw(token) ?? token.raw,
|
|
1737
|
+
marginBottom,
|
|
1738
|
+
undefined,
|
|
1739
|
+
this.createInitialStyledText(token),
|
|
1740
|
+
)
|
|
1741
|
+
return
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
state.renderable.destroyRecursively()
|
|
1745
|
+
const markdownRenderable = this.createMarkdownCodeRenderable(
|
|
1746
|
+
this.getTopLevelBlockRaw(token) ?? token.raw,
|
|
1747
|
+
`${this.id}-block-${index}`,
|
|
1748
|
+
marginBottom,
|
|
1749
|
+
this._linkifyMarkdownChunks,
|
|
1750
|
+
undefined,
|
|
1751
|
+
this.createInitialStyledText(token),
|
|
1752
|
+
)
|
|
1753
|
+
this.add(markdownRenderable, index)
|
|
1754
|
+
state.renderable = markdownRenderable
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
private updateTopLevelBlocks(tokens: MarkedToken[], forceTableRefresh: boolean): void {
|
|
1758
|
+
const blocks = this.buildTopLevelRenderBlocks(tokens)
|
|
1759
|
+
this._stableBlockCount = this.getStableBlockCount(blocks, this._parseState?.stableTokenCount ?? 0)
|
|
1760
|
+
|
|
1761
|
+
let blockIndex = 0
|
|
1762
|
+
for (let i = 0; i < blocks.length; i += 1) {
|
|
1763
|
+
const block = blocks[i]
|
|
1764
|
+
const existing = this._blockStates[blockIndex]
|
|
1765
|
+
|
|
1766
|
+
if (existing && existing.token === block.token && !forceTableRefresh) {
|
|
1767
|
+
if (existing.marginTop !== block.marginTop) {
|
|
1768
|
+
this.applyMargins(existing.renderable, block.marginTop, 0)
|
|
1769
|
+
}
|
|
1770
|
+
this.syncTopLevelBlockState(existing, block)
|
|
1771
|
+
blockIndex++
|
|
1772
|
+
continue
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
if (
|
|
1776
|
+
existing &&
|
|
1777
|
+
existing.tokenRaw === block.token.raw &&
|
|
1778
|
+
existing.token.type === block.token.type &&
|
|
1779
|
+
!forceTableRefresh
|
|
1780
|
+
) {
|
|
1781
|
+
if (existing.marginTop !== block.marginTop) {
|
|
1782
|
+
this.applyMargins(existing.renderable, block.marginTop, 0)
|
|
1783
|
+
}
|
|
1784
|
+
this.syncTopLevelBlockState(existing, block)
|
|
1785
|
+
blockIndex++
|
|
1786
|
+
continue
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (
|
|
1790
|
+
existing &&
|
|
1791
|
+
!forceTableRefresh &&
|
|
1792
|
+
existing.canUpdateInPlace &&
|
|
1793
|
+
existing.token.type === block.token.type &&
|
|
1794
|
+
this.canUpdateBlockRenderable(existing.renderable, block.token)
|
|
1795
|
+
) {
|
|
1796
|
+
if (this._renderNode) {
|
|
1797
|
+
const custom = this.createTopLevelCustomRenderable(block, blockIndex)
|
|
1798
|
+
if (custom.renderable && !custom.canUpdateInPlace) {
|
|
1799
|
+
const marginTop =
|
|
1800
|
+
typeof custom.renderable.marginTop === "number"
|
|
1801
|
+
? Math.max(custom.renderable.marginTop, block.marginTop)
|
|
1802
|
+
: block.marginTop
|
|
1803
|
+
this.applyMargins(custom.renderable, marginTop, 0)
|
|
1804
|
+
if (custom.renderable !== existing.renderable) {
|
|
1805
|
+
existing.renderable.destroyRecursively()
|
|
1806
|
+
this.add(custom.renderable, blockIndex)
|
|
1807
|
+
}
|
|
1808
|
+
this._blockStates[blockIndex] = {
|
|
1809
|
+
token: block.token,
|
|
1810
|
+
tokenRaw: block.token.raw,
|
|
1811
|
+
marginTop: block.marginTop,
|
|
1812
|
+
renderable: custom.renderable,
|
|
1813
|
+
tableContentCache: custom.tableContentCache,
|
|
1814
|
+
canUpdateInPlace: custom.canUpdateInPlace,
|
|
1815
|
+
}
|
|
1816
|
+
blockIndex++
|
|
1817
|
+
continue
|
|
1818
|
+
}
|
|
1819
|
+
this.destroyUnusedDefaultRenderable(custom.renderable)
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
this.updateBlockRenderable(existing, block.token, blockIndex, blocks[i + 1]?.token)
|
|
1823
|
+
existing.renderable.marginBottom = 0
|
|
1824
|
+
if (existing.marginTop !== block.marginTop) {
|
|
1825
|
+
this.applyMargins(existing.renderable, block.marginTop, 0)
|
|
1826
|
+
}
|
|
1827
|
+
this.syncTopLevelBlockState(existing, block)
|
|
1828
|
+
blockIndex++
|
|
1829
|
+
continue
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
if (existing) {
|
|
1833
|
+
existing.renderable.destroyRecursively()
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
const next = this.createTopLevelRenderable(block, blockIndex)
|
|
1837
|
+
if (next.renderable) {
|
|
1838
|
+
this.add(next.renderable, blockIndex)
|
|
1839
|
+
this._blockStates[blockIndex] = {
|
|
1840
|
+
token: block.token,
|
|
1841
|
+
tokenRaw: block.token.raw,
|
|
1842
|
+
marginTop: block.marginTop,
|
|
1843
|
+
renderable: next.renderable,
|
|
1844
|
+
tableContentCache: next.tableContentCache,
|
|
1845
|
+
canUpdateInPlace: next.canUpdateInPlace,
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
blockIndex++
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
while (this._blockStates.length > blockIndex) {
|
|
1852
|
+
const removed = this._blockStates.pop()!
|
|
1853
|
+
removed.renderable.destroyRecursively()
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
private canUpdateBlockRenderable(renderable: Renderable, token: MarkedToken): boolean {
|
|
1858
|
+
if (token.type === "code") return renderable instanceof CodeRenderable
|
|
1859
|
+
|
|
1860
|
+
if (token.type === "table") return renderable instanceof TextTableRenderable
|
|
1861
|
+
if (token.type === "blockquote") return renderable instanceof BoxRenderable
|
|
1862
|
+
if (token.type === "list") return renderable instanceof BoxRenderable
|
|
1863
|
+
if (token.type === "hr") return renderable instanceof BoxRenderable
|
|
1864
|
+
return renderable instanceof CodeRenderable
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
private updateBlocks(forceTableRefresh: boolean = false): void {
|
|
1868
|
+
if (this.isDestroyed) return
|
|
1869
|
+
if (!this._content) {
|
|
1870
|
+
this.clearBlockStates()
|
|
1871
|
+
this._parseState = null
|
|
1872
|
+
this._stableBlockCount = 0
|
|
1873
|
+
return
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
const trailingUnstable = this._streaming ? 2 : 0
|
|
1877
|
+
this._parseState = parseMarkdownIncremental(this._content, this._parseState, trailingUnstable)
|
|
1878
|
+
|
|
1879
|
+
const tokens = this._parseState.tokens
|
|
1880
|
+
|
|
1881
|
+
if (tokens.length === 0 && this._content.length > 0) {
|
|
1882
|
+
this.clearBlockStates()
|
|
1883
|
+
this._stableBlockCount = 0
|
|
1884
|
+
const fallback = this.createMarkdownCodeRenderable(this._content, `${this.id}-fallback`)
|
|
1885
|
+
this.add(fallback)
|
|
1886
|
+
this._blockStates = [
|
|
1887
|
+
{
|
|
1888
|
+
token: { type: "text", raw: this._content, text: this._content } as MarkedToken,
|
|
1889
|
+
tokenRaw: this._content,
|
|
1890
|
+
marginTop: 0,
|
|
1891
|
+
renderable: fallback,
|
|
1892
|
+
tracksInterBlockMargin: true,
|
|
1893
|
+
canUpdateInPlace: true,
|
|
1894
|
+
},
|
|
1895
|
+
]
|
|
1896
|
+
return
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
if (this._internalBlockMode === "top-level") {
|
|
1900
|
+
this.updateTopLevelBlocks(tokens, forceTableRefresh)
|
|
1901
|
+
return
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
this._stableBlockCount = 0
|
|
1905
|
+
const blockTokens = this.buildRenderableTokens(tokens)
|
|
1906
|
+
let blockIndex = 0
|
|
1907
|
+
for (let i = 0; i < blockTokens.length; i++) {
|
|
1908
|
+
const token = blockTokens[i]
|
|
1909
|
+
const nextToken = blockTokens[i + 1]
|
|
1910
|
+
const existing = this._blockStates[blockIndex]
|
|
1911
|
+
|
|
1912
|
+
const shouldForceRefresh = forceTableRefresh
|
|
1913
|
+
|
|
1914
|
+
if (existing && existing.token === token) {
|
|
1915
|
+
if (shouldForceRefresh) {
|
|
1916
|
+
this.updateBlockRenderable(existing, token, blockIndex, nextToken)
|
|
1917
|
+
existing.tokenRaw = token.raw
|
|
1918
|
+
} else {
|
|
1919
|
+
this.applyInterBlockMargin(existing, token, nextToken)
|
|
1920
|
+
}
|
|
1921
|
+
blockIndex++
|
|
1922
|
+
continue
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
if (existing && existing.tokenRaw === token.raw && existing.token.type === token.type) {
|
|
1926
|
+
existing.token = token
|
|
1927
|
+
if (shouldForceRefresh) {
|
|
1928
|
+
this.updateBlockRenderable(existing, token, blockIndex, nextToken)
|
|
1929
|
+
existing.tokenRaw = token.raw
|
|
1930
|
+
} else {
|
|
1931
|
+
this.applyInterBlockMargin(existing, token, nextToken)
|
|
1932
|
+
}
|
|
1933
|
+
blockIndex++
|
|
1934
|
+
continue
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
if (existing && existing.canUpdateInPlace && existing.token.type === token.type) {
|
|
1938
|
+
const custom = this.createCustomRenderable(token, blockIndex, nextToken)
|
|
1939
|
+
if (custom.renderable && !custom.canUpdateInPlace) {
|
|
1940
|
+
if (custom.renderable !== existing.renderable) {
|
|
1941
|
+
existing.renderable.destroyRecursively()
|
|
1942
|
+
this.add(custom.renderable, blockIndex)
|
|
1943
|
+
}
|
|
1944
|
+
this._blockStates[blockIndex] = {
|
|
1945
|
+
token,
|
|
1946
|
+
tokenRaw: token.raw,
|
|
1947
|
+
renderable: custom.renderable,
|
|
1948
|
+
tracksInterBlockMargin: custom.tracksInterBlockMargin,
|
|
1949
|
+
canUpdateInPlace: custom.canUpdateInPlace,
|
|
1950
|
+
}
|
|
1951
|
+
blockIndex++
|
|
1952
|
+
continue
|
|
1953
|
+
}
|
|
1954
|
+
this.destroyUnusedDefaultRenderable(custom.renderable)
|
|
1955
|
+
|
|
1956
|
+
this.updateBlockRenderable(existing, token, blockIndex, nextToken)
|
|
1957
|
+
existing.token = token
|
|
1958
|
+
existing.tokenRaw = token.raw
|
|
1959
|
+
existing.tracksInterBlockMargin = true
|
|
1960
|
+
blockIndex++
|
|
1961
|
+
continue
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
if (existing) {
|
|
1965
|
+
existing.renderable.destroyRecursively()
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
let renderable: Renderable | undefined
|
|
1969
|
+
let tableContentCache: TableContentCache | undefined
|
|
1970
|
+
let tracksInterBlockMargin = true
|
|
1971
|
+
let canUpdateInPlace = true
|
|
1972
|
+
|
|
1973
|
+
const custom = this.createCustomRenderable(token, blockIndex, nextToken)
|
|
1974
|
+
if (custom.renderable) {
|
|
1975
|
+
renderable = custom.renderable
|
|
1976
|
+
tracksInterBlockMargin = custom.tracksInterBlockMargin
|
|
1977
|
+
canUpdateInPlace = custom.canUpdateInPlace
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
if (!renderable) {
|
|
1981
|
+
if (token.type === "table") {
|
|
1982
|
+
const tableBlock = this.createTableBlock(
|
|
1983
|
+
token,
|
|
1984
|
+
`${this.id}-block-${blockIndex}`,
|
|
1985
|
+
this.getInterBlockMargin(token, nextToken),
|
|
1986
|
+
)
|
|
1987
|
+
renderable = tableBlock.renderable
|
|
1988
|
+
tableContentCache = tableBlock.tableContentCache
|
|
1989
|
+
} else {
|
|
1990
|
+
renderable = this.createDefaultRenderable(token, blockIndex, nextToken) ?? undefined
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
if (token.type === "table" && !tableContentCache && renderable instanceof TextTableRenderable) {
|
|
1995
|
+
const { cache } = this.buildTableContentCache(token as Tokens.Table)
|
|
1996
|
+
tableContentCache = cache ?? undefined
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
if (renderable) {
|
|
2000
|
+
this.add(renderable, blockIndex)
|
|
2001
|
+
this._blockStates[blockIndex] = {
|
|
2002
|
+
token,
|
|
2003
|
+
tokenRaw: token.raw,
|
|
2004
|
+
renderable,
|
|
2005
|
+
tableContentCache,
|
|
2006
|
+
tracksInterBlockMargin,
|
|
2007
|
+
canUpdateInPlace,
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
blockIndex++
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
while (this._blockStates.length > blockIndex) {
|
|
2014
|
+
const removed = this._blockStates.pop()!
|
|
2015
|
+
removed.renderable.destroyRecursively()
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
private clearBlockStates(): void {
|
|
2020
|
+
for (const state of this._blockStates) {
|
|
2021
|
+
state.renderable.destroyRecursively()
|
|
2022
|
+
}
|
|
2023
|
+
this._blockStates = []
|
|
2024
|
+
this._stableBlockCount = 0
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* Re-render existing blocks without rebuilding the parse state or block structure.
|
|
2029
|
+
* Used when only style/conceal changes - much faster than full rebuild.
|
|
2030
|
+
*/
|
|
2031
|
+
private rerenderBlocks(): void {
|
|
2032
|
+
if (this._internalBlockMode === "top-level") {
|
|
2033
|
+
this.updateBlocks(true)
|
|
2034
|
+
return
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
for (let i = 0; i < this._blockStates.length; i++) {
|
|
2038
|
+
const state = this._blockStates[i]
|
|
2039
|
+
const marginBottom = this.getInterBlockMargin(state.token, this._blockStates[i + 1]?.token)
|
|
2040
|
+
|
|
2041
|
+
if (state.token.type === "code") {
|
|
2042
|
+
this.applyCodeBlockRenderable(state.renderable, state.token as Tokens.Code, marginBottom)
|
|
2043
|
+
continue
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
if (state.token.type === "blockquote") {
|
|
2047
|
+
this.applyBlockquoteRenderable(state.renderable, state.token, marginBottom)
|
|
2048
|
+
continue
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (state.token.type === "list") {
|
|
2052
|
+
this.updateBlockRenderable(state, state.token, i, this._blockStates[i + 1]?.token, true)
|
|
2053
|
+
continue
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
if (state.token.type === "hr") {
|
|
2057
|
+
state.renderable.marginBottom = marginBottom
|
|
2058
|
+
continue
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
if (state.token.type === "table") {
|
|
2062
|
+
const tableToken = state.token as Tokens.Table
|
|
2063
|
+
const { cache } = this.buildTableContentCache(tableToken, state.tableContentCache, true)
|
|
2064
|
+
|
|
2065
|
+
if (!cache) {
|
|
2066
|
+
if (state.renderable instanceof CodeRenderable) {
|
|
2067
|
+
this.applyMarkdownCodeRenderable(state.renderable, tableToken.raw, marginBottom)
|
|
2068
|
+
} else {
|
|
2069
|
+
state.renderable.destroyRecursively()
|
|
2070
|
+
const fallbackRenderable = this.createMarkdownCodeRenderable(
|
|
2071
|
+
tableToken.raw,
|
|
2072
|
+
`${this.id}-block-${i}`,
|
|
2073
|
+
marginBottom,
|
|
2074
|
+
)
|
|
2075
|
+
this.add(fallbackRenderable, i)
|
|
2076
|
+
state.renderable = fallbackRenderable
|
|
2077
|
+
}
|
|
2078
|
+
state.tableContentCache = undefined
|
|
2079
|
+
continue
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (state.renderable instanceof TextTableRenderable) {
|
|
2083
|
+
state.renderable.content = cache.content
|
|
2084
|
+
this.applyTableRenderableOptions(state.renderable, this.resolveTableRenderableOptions())
|
|
2085
|
+
state.renderable.marginBottom = marginBottom
|
|
2086
|
+
state.tableContentCache = cache
|
|
2087
|
+
continue
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
state.renderable.destroyRecursively()
|
|
2091
|
+
const tableRenderable = this.createTextTableRenderable(cache.content, `${this.id}-block-${i}`, marginBottom)
|
|
2092
|
+
this.add(tableRenderable, i)
|
|
2093
|
+
state.renderable = tableRenderable
|
|
2094
|
+
state.tableContentCache = cache
|
|
2095
|
+
continue
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (state.renderable instanceof CodeRenderable) {
|
|
2099
|
+
this.applyMarkdownCodeRenderable(
|
|
2100
|
+
state.renderable,
|
|
2101
|
+
this.getTopLevelBlockRaw(state.token) ?? state.token.raw,
|
|
2102
|
+
marginBottom,
|
|
2103
|
+
undefined,
|
|
2104
|
+
this.createInitialStyledText(state.token),
|
|
2105
|
+
)
|
|
2106
|
+
continue
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
state.renderable.destroyRecursively()
|
|
2110
|
+
const markdownRenderable = this.createMarkdownCodeRenderable(
|
|
2111
|
+
this.getTopLevelBlockRaw(state.token) ?? state.token.raw,
|
|
2112
|
+
`${this.id}-block-${i}`,
|
|
2113
|
+
marginBottom,
|
|
2114
|
+
this._linkifyMarkdownChunks,
|
|
2115
|
+
undefined,
|
|
2116
|
+
this.createInitialStyledText(state.token),
|
|
2117
|
+
)
|
|
2118
|
+
this.add(markdownRenderable, i)
|
|
2119
|
+
state.renderable = markdownRenderable
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
public clearCache(): void {
|
|
2124
|
+
this._parseState = null
|
|
2125
|
+
this.clearBlockStates()
|
|
2126
|
+
this.updateBlocks()
|
|
2127
|
+
this.requestRender()
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
public refreshStyles(): void {
|
|
2131
|
+
this._styleDirty = false
|
|
2132
|
+
this.rerenderBlocks()
|
|
2133
|
+
this.requestRender()
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void {
|
|
2137
|
+
// Check if style/conceal changed - re-render blocks before rendering
|
|
2138
|
+
if (this._styleDirty) {
|
|
2139
|
+
this._styleDirty = false
|
|
2140
|
+
this.rerenderBlocks()
|
|
2141
|
+
}
|
|
2142
|
+
super.renderSelf(buffer, deltaTime)
|
|
2143
|
+
}
|
|
2144
|
+
}
|