@voithos-labs/aragonite 0.10.1
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/LICENSE +661 -0
- package/README.md +365 -0
- package/THIRD-PARTY-NOTICES.md +102 -0
- package/dist/a11y-strings.d.ts +46 -0
- package/dist/a11y-strings.js +62 -0
- package/dist/action-contracts.d.ts +301 -0
- package/dist/action-contracts.js +5 -0
- package/dist/active-editor.d.ts +16 -0
- package/dist/active-editor.js +67 -0
- package/dist/ambient/ambient-cursor.d.ts +41 -0
- package/dist/ambient/ambient-cursor.js +120 -0
- package/dist/ambient/ambient-dom.d.ts +9 -0
- package/dist/ambient/ambient-dom.js +102 -0
- package/dist/assert.d.ts +6 -0
- package/dist/assert.js +17 -0
- package/dist/block-component.d.ts +274 -0
- package/dist/block-component.js +43 -0
- package/dist/block-id.d.ts +14 -0
- package/dist/block-id.js +34 -0
- package/dist/bounded-memo.d.ts +14 -0
- package/dist/bounded-memo.js +32 -0
- package/dist/components/BlockDragHandle.svelte +66 -0
- package/dist/components/BlockDragHandle.svelte.d.ts +18 -0
- package/dist/components/BlockHost.svelte +328 -0
- package/dist/components/BlockHost.svelte.d.ts +15 -0
- package/dist/components/BlockList.svelte +114 -0
- package/dist/components/BlockList.svelte.d.ts +16 -0
- package/dist/components/DecorationOverlay.svelte +156 -0
- package/dist/components/DecorationOverlay.svelte.d.ts +14 -0
- package/dist/components/Editor.svelte +1849 -0
- package/dist/components/Editor.svelte.d.ts +49 -0
- package/dist/components/GapCaret.svelte +226 -0
- package/dist/components/GapCaret.svelte.d.ts +9 -0
- package/dist/components/SearchBar.svelte +243 -0
- package/dist/components/SearchBar.svelte.d.ts +7 -0
- package/dist/components/SelectionOverlay.svelte +171 -0
- package/dist/components/SelectionOverlay.svelte.d.ts +14 -0
- package/dist/components/block-content-selector.d.ts +12 -0
- package/dist/components/block-content-selector.js +12 -0
- package/dist/components/blocks/BlockquoteBlock.svelte +32 -0
- package/dist/components/blocks/BlockquoteBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/ThematicBreakBlock.svelte +156 -0
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +17 -0
- package/dist/components/blocks/code/CodeBlock.svelte +726 -0
- package/dist/components/blocks/code/CodeBlock.svelte.d.ts +23 -0
- package/dist/components/blocks/code/CodeLanguageChip.svelte +127 -0
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +14 -0
- package/dist/components/blocks/code/code-beforeinput.d.ts +30 -0
- package/dist/components/blocks/code/code-beforeinput.js +40 -0
- package/dist/components/blocks/code/code-bootstrap.d.ts +8 -0
- package/dist/components/blocks/code/code-bootstrap.js +54 -0
- package/dist/components/blocks/code/code-editing.d.ts +28 -0
- package/dist/components/blocks/code/code-editing.js +79 -0
- package/dist/components/blocks/code/code-enter.d.ts +22 -0
- package/dist/components/blocks/code/code-enter.js +19 -0
- package/dist/components/blocks/code/code-fence-boundary.d.ts +77 -0
- package/dist/components/blocks/code/code-fence-boundary.js +169 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +24 -0
- package/dist/components/blocks/code/code-fence-exit.js +36 -0
- package/dist/components/blocks/code/code-indent.d.ts +14 -0
- package/dist/components/blocks/code/code-indent.js +87 -0
- package/dist/components/blocks/code/code-languages.d.ts +15 -0
- package/dist/components/blocks/code/code-languages.js +30 -0
- package/dist/components/blocks/code/code-paste-surface.d.ts +6 -0
- package/dist/components/blocks/code/code-paste-surface.js +29 -0
- package/dist/components/blocks/code/code-paste.d.ts +24 -0
- package/dist/components/blocks/code/code-paste.js +21 -0
- package/dist/components/blocks/code/code-renderer.d.ts +18 -0
- package/dist/components/blocks/code/code-renderer.js +249 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +51 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/directive/activate-directives.d.ts +7 -0
- package/dist/components/blocks/directive/activate-directives.js +23 -0
- package/dist/components/blocks/editable-leaf.d.ts +126 -0
- package/dist/components/blocks/editable-leaf.js +410 -0
- package/dist/components/blocks/editable-surface.d.ts +209 -0
- package/dist/components/blocks/editable-surface.js +331 -0
- package/dist/components/blocks/list/ListBlock.svelte +162 -0
- package/dist/components/blocks/list/ListBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +324 -0
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +14 -0
- package/dist/components/blocks/list/task-checkbox.d.ts +3 -0
- package/dist/components/blocks/list/task-checkbox.js +33 -0
- package/dist/components/blocks/plain-text-backend.d.ts +21 -0
- package/dist/components/blocks/plain-text-backend.js +47 -0
- package/dist/components/blocks/surface-wiring.svelte.d.ts +18 -0
- package/dist/components/blocks/surface-wiring.svelte.js +68 -0
- package/dist/components/blocks/table/TableActionMenu.svelte +267 -0
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +15 -0
- package/dist/components/blocks/table/TableBlock.svelte +765 -0
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +30 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +1075 -0
- package/dist/components/blocks/table/TableCellBlock.svelte.d.ts +30 -0
- package/dist/components/blocks/table/TableGrip.svelte +91 -0
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +8 -0
- package/dist/components/blocks/table/TableRowBlock.svelte +233 -0
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +34 -0
- package/dist/components/blocks/table/cell-clipboard.d.ts +23 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -0
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +37 -0
- package/dist/components/blocks/table/cell-keydown-plan.js +66 -0
- package/dist/components/blocks/table/cell-pointer.d.ts +58 -0
- package/dist/components/blocks/table/cell-pointer.js +153 -0
- package/dist/components/blocks/table/cell-render.d.ts +51 -0
- package/dist/components/blocks/table/cell-render.js +98 -0
- package/dist/components/blocks/table/cell-table-commands.d.ts +12 -0
- package/dist/components/blocks/table/cell-table-commands.js +21 -0
- package/dist/components/blocks/table/cell-x-mapping.d.ts +6 -0
- package/dist/components/blocks/table/cell-x-mapping.js +16 -0
- package/dist/components/blocks/table/selected-cells.d.ts +19 -0
- package/dist/components/blocks/table/selected-cells.js +35 -0
- package/dist/components/blocks/table/table-caret-at-point.d.ts +8 -0
- package/dist/components/blocks/table/table-caret-at-point.js +35 -0
- package/dist/components/blocks/table/table-cell-paste.d.ts +16 -0
- package/dist/components/blocks/table/table-cell-paste.js +75 -0
- package/dist/components/blocks/table/table-drag-hit-test.d.ts +5 -0
- package/dist/components/blocks/table/table-drag-hit-test.js +20 -0
- package/dist/components/blocks/table/table-drop-target.d.ts +1 -0
- package/dist/components/blocks/table/table-drop-target.js +16 -0
- package/dist/components/blocks/table/table-menu-model.d.ts +52 -0
- package/dist/components/blocks/table/table-menu-model.js +78 -0
- package/dist/components/blocks/table/table-navigation.d.ts +8 -0
- package/dist/components/blocks/table/table-navigation.js +22 -0
- package/dist/components/blocks/table/table-reorder-drag.d.ts +78 -0
- package/dist/components/blocks/table/table-reorder-drag.js +97 -0
- package/dist/components/blocks/text/TextEditableBlock.svelte +1012 -0
- package/dist/components/blocks/text/TextEditableBlock.svelte.d.ts +33 -0
- package/dist/components/blocks/text/click-snap-guard.d.ts +8 -0
- package/dist/components/blocks/text/click-snap-guard.js +19 -0
- package/dist/components/blocks/text/composition-seat.d.ts +50 -0
- package/dist/components/blocks/text/composition-seat.js +65 -0
- package/dist/components/blocks/text/construct-edge-delete.d.ts +47 -0
- package/dist/components/blocks/text/construct-edge-delete.js +164 -0
- package/dist/components/blocks/text/construct-reveal.d.ts +39 -0
- package/dist/components/blocks/text/construct-reveal.js +164 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +91 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +457 -0
- package/dist/components/blocks/text/edge-seat.d.ts +42 -0
- package/dist/components/blocks/text/edge-seat.js +197 -0
- package/dist/components/blocks/text/hidden-suffix.d.ts +10 -0
- package/dist/components/blocks/text/hidden-suffix.js +15 -0
- package/dist/components/blocks/text/link-at-point.d.ts +33 -0
- package/dist/components/blocks/text/link-at-point.js +47 -0
- package/dist/components/blocks/text/link-source-bytes.d.ts +37 -0
- package/dist/components/blocks/text/link-source-bytes.js +161 -0
- package/dist/components/blocks/text/live-join-seam.d.ts +15 -0
- package/dist/components/blocks/text/live-join-seam.js +327 -0
- package/dist/components/blocks/text/live-selection-edit.d.ts +68 -0
- package/dist/components/blocks/text/live-selection-edit.js +120 -0
- package/dist/components/blocks/text/live-split-rebalance.d.ts +56 -0
- package/dist/components/blocks/text/live-split-rebalance.js +239 -0
- package/dist/components/blocks/text/marker-completion.d.ts +18 -0
- package/dist/components/blocks/text/marker-completion.js +29 -0
- package/dist/components/blocks/text/pending-mark-insert.d.ts +37 -0
- package/dist/components/blocks/text/pending-mark-insert.js +188 -0
- package/dist/components/blocks/text/screen-diff.d.ts +20 -0
- package/dist/components/blocks/text/screen-diff.js +39 -0
- package/dist/components/blocks/text/text-clipboard.d.ts +70 -0
- package/dist/components/blocks/text/text-clipboard.js +131 -0
- package/dist/components/blocks/text/text-keydown.d.ts +39 -0
- package/dist/components/blocks/text/text-keydown.js +80 -0
- package/dist/components/blocks/text/text-render.d.ts +67 -0
- package/dist/components/blocks/text/text-render.js +215 -0
- package/dist/components/blocks/text/widget-adjacency.d.ts +30 -0
- package/dist/components/blocks/text/widget-adjacency.js +68 -0
- package/dist/components/blocks/text/widget-interaction.d.ts +90 -0
- package/dist/components/blocks/text/widget-interaction.js +591 -0
- package/dist/components/blocks/widget-portal.d.ts +54 -0
- package/dist/components/blocks/widget-portal.js +136 -0
- package/dist/components/built-in-blocks.d.ts +7 -0
- package/dist/components/built-in-blocks.js +77 -0
- package/dist/components/editor-root-clipboard.d.ts +29 -0
- package/dist/components/editor-root-clipboard.js +105 -0
- package/dist/components/editor-root-keydown.d.ts +47 -0
- package/dist/components/editor-root-keydown.js +88 -0
- package/dist/components/editor-root-listeners.d.ts +44 -0
- package/dist/components/editor-root-listeners.js +112 -0
- package/dist/components/image/ImageOverlayHost.svelte +115 -0
- package/dist/components/image/ImageOverlayHost.svelte.d.ts +20 -0
- package/dist/components/image/ImageProperties.svelte +153 -0
- package/dist/components/image/ImageProperties.svelte.d.ts +14 -0
- package/dist/components/image/ImageResizeHandles.svelte +188 -0
- package/dist/components/image/ImageResizeHandles.svelte.d.ts +10 -0
- package/dist/components/image/image-edit-commit.d.ts +38 -0
- package/dist/components/image/image-edit-commit.js +159 -0
- package/dist/components/image/image-resize.d.ts +11 -0
- package/dist/components/image/image-resize.js +38 -0
- package/dist/components/image/image-source-bytes.d.ts +15 -0
- package/dist/components/image/image-source-bytes.js +73 -0
- package/dist/components/image/image-widget-editing.d.ts +7 -0
- package/dist/components/image/image-widget-editing.js +41 -0
- package/dist/components/image/widget-dom.d.ts +11 -0
- package/dist/components/image/widget-dom.js +110 -0
- package/dist/components/image/widget-selection-state.svelte.d.ts +18 -0
- package/dist/components/image/widget-selection-state.svelte.js +26 -0
- package/dist/components/link-card/LinkCard.svelte +190 -0
- package/dist/components/link-card/LinkCard.svelte.d.ts +18 -0
- package/dist/components/link-card/LinkCardHost.svelte +181 -0
- package/dist/components/link-card/LinkCardHost.svelte.d.ts +25 -0
- package/dist/components/link-card/link-card-commit.d.ts +50 -0
- package/dist/components/link-card/link-card-commit.js +134 -0
- package/dist/components/link-card/link-card-entry.d.ts +39 -0
- package/dist/components/link-card/link-card-entry.js +51 -0
- package/dist/components/link-card/link-card-state.svelte.d.ts +44 -0
- package/dist/components/link-card/link-card-state.svelte.js +46 -0
- package/dist/components/lrd-map-gate.d.ts +18 -0
- package/dist/components/lrd-map-gate.js +22 -0
- package/dist/components/paste-image-arm.d.ts +29 -0
- package/dist/components/paste-image-arm.js +61 -0
- package/dist/components/portal.d.ts +11 -0
- package/dist/components/portal.js +29 -0
- package/dist/core/directive/activate.d.ts +8 -0
- package/dist/core/directive/activate.js +25 -0
- package/dist/core/directive/container-opener.d.ts +7 -0
- package/dist/core/directive/container-opener.js +162 -0
- package/dist/core/directive/grammar.d.ts +50 -0
- package/dist/core/directive/grammar.js +88 -0
- package/dist/core/directive/kinds.d.ts +31 -0
- package/dist/core/directive/kinds.js +137 -0
- package/dist/core/directive/registry.d.ts +39 -0
- package/dist/core/directive/registry.js +49 -0
- package/dist/core/directive/text-recognizer.d.ts +8 -0
- package/dist/core/directive/text-recognizer.js +87 -0
- package/dist/core/escapable.d.ts +6 -0
- package/dist/core/escapable.js +6 -0
- package/dist/core/inline/backticks.d.ts +16 -0
- package/dist/core/inline/backticks.js +62 -0
- package/dist/core/inline/character-refs.d.ts +7 -0
- package/dist/core/inline/character-refs.js +70 -0
- package/dist/core/inline/destination-bytes.d.ts +7 -0
- package/dist/core/inline/destination-bytes.js +14 -0
- package/dist/core/inline/entity-widget.d.ts +11 -0
- package/dist/core/inline/entity-widget.js +21 -0
- package/dist/core/inline/format-toggle.d.ts +50 -0
- package/dist/core/inline/format-toggle.js +533 -0
- package/dist/core/inline/html-entities.d.ts +8 -0
- package/dist/core/inline/html-entities.js +2134 -0
- package/dist/core/inline/html-tag-grammar.d.ts +16 -0
- package/dist/core/inline/html-tag-grammar.js +58 -0
- package/dist/core/inline/image-dimensions.d.ts +6 -0
- package/dist/core/inline/image-dimensions.js +42 -0
- package/dist/core/inline/index.d.ts +42 -0
- package/dist/core/inline/index.js +77 -0
- package/dist/core/inline/inline-cache.d.ts +20 -0
- package/dist/core/inline/inline-cache.js +28 -0
- package/dist/core/inline/inline-widgets.d.ts +124 -0
- package/dist/core/inline/inline-widgets.js +131 -0
- package/dist/core/inline/link-reference-resolver.d.ts +21 -0
- package/dist/core/inline/link-reference-resolver.js +46 -0
- package/dist/core/inline/raw-html-widget.d.ts +10 -0
- package/dist/core/inline/raw-html-widget.js +22 -0
- package/dist/core/inline/scan/autolinks.d.ts +23 -0
- package/dist/core/inline/scan/autolinks.js +354 -0
- package/dist/core/inline/scan/brackets.d.ts +11 -0
- package/dist/core/inline/scan/brackets.js +284 -0
- package/dist/core/inline/scan/code-spans.d.ts +7 -0
- package/dist/core/inline/scan/code-spans.js +27 -0
- package/dist/core/inline/scan/emphasis.d.ts +10 -0
- package/dist/core/inline/scan/emphasis.js +276 -0
- package/dist/core/inline/scan/index.d.ts +7 -0
- package/dist/core/inline/scan/index.js +176 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +62 -0
- package/dist/core/inline/scan/plugin-syntax.js +150 -0
- package/dist/core/inline/scan/scan-state.d.ts +47 -0
- package/dist/core/inline/scan/scan-state.js +48 -0
- package/dist/core/inline/scan/simple-nodes.d.ts +14 -0
- package/dist/core/inline/scan/simple-nodes.js +53 -0
- package/dist/core/inline/scan/url.d.ts +12 -0
- package/dist/core/inline/scan/url.js +82 -0
- package/dist/core/inline/transparency.d.ts +8 -0
- package/dist/core/inline/transparency.js +39 -0
- package/dist/core/inline/visibility.d.ts +81 -0
- package/dist/core/inline/visibility.js +144 -0
- package/dist/core/inline/walk.d.ts +14 -0
- package/dist/core/inline/walk.js +29 -0
- package/dist/core/inline-render.d.ts +48 -0
- package/dist/core/inline-render.js +322 -0
- package/dist/core/lines.d.ts +44 -0
- package/dist/core/lines.js +91 -0
- package/dist/core/node-views.d.ts +22 -0
- package/dist/core/node-views.js +8 -0
- package/dist/core/nodes.d.ts +297 -0
- package/dist/core/nodes.js +83 -0
- package/dist/core/parser.d.ts +68 -0
- package/dist/core/parser.js +189 -0
- package/dist/core/parsers/blockquote.d.ts +17 -0
- package/dist/core/parsers/blockquote.js +69 -0
- package/dist/core/parsers/built-in-openers.d.ts +8 -0
- package/dist/core/parsers/built-in-openers.js +124 -0
- package/dist/core/parsers/fence-syntax.d.ts +24 -0
- package/dist/core/parsers/fence-syntax.js +44 -0
- package/dist/core/parsers/fenced-code.d.ts +7 -0
- package/dist/core/parsers/fenced-code.js +29 -0
- package/dist/core/parsers/heading.d.ts +3 -0
- package/dist/core/parsers/heading.js +6 -0
- package/dist/core/parsers/html-block.d.ts +17 -0
- package/dist/core/parsers/html-block.js +94 -0
- package/dist/core/parsers/indented-code.d.ts +4 -0
- package/dist/core/parsers/indented-code.js +41 -0
- package/dist/core/parsers/link-reference.d.ts +7 -0
- package/dist/core/parsers/link-reference.js +152 -0
- package/dist/core/parsers/list.d.ts +19 -0
- package/dist/core/parsers/list.js +133 -0
- package/dist/core/parsers/paragraph.d.ts +10 -0
- package/dist/core/parsers/paragraph.js +42 -0
- package/dist/core/parsers/table-completion.d.ts +13 -0
- package/dist/core/parsers/table-completion.js +48 -0
- package/dist/core/parsers/table.d.ts +18 -0
- package/dist/core/parsers/table.js +110 -0
- package/dist/core/parsers/thematic-break.d.ts +1 -0
- package/dist/core/parsers/thematic-break.js +19 -0
- package/dist/core/serializer.d.ts +15 -0
- package/dist/core/serializer.js +11 -0
- package/dist/core/terminator-escalation.d.ts +8 -0
- package/dist/core/terminator-escalation.js +18 -0
- package/dist/core/url-policy.d.ts +14 -0
- package/dist/core/url-policy.js +44 -0
- package/dist/cursor/content-offsets.d.ts +29 -0
- package/dist/cursor/content-offsets.js +151 -0
- package/dist/cursor/coordinate-spaces.d.ts +65 -0
- package/dist/cursor/coordinate-spaces.js +66 -0
- package/dist/cursor/dom-walk.d.ts +18 -0
- package/dist/cursor/dom-walk.js +31 -0
- package/dist/cursor/edge-affinity.d.ts +44 -0
- package/dist/cursor/edge-affinity.js +60 -0
- package/dist/cursor/focused-caret.d.ts +10 -0
- package/dist/cursor/focused-caret.js +24 -0
- package/dist/cursor/height-model.d.ts +24 -0
- package/dist/cursor/height-model.js +72 -0
- package/dist/cursor/height-oracle.d.ts +24 -0
- package/dist/cursor/height-oracle.js +94 -0
- package/dist/cursor/overlay-rects.d.ts +8 -0
- package/dist/cursor/overlay-rects.js +19 -0
- package/dist/cursor/overlay-remeasure.d.ts +17 -0
- package/dist/cursor/overlay-remeasure.js +31 -0
- package/dist/cursor/pending-marks.d.ts +24 -0
- package/dist/cursor/pending-marks.js +36 -0
- package/dist/cursor/point-offset.d.ts +21 -0
- package/dist/cursor/point-offset.js +48 -0
- package/dist/cursor/reveal-anchor.d.ts +30 -0
- package/dist/cursor/reveal-anchor.js +31 -0
- package/dist/cursor/reveal-source.d.ts +35 -0
- package/dist/cursor/reveal-source.js +51 -0
- package/dist/cursor/scroll-ancestors.d.ts +28 -0
- package/dist/cursor/scroll-ancestors.js +89 -0
- package/dist/cursor/scrollport.d.ts +20 -0
- package/dist/cursor/scrollport.js +41 -0
- package/dist/cursor/sticky-column.d.ts +35 -0
- package/dist/cursor/sticky-column.js +64 -0
- package/dist/cursor/sticky-measure.d.ts +16 -0
- package/dist/cursor/sticky-measure.js +97 -0
- package/dist/cursor/typography-estimates.d.ts +21 -0
- package/dist/cursor/typography-estimates.js +21 -0
- package/dist/cursor/visual-lines.d.ts +25 -0
- package/dist/cursor/visual-lines.js +125 -0
- package/dist/cursor/widget-offset.d.ts +132 -0
- package/dist/cursor/widget-offset.js +543 -0
- package/dist/debug/diagnostics-report.d.ts +17 -0
- package/dist/debug/diagnostics-report.js +25 -0
- package/dist/debug/dump-tree.d.ts +6 -0
- package/dist/debug/dump-tree.js +114 -0
- package/dist/debug/inspect.d.ts +19 -0
- package/dist/debug/inspect.js +116 -0
- package/dist/debug/interaction-trace.d.ts +39 -0
- package/dist/debug/interaction-trace.js +105 -0
- package/dist/debug/operations-log.d.ts +14 -0
- package/dist/debug/operations-log.js +30 -0
- package/dist/decorations/buckets.d.ts +40 -0
- package/dist/decorations/buckets.js +87 -0
- package/dist/decorations/decoration-state.svelte.d.ts +20 -0
- package/dist/decorations/decoration-state.svelte.js +195 -0
- package/dist/decorations/island-dom.d.ts +35 -0
- package/dist/decorations/island-dom.js +148 -0
- package/dist/decorations/reserved-attrs.d.ts +12 -0
- package/dist/decorations/reserved-attrs.js +47 -0
- package/dist/decorations/types.d.ts +68 -0
- package/dist/decorations/types.js +5 -0
- package/dist/decorations/widget-dom.d.ts +15 -0
- package/dist/decorations/widget-dom.js +35 -0
- package/dist/dev-warn.d.ts +13 -0
- package/dist/dev-warn.js +28 -0
- package/dist/editor-actions/ancestry-folds.d.ts +32 -0
- package/dist/editor-actions/ancestry-folds.js +106 -0
- package/dist/editor-actions/block-edit-core.d.ts +29 -0
- package/dist/editor-actions/block-edit-core.js +277 -0
- package/dist/editor-actions/block-edit-scope.d.ts +79 -0
- package/dist/editor-actions/block-edit-scope.js +76 -0
- package/dist/editor-actions/block-edit.d.ts +8 -0
- package/dist/editor-actions/block-edit.js +111 -0
- package/dist/editor-actions/commit/history.d.ts +9 -0
- package/dist/editor-actions/commit/history.js +77 -0
- package/dist/editor-actions/commit/text-batch.d.ts +34 -0
- package/dist/editor-actions/commit/text-batch.js +61 -0
- package/dist/editor-actions/commit/undo-controller.d.ts +7 -0
- package/dist/editor-actions/commit/undo-controller.js +570 -0
- package/dist/editor-actions/container-block-component.d.ts +85 -0
- package/dist/editor-actions/container-block-component.js +228 -0
- package/dist/editor-actions/container-edit.d.ts +7 -0
- package/dist/editor-actions/container-edit.js +49 -0
- package/dist/editor-actions/container-exit-overrides.d.ts +16 -0
- package/dist/editor-actions/container-exit-overrides.js +38 -0
- package/dist/editor-actions/deps.d.ts +60 -0
- package/dist/editor-actions/deps.js +1 -0
- package/dist/editor-actions/enter-completion.d.ts +25 -0
- package/dist/editor-actions/enter-completion.js +76 -0
- package/dist/editor-actions/focus/focus-dispatch.d.ts +36 -0
- package/dist/editor-actions/focus/focus-dispatch.js +106 -0
- package/dist/editor-actions/focus/focus-landing.d.ts +7 -0
- package/dist/editor-actions/focus/focus-landing.js +39 -0
- package/dist/editor-actions/focus/focus.d.ts +7 -0
- package/dist/editor-actions/focus/focus.js +74 -0
- package/dist/editor-actions/index.d.ts +15 -0
- package/dist/editor-actions/index.js +19 -0
- package/dist/editor-actions/inline-range-commit.d.ts +20 -0
- package/dist/editor-actions/inline-range-commit.js +63 -0
- package/dist/editor-actions/list-context.d.ts +25 -0
- package/dist/editor-actions/list-context.js +285 -0
- package/dist/editor-actions/list-overrides.d.ts +13 -0
- package/dist/editor-actions/list-overrides.js +26 -0
- package/dist/editor-actions/merge-fallback.d.ts +19 -0
- package/dist/editor-actions/merge-fallback.js +27 -0
- package/dist/editor-actions/nested/nested-actions.d.ts +65 -0
- package/dist/editor-actions/nested/nested-actions.js +59 -0
- package/dist/editor-actions/nested/nested-block-edit.d.ts +10 -0
- package/dist/editor-actions/nested/nested-block-edit.js +181 -0
- package/dist/editor-actions/nested/nested-focus.d.ts +9 -0
- package/dist/editor-actions/nested/nested-focus.js +30 -0
- package/dist/editor-actions/paste-coordinator.d.ts +4 -0
- package/dist/editor-actions/paste-coordinator.js +20 -0
- package/dist/editor-actions/plugin/chrome-leaf.d.ts +25 -0
- package/dist/editor-actions/plugin/chrome-leaf.js +80 -0
- package/dist/editor-actions/plugin/container.d.ts +137 -0
- package/dist/editor-actions/plugin/container.js +351 -0
- package/dist/editor-actions/plugin/directive-container.d.ts +13 -0
- package/dist/editor-actions/plugin/directive-container.js +31 -0
- package/dist/editor-actions/reorder-action.d.ts +17 -0
- package/dist/editor-actions/reorder-action.js +104 -0
- package/dist/editor-actions/reorder-drag.d.ts +34 -0
- package/dist/editor-actions/reorder-drag.js +152 -0
- package/dist/editor-actions/replacement-focus.d.ts +27 -0
- package/dist/editor-actions/replacement-focus.js +60 -0
- package/dist/editor-actions/search-replace.d.ts +6 -0
- package/dist/editor-actions/search-replace.js +145 -0
- package/dist/editor-actions/table-context.d.ts +34 -0
- package/dist/editor-actions/table-context.js +292 -0
- package/dist/editor-actions/unwrap-strategies.d.ts +14 -0
- package/dist/editor-actions/unwrap-strategies.js +131 -0
- package/dist/editor-actions/whole-block-focus-surface.d.ts +51 -0
- package/dist/editor-actions/whole-block-focus-surface.js +171 -0
- package/dist/editor-events.d.ts +89 -0
- package/dist/editor-events.js +96 -0
- package/dist/editor-keys.d.ts +197 -0
- package/dist/editor-keys.js +40 -0
- package/dist/editor-props.d.ts +164 -0
- package/dist/editor-props.js +1 -0
- package/dist/editor-rects.d.ts +58 -0
- package/dist/editor-rects.js +114 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +20 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/invariants/commit-paths.d.ts +10 -0
- package/dist/invariants/commit-paths.js +25 -0
- package/dist/invariants/commit-scope.d.ts +9 -0
- package/dist/invariants/commit-scope.js +16 -0
- package/dist/invariants/context-keys.d.ts +7 -0
- package/dist/invariants/context-keys.js +14 -0
- package/dist/invariants/descriptor.d.ts +10 -0
- package/dist/invariants/descriptor.js +24 -0
- package/dist/invariants/inline-transitions.d.ts +23 -0
- package/dist/invariants/inline-transitions.js +52 -0
- package/dist/invariants/install.d.ts +36 -0
- package/dist/invariants/install.js +66 -0
- package/dist/invariants/landable-caret.d.ts +12 -0
- package/dist/invariants/landable-caret.js +31 -0
- package/dist/invariants/marker-css-parity.d.ts +10 -0
- package/dist/invariants/marker-css-parity.js +87 -0
- package/dist/invariants/node-shape.d.ts +44 -0
- package/dist/invariants/node-shape.js +258 -0
- package/dist/invariants/registry.d.ts +126 -0
- package/dist/invariants/registry.js +349 -0
- package/dist/invariants/render-fidelity.d.ts +8 -0
- package/dist/invariants/render-fidelity.js +33 -0
- package/dist/invariants/selection-endpoints.d.ts +19 -0
- package/dist/invariants/selection-endpoints.js +78 -0
- package/dist/invariants/single-node-sink.d.ts +9 -0
- package/dist/invariants/single-node-sink.js +16 -0
- package/dist/invariants/snapshot-integrity.d.ts +17 -0
- package/dist/invariants/snapshot-integrity.js +24 -0
- package/dist/invariants/split-landing.d.ts +8 -0
- package/dist/invariants/split-landing.js +15 -0
- package/dist/invariants/structural-descriptor.d.ts +21 -0
- package/dist/invariants/structural-descriptor.js +50 -0
- package/dist/perf/instruments.d.ts +49 -0
- package/dist/perf/instruments.js +154 -0
- package/dist/perf/use-mount-gauge.svelte.d.ts +1 -0
- package/dist/perf/use-mount-gauge.svelte.js +15 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +156 -0
- package/dist/plugins/admonitions/AdmonitionBlock.svelte +144 -0
- package/dist/plugins/admonitions/AdmonitionBlock.svelte.d.ts +11 -0
- package/dist/plugins/admonitions/admonition-kind.d.ts +8 -0
- package/dist/plugins/admonitions/admonition-kind.js +97 -0
- package/dist/plugins/admonitions/convert-document.d.ts +11 -0
- package/dist/plugins/admonitions/convert-document.js +39 -0
- package/dist/plugins/admonitions/gh-alert.d.ts +13 -0
- package/dist/plugins/admonitions/gh-alert.js +83 -0
- package/dist/plugins/admonitions/github-alert-kind.d.ts +12 -0
- package/dist/plugins/admonitions/github-alert-kind.js +125 -0
- package/dist/plugins/admonitions/index.d.ts +6 -0
- package/dist/plugins/admonitions/index.js +8 -0
- package/dist/plugins/admonitions/kinds.d.ts +26 -0
- package/dist/plugins/admonitions/kinds.js +18 -0
- package/dist/plugins/admonitions/register.d.ts +7 -0
- package/dist/plugins/admonitions/register.js +12 -0
- package/dist/plugins/details/DetailsBlock.svelte +129 -0
- package/dist/plugins/details/DetailsBlock.svelte.d.ts +11 -0
- package/dist/plugins/details/details-disclosure.svelte.d.ts +17 -0
- package/dist/plugins/details/details-disclosure.svelte.js +24 -0
- package/dist/plugins/details/details-kind.d.ts +25 -0
- package/dist/plugins/details/details-kind.js +237 -0
- package/dist/plugins/details/index.d.ts +2 -0
- package/dist/plugins/details/index.js +2 -0
- package/dist/plugins/details/register.d.ts +6 -0
- package/dist/plugins/details/register.js +15 -0
- package/dist/plugins/emoji/emoji-plugin.d.ts +2 -0
- package/dist/plugins/emoji/emoji-plugin.js +10 -0
- package/dist/plugins/emoji/emoji-recognizer.d.ts +18 -0
- package/dist/plugins/emoji/emoji-recognizer.js +63 -0
- package/dist/plugins/emoji/emoji-table.d.ts +1 -0
- package/dist/plugins/emoji/emoji-table.js +1919 -0
- package/dist/plugins/emoji/index.d.ts +3 -0
- package/dist/plugins/emoji/index.js +4 -0
- package/dist/plugins/footnotes/FootnoteDefinition.svelte +103 -0
- package/dist/plugins/footnotes/FootnoteDefinition.svelte.d.ts +13 -0
- package/dist/plugins/footnotes/FootnoteReference.svelte +50 -0
- package/dist/plugins/footnotes/FootnoteReference.svelte.d.ts +4 -0
- package/dist/plugins/footnotes/constants.d.ts +2 -0
- package/dist/plugins/footnotes/constants.js +4 -0
- package/dist/plugins/footnotes/footnote-definition.d.ts +14 -0
- package/dist/plugins/footnotes/footnote-definition.js +147 -0
- package/dist/plugins/footnotes/footnote-lookup.d.ts +9 -0
- package/dist/plugins/footnotes/footnote-lookup.js +33 -0
- package/dist/plugins/footnotes/footnote-numbering.d.ts +23 -0
- package/dist/plugins/footnotes/footnote-numbering.js +78 -0
- package/dist/plugins/footnotes/footnote-reference.d.ts +7 -0
- package/dist/plugins/footnotes/footnote-reference.js +54 -0
- package/dist/plugins/footnotes/footnotes-plugin.d.ts +7 -0
- package/dist/plugins/footnotes/footnotes-plugin.js +17 -0
- package/dist/plugins/footnotes/index.d.ts +5 -0
- package/dist/plugins/footnotes/index.js +6 -0
- package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.d.ts +17 -0
- package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.js +31 -0
- package/dist/plugins/highlight-occurrences/index.d.ts +1 -0
- package/dist/plugins/highlight-occurrences/index.js +1 -0
- package/dist/plugins/highlight-occurrences/occurrence-source.d.ts +26 -0
- package/dist/plugins/highlight-occurrences/occurrence-source.js +54 -0
- package/dist/plugins/highlight-occurrences/occurrences.d.ts +33 -0
- package/dist/plugins/highlight-occurrences/occurrences.js +108 -0
- package/dist/plugins/latex/BlockMath.svelte +119 -0
- package/dist/plugins/latex/BlockMath.svelte.d.ts +21 -0
- package/dist/plugins/latex/MathInline.svelte +28 -0
- package/dist/plugins/latex/MathInline.svelte.d.ts +4 -0
- package/dist/plugins/latex/index.d.ts +3 -0
- package/dist/plugins/latex/index.js +4 -0
- package/dist/plugins/latex/latex-kind.d.ts +17 -0
- package/dist/plugins/latex/latex-kind.js +224 -0
- package/dist/plugins/latex/math-completion.d.ts +10 -0
- package/dist/plugins/latex/math-completion.js +19 -0
- package/dist/plugins/latex/math-renderer.d.ts +26 -0
- package/dist/plugins/latex/math-renderer.js +42 -0
- package/dist/plugins/latex/register.d.ts +10 -0
- package/dist/plugins/latex/register.js +23 -0
- package/dist/plugins/latex/renderer.d.ts +13 -0
- package/dist/plugins/latex/renderer.js +34 -0
- package/dist/plugins/mermaid/MermaidBlock.svelte +572 -0
- package/dist/plugins/mermaid/MermaidBlock.svelte.d.ts +11 -0
- package/dist/plugins/mermaid/index.d.ts +3 -0
- package/dist/plugins/mermaid/index.js +3 -0
- package/dist/plugins/mermaid/mermaid-kind.d.ts +38 -0
- package/dist/plugins/mermaid/mermaid-kind.js +176 -0
- package/dist/plugins/mermaid/mermaid-renderer.d.ts +28 -0
- package/dist/plugins/mermaid/mermaid-renderer.js +35 -0
- package/dist/plugins/mermaid/register.d.ts +10 -0
- package/dist/plugins/mermaid/register.js +20 -0
- package/dist/plugins/mermaid/renderer.d.ts +11 -0
- package/dist/plugins/mermaid/renderer.js +53 -0
- package/dist/plugins/parrot/ParrotBlock.svelte +342 -0
- package/dist/plugins/parrot/ParrotBlock.svelte.d.ts +21 -0
- package/dist/plugins/parrot/index.d.ts +1 -0
- package/dist/plugins/parrot/index.js +2 -0
- package/dist/plugins/parrot/parrot-plugin.d.ts +3 -0
- package/dist/plugins/parrot/parrot-plugin.js +49 -0
- package/dist/plugins/toc/TocBlock.svelte +218 -0
- package/dist/plugins/toc/TocBlock.svelte.d.ts +24 -0
- package/dist/plugins/toc/heading-outline.d.ts +28 -0
- package/dist/plugins/toc/heading-outline.js +65 -0
- package/dist/plugins/toc/index.d.ts +2 -0
- package/dist/plugins/toc/index.js +2 -0
- package/dist/plugins/toc/navigation-queue.d.ts +12 -0
- package/dist/plugins/toc/navigation-queue.js +21 -0
- package/dist/plugins/toc/toc-plugin.d.ts +16 -0
- package/dist/plugins/toc/toc-plugin.js +71 -0
- package/dist/presentation-mode.d.ts +32 -0
- package/dist/presentation-mode.js +50 -0
- package/dist/reactivity/block-list-state.svelte.d.ts +22 -0
- package/dist/reactivity/block-list-state.svelte.js +46 -0
- package/dist/reactivity/block-window.svelte.d.ts +41 -0
- package/dist/reactivity/block-window.svelte.js +93 -0
- package/dist/reactivity/content-version.svelte.d.ts +14 -0
- package/dist/reactivity/content-version.svelte.js +16 -0
- package/dist/reactivity/list-windowing.svelte.d.ts +100 -0
- package/dist/reactivity/list-windowing.svelte.js +402 -0
- package/dist/reactivity/measure-batch.d.ts +15 -0
- package/dist/reactivity/measure-batch.js +18 -0
- package/dist/reactivity/publish-ref.svelte.d.ts +54 -0
- package/dist/reactivity/publish-ref.svelte.js +128 -0
- package/dist/reactivity/scope-geometry.d.ts +18 -0
- package/dist/reactivity/scope-geometry.js +29 -0
- package/dist/reactivity/state-registry.d.ts +10 -0
- package/dist/reactivity/state-registry.js +44 -0
- package/dist/reactivity/use-container-windowing.svelte.d.ts +35 -0
- package/dist/reactivity/use-container-windowing.svelte.js +95 -0
- package/dist/reactivity/window-slice.d.ts +11 -0
- package/dist/reactivity/window-slice.js +9 -0
- package/dist/scan-index.d.ts +11 -0
- package/dist/scan-index.js +27 -0
- package/dist/schema/block-commands.d.ts +105 -0
- package/dist/schema/block-commands.js +179 -0
- package/dist/schema/block-completions.d.ts +33 -0
- package/dist/schema/block-completions.js +49 -0
- package/dist/schema/block-component-registry.d.ts +33 -0
- package/dist/schema/block-component-registry.js +33 -0
- package/dist/schema/block-kind-descriptor.d.ts +281 -0
- package/dist/schema/block-kind-descriptor.js +187 -0
- package/dist/schema/block-openers.d.ts +94 -0
- package/dist/schema/block-openers.js +150 -0
- package/dist/schema/built-in-descriptors.d.ts +8 -0
- package/dist/schema/built-in-descriptors.js +560 -0
- package/dist/schema/child-spans.d.ts +25 -0
- package/dist/schema/child-spans.js +161 -0
- package/dist/schema/closure.d.ts +39 -0
- package/dist/schema/closure.js +55 -0
- package/dist/schema/command-id.d.ts +20 -0
- package/dist/schema/command-id.js +42 -0
- package/dist/schema/commands.d.ts +147 -0
- package/dist/schema/commands.js +294 -0
- package/dist/schema/container-raw.d.ts +21 -0
- package/dist/schema/container-raw.js +43 -0
- package/dist/schema/container-rebuilders.d.ts +32 -0
- package/dist/schema/container-rebuilders.js +95 -0
- package/dist/schema/define-plugin-block.d.ts +15 -0
- package/dist/schema/define-plugin-block.js +18 -0
- package/dist/schema/fenced-code-raw.d.ts +43 -0
- package/dist/schema/fenced-code-raw.js +220 -0
- package/dist/schema/global-commands.d.ts +11 -0
- package/dist/schema/global-commands.js +40 -0
- package/dist/schema/inline-construct-policy.d.ts +131 -0
- package/dist/schema/inline-construct-policy.js +96 -0
- package/dist/schema/keybinding-overrides.d.ts +39 -0
- package/dist/schema/keybinding-overrides.js +43 -0
- package/dist/schema/keybindings.d.ts +36 -0
- package/dist/schema/keybindings.js +75 -0
- package/dist/schema/merge-rules.d.ts +28 -0
- package/dist/schema/merge-rules.js +57 -0
- package/dist/schema/opener-priorities.d.ts +16 -0
- package/dist/schema/opener-priorities.js +16 -0
- package/dist/schema/operations.d.ts +110 -0
- package/dist/schema/operations.js +6 -0
- package/dist/schema/plugin-activation.d.ts +13 -0
- package/dist/schema/plugin-activation.js +18 -0
- package/dist/schema/plugin-editor-context.d.ts +33 -0
- package/dist/schema/plugin-editor-context.js +65 -0
- package/dist/schema/plugin-install.d.ts +58 -0
- package/dist/schema/plugin-install.js +132 -0
- package/dist/schema/plugin-kind.d.ts +21 -0
- package/dist/schema/plugin-kind.js +86 -0
- package/dist/schema/plugin-name.d.ts +1 -0
- package/dist/schema/plugin-name.js +7 -0
- package/dist/schema/register-once.d.ts +9 -0
- package/dist/schema/register-once.js +35 -0
- package/dist/schema/registration-checks.d.ts +32 -0
- package/dist/schema/registration-checks.js +122 -0
- package/dist/schema/registration-pending.d.ts +28 -0
- package/dist/schema/registration-pending.js +48 -0
- package/dist/schema/registry-reset.d.ts +7 -0
- package/dist/schema/registry-reset.js +30 -0
- package/dist/schema/registry-view.d.ts +30 -0
- package/dist/schema/registry-view.js +39 -0
- package/dist/schema/reserved-chords.d.ts +35 -0
- package/dist/schema/reserved-chords.js +313 -0
- package/dist/schema/reserved-chrome.d.ts +23 -0
- package/dist/schema/reserved-chrome.js +33 -0
- package/dist/schema/table-cell-raw.d.ts +14 -0
- package/dist/schema/table-cell-raw.js +30 -0
- package/dist/schema/whole-block-unit.d.ts +8 -0
- package/dist/schema/whole-block-unit.js +12 -0
- package/dist/search/document-scan.d.ts +22 -0
- package/dist/search/document-scan.js +39 -0
- package/dist/search/matcher.d.ts +31 -0
- package/dist/search/matcher.js +70 -0
- package/dist/search/regex-executor.d.ts +41 -0
- package/dist/search/regex-executor.js +165 -0
- package/dist/search/replace.d.ts +15 -0
- package/dist/search/replace.js +29 -0
- package/dist/search/search-state.svelte.d.ts +59 -0
- package/dist/search/search-state.svelte.js +231 -0
- package/dist/selection/autoscroll.d.ts +26 -0
- package/dist/selection/autoscroll.js +91 -0
- package/dist/selection/block-hit-test.d.ts +37 -0
- package/dist/selection/block-hit-test.js +59 -0
- package/dist/selection/caret-doors.d.ts +20 -0
- package/dist/selection/caret-doors.js +44 -0
- package/dist/selection/caret-restore.d.ts +15 -0
- package/dist/selection/caret-restore.js +32 -0
- package/dist/selection/char-endpoint-snap.d.ts +15 -0
- package/dist/selection/char-endpoint-snap.js +39 -0
- package/dist/selection/clipboard-text.d.ts +13 -0
- package/dist/selection/clipboard-text.js +314 -0
- package/dist/selection/cross-block/clipboard.d.ts +14 -0
- package/dist/selection/cross-block/clipboard.js +20 -0
- package/dist/selection/cross-block/dispatch.d.ts +78 -0
- package/dist/selection/cross-block/dispatch.js +57 -0
- package/dist/selection/cross-block/format-range.d.ts +38 -0
- package/dist/selection/cross-block/format-range.js +193 -0
- package/dist/selection/cross-block/format-toggle.d.ts +27 -0
- package/dist/selection/cross-block/format-toggle.js +109 -0
- package/dist/selection/cross-block/keydown.d.ts +11 -0
- package/dist/selection/cross-block/keydown.js +339 -0
- package/dist/selection/cross-block/ops.d.ts +56 -0
- package/dist/selection/cross-block/ops.js +210 -0
- package/dist/selection/cross-block/paste.d.ts +8 -0
- package/dist/selection/cross-block/paste.js +156 -0
- package/dist/selection/cross-block/pointer.d.ts +19 -0
- package/dist/selection/cross-block/pointer.js +80 -0
- package/dist/selection/cross-block/type-replace.d.ts +10 -0
- package/dist/selection/cross-block/type-replace.js +129 -0
- package/dist/selection/dead-space-caret.d.ts +39 -0
- package/dist/selection/dead-space-caret.js +173 -0
- package/dist/selection/drag-pointer.d.ts +22 -0
- package/dist/selection/drag-pointer.js +89 -0
- package/dist/selection/gap-caret.d.ts +47 -0
- package/dist/selection/gap-caret.js +72 -0
- package/dist/selection/keyboard-extend.d.ts +43 -0
- package/dist/selection/keyboard-extend.js +257 -0
- package/dist/selection/native-bridge.d.ts +47 -0
- package/dist/selection/native-bridge.js +204 -0
- package/dist/selection/nearest-block.d.ts +43 -0
- package/dist/selection/nearest-block.js +73 -0
- package/dist/selection/path-lookup.d.ts +32 -0
- package/dist/selection/path-lookup.js +132 -0
- package/dist/selection/path-math.d.ts +35 -0
- package/dist/selection/path-math.js +90 -0
- package/dist/selection/pointer-session.d.ts +43 -0
- package/dist/selection/pointer-session.js +111 -0
- package/dist/selection/primitives.d.ts +75 -0
- package/dist/selection/primitives.js +91 -0
- package/dist/selection/range-delete-ceremony.d.ts +85 -0
- package/dist/selection/range-delete-ceremony.js +197 -0
- package/dist/selection/range-delete-chrome.d.ts +39 -0
- package/dist/selection/range-delete-chrome.js +105 -0
- package/dist/selection/range-delete-table-coverage.d.ts +28 -0
- package/dist/selection/range-delete-table-coverage.js +168 -0
- package/dist/selection/range-delete-table.d.ts +16 -0
- package/dist/selection/range-delete-table.js +347 -0
- package/dist/selection/range-delete.d.ts +33 -0
- package/dist/selection/range-delete.js +121 -0
- package/dist/selection/selection-description.d.ts +7 -0
- package/dist/selection/selection-description.js +18 -0
- package/dist/selection/selection-restore.d.ts +46 -0
- package/dist/selection/selection-restore.js +66 -0
- package/dist/selection/selection-state.svelte.d.ts +72 -0
- package/dist/selection/selection-state.svelte.js +247 -0
- package/dist/selection/shared-keydown.d.ts +61 -0
- package/dist/selection/shared-keydown.js +150 -0
- package/dist/selection/table-endpoint-snap.d.ts +55 -0
- package/dist/selection/table-endpoint-snap.js +143 -0
- package/dist/selection/table-rect-extend.d.ts +23 -0
- package/dist/selection/table-rect-extend.js +46 -0
- package/dist/selection/widget-range-paint.d.ts +12 -0
- package/dist/selection/widget-range-paint.js +44 -0
- package/dist/styles/editor-theme.css +151 -0
- package/dist/styles/editor.css +585 -0
- package/dist/testing/conformance-core.d.ts +45 -0
- package/dist/testing/conformance-core.js +105 -0
- package/dist/testing/container-conformance.d.ts +137 -0
- package/dist/testing/container-conformance.js +533 -0
- package/dist/testing/headless-actions.d.ts +37 -0
- package/dist/testing/headless-actions.js +118 -0
- package/dist/testing/inline-conformance.d.ts +50 -0
- package/dist/testing/inline-conformance.js +442 -0
- package/dist/testing/kind-conformance.d.ts +54 -0
- package/dist/testing/kind-conformance.js +325 -0
- package/dist/testing/mount-dom-stubs.d.ts +8 -0
- package/dist/testing/mount-dom-stubs.js +19 -0
- package/dist/testing/parse-convergence.d.ts +14 -0
- package/dist/testing/parse-convergence.js +81 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +61 -0
- package/dist/tree-operations/blockquote.d.ts +18 -0
- package/dist/tree-operations/blockquote.js +59 -0
- package/dist/tree-operations/children.d.ts +17 -0
- package/dist/tree-operations/children.js +56 -0
- package/dist/tree-operations/cleanup.d.ts +10 -0
- package/dist/tree-operations/cleanup.js +25 -0
- package/dist/tree-operations/clone.d.ts +5 -0
- package/dist/tree-operations/clone.js +43 -0
- package/dist/tree-operations/container-lift.d.ts +9 -0
- package/dist/tree-operations/container-lift.js +29 -0
- package/dist/tree-operations/index.d.ts +16 -0
- package/dist/tree-operations/index.js +14 -0
- package/dist/tree-operations/list/empty-check.d.ts +7 -0
- package/dist/tree-operations/list/empty-check.js +19 -0
- package/dist/tree-operations/list/exit-replacement.d.ts +13 -0
- package/dist/tree-operations/list/exit-replacement.js +49 -0
- package/dist/tree-operations/list/item-partition.d.ts +13 -0
- package/dist/tree-operations/list/item-partition.js +33 -0
- package/dist/tree-operations/list/list-builders.d.ts +44 -0
- package/dist/tree-operations/list/list-builders.js +129 -0
- package/dist/tree-operations/list/ordered-markers.d.ts +36 -0
- package/dist/tree-operations/list/ordered-markers.js +114 -0
- package/dist/tree-operations/list/reconcile-task.d.ts +12 -0
- package/dist/tree-operations/list/reconcile-task.js +47 -0
- package/dist/tree-operations/list/sublist-separator.d.ts +13 -0
- package/dist/tree-operations/list/sublist-separator.js +34 -0
- package/dist/tree-operations/list/terminator.d.ts +17 -0
- package/dist/tree-operations/list/terminator.js +60 -0
- package/dist/tree-operations/list/unwrap-merge.d.ts +29 -0
- package/dist/tree-operations/list/unwrap-merge.js +202 -0
- package/dist/tree-operations/node-ops.d.ts +296 -0
- package/dist/tree-operations/node-ops.js +1428 -0
- package/dist/tree-operations/parse-block.d.ts +3 -0
- package/dist/tree-operations/parse-block.js +8 -0
- package/dist/tree-operations/paste/apply.d.ts +12 -0
- package/dist/tree-operations/paste/apply.js +75 -0
- package/dist/tree-operations/paste/body-write.d.ts +19 -0
- package/dist/tree-operations/paste/body-write.js +53 -0
- package/dist/tree-operations/paste/container-match.d.ts +32 -0
- package/dist/tree-operations/paste/container-match.js +238 -0
- package/dist/tree-operations/paste/container-paste.d.ts +10 -0
- package/dist/tree-operations/paste/container-paste.js +27 -0
- package/dist/tree-operations/paste/dispatch.d.ts +61 -0
- package/dist/tree-operations/paste/dispatch.js +138 -0
- package/dist/tree-operations/paste/find-enclosing-list.d.ts +8 -0
- package/dist/tree-operations/paste/find-enclosing-list.js +30 -0
- package/dist/tree-operations/paste/focus-target.d.ts +27 -0
- package/dist/tree-operations/paste/focus-target.js +40 -0
- package/dist/tree-operations/paste/hooks.d.ts +17 -0
- package/dist/tree-operations/paste/hooks.js +80 -0
- package/dist/tree-operations/paste/list-absorb.d.ts +29 -0
- package/dist/tree-operations/paste/list-absorb.js +106 -0
- package/dist/tree-operations/paste/list-break-out.d.ts +42 -0
- package/dist/tree-operations/paste/list-break-out.js +136 -0
- package/dist/tree-operations/paste/parent-scope.d.ts +17 -0
- package/dist/tree-operations/paste/parent-scope.js +35 -0
- package/dist/tree-operations/paste/paste-deps.d.ts +20 -0
- package/dist/tree-operations/paste/paste-deps.js +6 -0
- package/dist/tree-operations/paste/paste-replacement.d.ts +8 -0
- package/dist/tree-operations/paste/paste-replacement.js +60 -0
- package/dist/tree-operations/paste/paste-transforms.d.ts +25 -0
- package/dist/tree-operations/paste/paste-transforms.js +72 -0
- package/dist/tree-operations/paste/replace-block-at-parent.d.ts +29 -0
- package/dist/tree-operations/paste/replace-block-at-parent.js +75 -0
- package/dist/tree-operations/paste/strategy.d.ts +9 -0
- package/dist/tree-operations/paste/strategy.js +20 -0
- package/dist/tree-operations/paste/table-slice.d.ts +10 -0
- package/dist/tree-operations/paste/table-slice.js +39 -0
- package/dist/tree-operations/paste-surfaces.d.ts +61 -0
- package/dist/tree-operations/paste-surfaces.js +15 -0
- package/dist/tree-operations/path-mutate.d.ts +12 -0
- package/dist/tree-operations/path-mutate.js +20 -0
- package/dist/tree-operations/reorder-unit.d.ts +17 -0
- package/dist/tree-operations/reorder-unit.js +37 -0
- package/dist/tree-operations/reorder.d.ts +13 -0
- package/dist/tree-operations/reorder.js +57 -0
- package/dist/tree-operations/sharing.d.ts +18 -0
- package/dist/tree-operations/sharing.js +14 -0
- package/dist/tree-operations/splice-many.d.ts +9 -0
- package/dist/tree-operations/splice-many.js +22 -0
- package/dist/tree-operations/structural-change.d.ts +65 -0
- package/dist/tree-operations/structural-change.js +117 -0
- package/dist/tree-operations/sub-table-copy.d.ts +6 -0
- package/dist/tree-operations/sub-table-copy.js +43 -0
- package/dist/tree-operations/table-mutations.d.ts +17 -0
- package/dist/tree-operations/table-mutations.js +101 -0
- package/dist/tree-operations/unshare.d.ts +101 -0
- package/dist/tree-operations/unshare.js +248 -0
- package/dist/undo/manager.d.ts +2 -0
- package/dist/undo/manager.js +50 -0
- package/dist/undo/types.d.ts +44 -0
- package/dist/undo/types.js +7 -0
- package/docs/guide/consumer-guide.md +1098 -0
- package/docs/guide/directives.md +223 -0
- package/docs/guide/plugin-api.md +329 -0
- package/docs/guide/plugin-guide/conspiracy.gif +0 -0
- package/docs/guide/plugin-guide/parrot-frames.md +209 -0
- package/docs/guide/plugin-guide/parrot.gif +0 -0
- package/docs/guide/plugin-guide.md +1693 -0
- package/docs/guide/plugin-testing.md +463 -0
- package/package.json +238 -0
|
@@ -0,0 +1,1428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kind-agnostic CST node mutations: path resolution, split, merge, delete, update. Children-array
|
|
3
|
+
* contract: an op mutating a container's top-level children takes the array as a parameter and
|
|
4
|
+
* mutates that, never `node.children` — the caller owns and republishes it, so a direct splice is
|
|
5
|
+
* overwritten. A descendant found by walking the live tree is the exception: mutate it in place on
|
|
6
|
+
* a caller-unshared spine (`unshare.ts`), a STRUCTURAL one via `commitMultiScope`.
|
|
7
|
+
*/
|
|
8
|
+
import { DEV } from 'esm-env';
|
|
9
|
+
import { isBlankParagraph, isBlankSource, parse } from '../core/parser';
|
|
10
|
+
import { escalatedFenceLength, matchFenceOpen } from '../core/parsers/fence-syntax';
|
|
11
|
+
import { isBlockOpenerRegistered } from '../schema/block-openers';
|
|
12
|
+
import { getLiveJoinSeamCleaner, getLiveSplitRebalancer } from '../schema/inline-construct-policy';
|
|
13
|
+
import { displayLength, snapToScalarBoundary, terminateLine, trailingLineEnding, trimTrailingLineEnding } from '../core/lines';
|
|
14
|
+
import { devWarn } from '../dev-warn';
|
|
15
|
+
import { assignChildIdsDeep } from '../block-id';
|
|
16
|
+
import { perfEnabled, recordContainerKindReparse } from '../perf/instruments';
|
|
17
|
+
import { findMergeTarget } from '../schema/merge-rules';
|
|
18
|
+
import { rebuildAncestryRaw } from '../schema/container-raw';
|
|
19
|
+
import { getBlockKindDescriptor, tryGetBlockKindDescriptor } from '../schema/block-kind-descriptor';
|
|
20
|
+
import { reservedChromeKindOf } from '../schema/reserved-chrome';
|
|
21
|
+
import { ensureUnsharedChild, ensureUnsharedPath } from './unshare';
|
|
22
|
+
import { resyncChildIds, spliceChildren } from './children';
|
|
23
|
+
import { spliceMany } from './splice-many';
|
|
24
|
+
import { applyStructuralChangeToIdsRefs, replacePreservingFirst } from './structural-change';
|
|
25
|
+
import { assertInvariant } from '../assert';
|
|
26
|
+
import { dropChildSpans } from '../schema/child-spans';
|
|
27
|
+
import { checkSingleNodeSink } from '../invariants/single-node-sink';
|
|
28
|
+
import { checkSplitLanding } from '../invariants/split-landing';
|
|
29
|
+
import { checkStructuralDescriptor } from '../invariants/structural-descriptor';
|
|
30
|
+
const ownerKindOf = (parent) => 'ownerKind' in parent ? parent.ownerKind : undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Text made legal as a child's raw inside a container of kind `ownerKind`. Exported for
|
|
33
|
+
* sinks that build their own bytes rather than handing text to {@link updateNodeContent}.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeBodyWrite(ownerKind, raw) {
|
|
36
|
+
const owner = ownerKind === undefined ? undefined : tryGetBlockKindDescriptor(ownerKind);
|
|
37
|
+
return owner?.bodyWrite?.normalize(raw) ?? raw;
|
|
38
|
+
}
|
|
39
|
+
const forBody = (parent, raw) => normalizeBodyWrite(ownerKindOf(parent), raw);
|
|
40
|
+
/**
|
|
41
|
+
* `raw` made legal as `node`'s OWN bytes — {@link normalizeBodyWrite}'s node-side twin, for the
|
|
42
|
+
* kind's own grammar rather than its container's. Sinks that REPLACE the node with a reparse of
|
|
43
|
+
* the result call this door: the reparse re-derives metadata from the bytes, so structure the
|
|
44
|
+
* rule restores from the old metadata (a fence closer a truncation consumed) must land first.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeOwnRaw(node, raw) {
|
|
47
|
+
return tryGetBlockKindDescriptor(node.kind)?.normalizeRawWrite?.(raw, node) ?? raw;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Write `raw` as `node`'s OWN bytes through its kind's rule — {@link normalizeOwnRaw}'s in-place
|
|
51
|
+
* sink. Every sink writing a leaf's bytes without the kind's surface in front of it owes one of
|
|
52
|
+
* the two (pinned by `lint/leaf-raw-write-rule`).
|
|
53
|
+
*/
|
|
54
|
+
export function writeOwnRaw(node, raw, grammar) {
|
|
55
|
+
const descriptor = tryGetBlockKindDescriptor(node.kind);
|
|
56
|
+
const legal = descriptor?.normalizeRawWrite?.(raw, node) ?? raw;
|
|
57
|
+
node.raw = legal;
|
|
58
|
+
// A context-dependent kind's raw does not reparse to itself, so its metadata was never
|
|
59
|
+
// parse-derived and a fragment parse would only mis-read it.
|
|
60
|
+
if (descriptor?.contextDependentKind)
|
|
61
|
+
return;
|
|
62
|
+
// In place means no reparse replaces the node, so parse-owned metadata re-derives here —
|
|
63
|
+
// for the rule's own rewrite AND bytes the caller's edit already changed.
|
|
64
|
+
const reparsed = parse(legal, { grammar, scope: 'fragment' }).children;
|
|
65
|
+
if (reparsed.length === 1 && reparsed[0].kind === node.kind)
|
|
66
|
+
node.metadata = reparsed[0].metadata;
|
|
67
|
+
}
|
|
68
|
+
// ── Node minting ──
|
|
69
|
+
/**
|
|
70
|
+
* The paragraph mint. Every argument is required: a paragraph's raw ENDS in a line ending,
|
|
71
|
+
* so a mint site must answer which document it lands in (G4.20) rather than strand a lone
|
|
72
|
+
* LF in a CRLF file. Returns a fresh node every call — a shared instance would alias across
|
|
73
|
+
* tree positions and corrupt the snapshot/unshare model (G1.9).
|
|
74
|
+
*/
|
|
75
|
+
export function paragraphNode(leadingTrivia, text, lineEnding) {
|
|
76
|
+
return { kind: 'paragraph', leadingTrivia, raw: text + lineEnding };
|
|
77
|
+
}
|
|
78
|
+
/** The empty-paragraph placeholder keeping an emptied document or container caret-addressable. */
|
|
79
|
+
export function emptyParagraph(leadingTrivia, lineEnding) {
|
|
80
|
+
return paragraphNode(leadingTrivia, '', lineEnding);
|
|
81
|
+
}
|
|
82
|
+
export function nodeAt(doc, path) {
|
|
83
|
+
let cur = doc;
|
|
84
|
+
for (const idx of path) {
|
|
85
|
+
if (!cur.children || idx < 0 || idx >= cur.children.length)
|
|
86
|
+
return null;
|
|
87
|
+
cur = cur.children[idx];
|
|
88
|
+
}
|
|
89
|
+
return cur;
|
|
90
|
+
}
|
|
91
|
+
export function blockNodeAt(doc, path) {
|
|
92
|
+
const node = nodeAt(doc, path);
|
|
93
|
+
return node !== null && isBlockNode(node) ? node : null;
|
|
94
|
+
}
|
|
95
|
+
export function isBlockNode(node) {
|
|
96
|
+
return 'raw' in node;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The landing a site is about to consume, held to the primitive's answer (G1.34). The
|
|
100
|
+
* top-level path seats the caret at it and the list path splices at it, so both cross this
|
|
101
|
+
* on their way to using it; a re-derived `blockIndex + 1` warns instead of shipping.
|
|
102
|
+
*/
|
|
103
|
+
export function assertSplitLanding(split, landing) {
|
|
104
|
+
assertInvariant('split-landing', () => checkSplitLanding(split.secondHalfIndex, landing));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* What a one-slot sink is about to put in its slot, held to one node (G1.35). Asked at the WRITE
|
|
108
|
+
* with the nodes being written, so the guard answers for sink N+1 — one that skips the refusal its
|
|
109
|
+
* siblings make, or splices a plural replacement into a slot that holds one.
|
|
110
|
+
*/
|
|
111
|
+
export function assertSingleNodeSink(sink, installed) {
|
|
112
|
+
assertInvariant('single-node-sink', () => checkSingleNodeSink(sink, installed.length));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Split the node at `blockIndex` at raw `offset` (display-relative). The first half inherits the
|
|
116
|
+
* original ID and the whole structural suffix (a setext underline), which a plain cut would strand
|
|
117
|
+
* below as junk. The second half opens with a blank separator wherever one does structural work
|
|
118
|
+
* ({@link separatorSplitsOffNextLine}) — without it GFM lazy continuation folds the halves back
|
|
119
|
+
* into one block on reload.
|
|
120
|
+
*/
|
|
121
|
+
export function splitNode(parent, blockIndex, offset, sharing, presentationMode, linkRef) {
|
|
122
|
+
const noop = { change: { op: 'noop' }, secondHalfIndex: blockIndex + 1 };
|
|
123
|
+
if (blockIndex < 0 || blockIndex >= parent.children.length)
|
|
124
|
+
return noop;
|
|
125
|
+
const node = parent.children[blockIndex];
|
|
126
|
+
const descriptor = getBlockKindDescriptor(node.kind);
|
|
127
|
+
// A context-dependent kind (tableCell, container chrome) has no standalone recognizer,
|
|
128
|
+
// so the reparse would destroy both halves.
|
|
129
|
+
if (descriptor.contextDependentKind)
|
|
130
|
+
return noop;
|
|
131
|
+
const rawText = node.raw;
|
|
132
|
+
const lineEnding = trailingLineEnding(rawText);
|
|
133
|
+
const cut = cutPastLineEnding(descriptor, node, offset);
|
|
134
|
+
const suffixSplit = structuralSuffixSplit(descriptor, node, cut);
|
|
135
|
+
// Both halves: each can collide alone — a `</details>` stranded on the second half, or
|
|
136
|
+
// a first half promoted to a bare terminator once its trailing text is cut away.
|
|
137
|
+
let firstRaw = forBody(parent, suffixSplit ? suffixSplit.firstRaw : rawText.slice(0, cut));
|
|
138
|
+
let secondRaw = forBody(parent, suffixSplit ? suffixSplit.secondRaw : rawText.slice(cut));
|
|
139
|
+
firstRaw = terminateLine(firstRaw, rawText);
|
|
140
|
+
secondRaw = terminateLine(secondRaw, rawText);
|
|
141
|
+
// Live alone rebalances: there the delimiters around the cut are unpainted, so a byte-literal
|
|
142
|
+
// half would surface runs the reader never saw. The rebalancer verifies its own bytes and
|
|
143
|
+
// declines when they do not parse back, leaving the literal cut every other mode gets.
|
|
144
|
+
if (presentationMode === 'live') {
|
|
145
|
+
const rebalanced = getLiveSplitRebalancer()?.(node, offset, firstRaw, secondRaw, linkRef);
|
|
146
|
+
if (rebalanced) {
|
|
147
|
+
firstRaw = rebalanced.firstRaw;
|
|
148
|
+
secondRaw = rebalanced.secondRaw;
|
|
149
|
+
// The rewrite verifies its halves STANDALONE, where a missing final ending is
|
|
150
|
+
// legal; at the seam the halves would share a line and rejoin on reload.
|
|
151
|
+
firstRaw = terminateLine(firstRaw, rawText);
|
|
152
|
+
secondRaw = terminateLine(secondRaw, rawText);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const separator = splitSeparator(firstRaw, secondRaw, lineEnding, parent.children[blockIndex + 1]);
|
|
156
|
+
const first = reparseAsNodes(firstRaw, node.leadingTrivia);
|
|
157
|
+
// The first half's peeled line stands between the halves, so it is the second half's
|
|
158
|
+
// separator; `separator` answers no when the bytes already end in a blank line.
|
|
159
|
+
const second = reparseAsNodes(secondRaw, first.suffix + separator);
|
|
160
|
+
if (DEV && first.nodes.length > 1) {
|
|
161
|
+
// Legal — the landing index rides the result — but rare enough to keep visible.
|
|
162
|
+
devWarn('tree-ops', `splitNode: the first half parsed to ${first.nodes.length} blocks`);
|
|
163
|
+
}
|
|
164
|
+
const nodes = [...first.nodes, ...second.nodes];
|
|
165
|
+
// The second half's peeled line has no follower inside the splice, so it stays in raw.
|
|
166
|
+
nodes[nodes.length - 1].raw += second.suffix;
|
|
167
|
+
const splitTail = blockIndex === parent.children.length - 1;
|
|
168
|
+
parent.children.splice(blockIndex, 1, ...nodes);
|
|
169
|
+
// Floor at the seam itself: a wider window would reach back into the spliced set and
|
|
170
|
+
// break the one-window accounting below. At the tail there is no successor to re-read, and
|
|
171
|
+
// the document's folded line is the settle funnel's question, not this sink's.
|
|
172
|
+
const seamLeft = blockIndex + nodes.length - 1;
|
|
173
|
+
const eaten = splitTail ? 0 : absorbSeamReading(parent, seamLeft, seamLeft, sharing).eaten;
|
|
174
|
+
return {
|
|
175
|
+
change: replacePreservingFirst(blockIndex, 1 + eaten, nodes.length),
|
|
176
|
+
secondHalfIndex: blockIndex + first.nodes.length
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The cut a split makes, given the caret's `offset`: an ending the offset lands ON terminates
|
|
181
|
+
* the FIRST half rather than opening the second, which would mint a blank line nobody typed.
|
|
182
|
+
* A CRLF is one boundary, so a cut between its bytes moves past both. Clamped to a content
|
|
183
|
+
* range's end, past which the offset stops being a content position at all.
|
|
184
|
+
*/
|
|
185
|
+
function cutPastLineEnding(descriptor, node, offset) {
|
|
186
|
+
const raw = node.raw;
|
|
187
|
+
// A surrogate pair is one boundary too: a cut through it leaves each half in a block of its
|
|
188
|
+
// own, where nothing can put them back.
|
|
189
|
+
const at = snapToScalarBoundary(raw, offset);
|
|
190
|
+
const ending = raw[at] === '\n' ? '\n' : raw.startsWith('\r\n', at) ? '\r\n' : '';
|
|
191
|
+
if (ending === '')
|
|
192
|
+
return at;
|
|
193
|
+
const contentEnd = descriptor.getContentRange?.(node).end;
|
|
194
|
+
return contentEnd === undefined ? at + ending.length : Math.min(at + ending.length, contentEnd);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The stand-in for whatever the user types into the second half: the maximally-continuable
|
|
198
|
+
* line, so the predicate answers for the worst case rather than one construct. Openers are
|
|
199
|
+
* arbitrary code, so no line is unclaimable by construction — {@link probeLineOpensAsProse}
|
|
200
|
+
* is the runtime check.
|
|
201
|
+
*/
|
|
202
|
+
export const NEXT_PROSE_LINE = 'x';
|
|
203
|
+
/** Whether the ambient grammar still leaves {@link NEXT_PROSE_LINE} an ordinary paragraph. */
|
|
204
|
+
export function probeLineOpensAsProse(grammar) {
|
|
205
|
+
return lineOpensAs(NEXT_PROSE_LINE, grammar) === 'paragraph';
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Leading trivia for a freshly minted BLANK block. A blank line is a block only past its
|
|
209
|
+
* run's first line, so it separates from a non-blank predecessor — unless a run is already
|
|
210
|
+
* open below, where the successor's own separator opens it.
|
|
211
|
+
*/
|
|
212
|
+
function blankBlockTrivia(predecessorIsBlank, successor, lineEnding) {
|
|
213
|
+
if (predecessorIsBlank)
|
|
214
|
+
return '';
|
|
215
|
+
const runOpenBelow = successor !== undefined && (successor.leadingTrivia !== '' || isBlankParagraph(successor));
|
|
216
|
+
return runOpenBelow ? '' : lineEnding;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The second half's leading trivia. A blank half follows {@link blankBlockTrivia}; a prose
|
|
220
|
+
* half takes a separator exactly when lazy continuation would fold the halves back together.
|
|
221
|
+
*/
|
|
222
|
+
function splitSeparator(firstRaw, secondRaw, lineEnding, successor) {
|
|
223
|
+
if (isBlankSource(secondRaw)) {
|
|
224
|
+
const trivia = blankBlockTrivia(isBlankSource(firstRaw), successor, lineEnding);
|
|
225
|
+
// A body that swallows blank lines (an unclosed fence) takes the separator inside
|
|
226
|
+
// itself and gains nothing, so ask the bytes rather than assume.
|
|
227
|
+
return trivia !== '' && blankHalfBecomesBlock(firstRaw, secondRaw, lineEnding) ? trivia : '';
|
|
228
|
+
}
|
|
229
|
+
return separatorSplitsOffNextLine(firstRaw, secondRaw, lineEnding) ? lineEnding : '';
|
|
230
|
+
}
|
|
231
|
+
function blankHalfBecomesBlock(firstRaw, secondRaw, lineEnding) {
|
|
232
|
+
return (parse(firstRaw + lineEnding + secondRaw, { scope: 'fragment' }).children.length >
|
|
233
|
+
parse(firstRaw + secondRaw, { scope: 'fragment' }).children.length);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Would a blank line between `raw` and the line after it split off a second block? Asked of the
|
|
237
|
+
* bytes, never a kind list: a construct whose body swallows both forms alike answers no on its
|
|
238
|
+
* own, so the separator never lands inside a body. Blank blocks are discounted on both sides —
|
|
239
|
+
* the separator materializes as one, and counting it would answer yes for every raw.
|
|
240
|
+
*/
|
|
241
|
+
function separatorSplitsOffNextLine(raw, secondRaw, lineEnding) {
|
|
242
|
+
if (DEV && !probeLineOpensAsProse()) {
|
|
243
|
+
devWarn('tree-ops', `a registered opener claims ${JSON.stringify(NEXT_PROSE_LINE)}, so the split-separator probe no longer stands in for prose`);
|
|
244
|
+
}
|
|
245
|
+
// Both lines that will ever sit under `raw`: the second half's actual head (a promoted table
|
|
246
|
+
// absorbs a pipe-bearing one), and the prose stand-in for whatever a later edit puts there.
|
|
247
|
+
const probes = [secondRaw.slice(0, secondRaw.indexOf('\n') + 1), NEXT_PROSE_LINE + lineEnding];
|
|
248
|
+
return probes.some((probe) => contentBlockCount(raw + lineEnding + probe) > contentBlockCount(raw + probe));
|
|
249
|
+
}
|
|
250
|
+
function contentBlockCount(source) {
|
|
251
|
+
return parse(source, { scope: 'fragment' }).children.filter((node) => !isBlankParagraph(node))
|
|
252
|
+
.length;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A split that keeps a kind's structural suffix — raw beyond its content range, the setext
|
|
256
|
+
* underline — on the first half. Null when the kind has no suffix, or the offset is at block
|
|
257
|
+
* start or inside the suffix itself; both keep the plain raw cut.
|
|
258
|
+
*/
|
|
259
|
+
function structuralSuffixSplit(descriptor, node, offset) {
|
|
260
|
+
const getRange = descriptor.getContentRange;
|
|
261
|
+
if (!getRange)
|
|
262
|
+
return null;
|
|
263
|
+
const raw = node.raw;
|
|
264
|
+
const contentEnd = getRange(node).end;
|
|
265
|
+
if (contentEnd >= displayLength(raw) || offset <= 0 || offset > contentEnd)
|
|
266
|
+
return null;
|
|
267
|
+
// A remainder opening with a whitespace-only line reloads as blank: the cut
|
|
268
|
+
// consumes that whitespace into the first half, as it does a bare line ending.
|
|
269
|
+
const wsLine = /^[ \t]+\r?\n/.exec(raw.slice(offset, contentEnd))?.[0] ?? '';
|
|
270
|
+
return {
|
|
271
|
+
// The retained suffix opens with the ending of the line it follows, so a cut sitting
|
|
272
|
+
// just past one would double it into a blank line and strand the suffix below.
|
|
273
|
+
firstRaw: trimTrailingLineEnding(raw.slice(0, offset) + trimTrailingLineEnding(wsLine)) +
|
|
274
|
+
raw.slice(contentEnd),
|
|
275
|
+
secondRaw: raw.slice(offset + wsLine.length, contentEnd)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// ── Merge ──
|
|
279
|
+
/**
|
|
280
|
+
* `join.mergedRaw` with the delimiter runs the join orphaned at its seam dropped — live only,
|
|
281
|
+
* where those runs are unpainted and a literal concatenation surfaces bytes the reader never saw
|
|
282
|
+
* (live-mode.md § 4.5). The one registered cleaner verifies its own bytes and otherwise declines,
|
|
283
|
+
* leaving the literal join every other mode gets. Every destructive join crosses this door.
|
|
284
|
+
*/
|
|
285
|
+
export function cleanJoinedRaw(join, presentationMode) {
|
|
286
|
+
const literal = { raw: join.mergedRaw, seam: join.seam };
|
|
287
|
+
if (presentationMode !== 'live')
|
|
288
|
+
return literal;
|
|
289
|
+
return getLiveJoinSeamCleaner()?.(join) ?? literal;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* The bytes a single-block edit leaves when it deletes `range` out of `display`. A delete-then-
|
|
293
|
+
* insert is a join like any other, so it crosses the same cleanup, and the returned offset is
|
|
294
|
+
* where the two sides now meet (a cleanup that drops a run on the first side moves it).
|
|
295
|
+
*/
|
|
296
|
+
export function cutRangeFromDisplay(node, display, range, presentationMode, linkRef) {
|
|
297
|
+
// Both ends off any scalar interior before the slice: a half-pair here is unrecoverable
|
|
298
|
+
// bytes, not a recoverable edit. Snapping the same direction cannot invert the range.
|
|
299
|
+
const start = snapToScalarBoundary(display, range.start);
|
|
300
|
+
const end = snapToScalarBoundary(display, range.end);
|
|
301
|
+
if (start >= end)
|
|
302
|
+
return { display, offset: start };
|
|
303
|
+
const cleaned = cleanJoinedRaw({
|
|
304
|
+
mergedRaw: display.slice(0, start) + display.slice(end),
|
|
305
|
+
seam: start,
|
|
306
|
+
start: { node, offset: start },
|
|
307
|
+
end: { node, offset: end },
|
|
308
|
+
linkRef
|
|
309
|
+
}, presentationMode);
|
|
310
|
+
return { display: cleaned.raw, offset: cleaned.seam };
|
|
311
|
+
}
|
|
312
|
+
/** The bytes two adjacent blocks make when one absorbs the other, seam cleanup included. */
|
|
313
|
+
function joinRaw(prev, curr, presentationMode, linkRef) {
|
|
314
|
+
const seam = displayLength(prev.raw);
|
|
315
|
+
return cleanJoinedRaw({
|
|
316
|
+
mergedRaw: prev.raw.slice(0, seam) + curr.raw,
|
|
317
|
+
seam,
|
|
318
|
+
start: { node: prev, offset: seam },
|
|
319
|
+
end: { node: curr, offset: 0 },
|
|
320
|
+
linkRef
|
|
321
|
+
}, presentationMode);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Merge `curr` into the deepest prose leaf of `prev`, writing into that leaf rather than
|
|
325
|
+
* reparsing concatenated raw — preserves prev's component identity, IME state, and the
|
|
326
|
+
* leaves' inline caches. Pass `sharing` to unshare everything the merge writes. Null when
|
|
327
|
+
* no mergeable leaf exists, so the caller can fall back to move-focus.
|
|
328
|
+
*/
|
|
329
|
+
export function mergeIntoPrevDeepLeaf(parent, blockIndex, sharing, presentationMode, linkRef, grammar) {
|
|
330
|
+
if (blockIndex <= 0 || blockIndex >= parent.children.length)
|
|
331
|
+
return null;
|
|
332
|
+
const mergeTarget = findMergeTarget(parent.children[blockIndex - 1]);
|
|
333
|
+
if (!mergeTarget)
|
|
334
|
+
return null;
|
|
335
|
+
const leafPath = [blockIndex - 1, ...mergeTarget.path];
|
|
336
|
+
const slot = leafPath[leafPath.length - 1];
|
|
337
|
+
// The verdict comes off the LIVE tree, ahead of the unshare: an unshared spine is a write,
|
|
338
|
+
// and a refused join must leave the pair exactly as it stands.
|
|
339
|
+
const target = holderChildrenAt(parent.children, leafPath)[slot];
|
|
340
|
+
const curr = parent.children[blockIndex];
|
|
341
|
+
const lineEnding = trailingLineEnding(target.raw);
|
|
342
|
+
const { raw: mergedRaw, seam: joinOffset } = joinRaw(target, curr, presentationMode, linkRef);
|
|
343
|
+
const merged = mergedLeafFor(target, trimTrailingLineEnding(mergedRaw) + lineEnding, grammar);
|
|
344
|
+
if (!merged)
|
|
345
|
+
return null;
|
|
346
|
+
// The merge writes the deep leaf's raw plus every spine ancestor's rebuilt raw, so
|
|
347
|
+
// unshare the whole spine and resolve through the owned copies.
|
|
348
|
+
if (sharing)
|
|
349
|
+
ensureUnsharedPath(parent, leafPath, sharing);
|
|
350
|
+
// Write-then-re-read (tree-operations/unshare.ts header), down to the leaf's own slot
|
|
351
|
+
// so a kind change can mint into it.
|
|
352
|
+
installMergedLeaf(holderChildrenAt(parent.children, leafPath), slot, merged, sharing);
|
|
353
|
+
if (mergeTarget.path.length > 0) {
|
|
354
|
+
rebuildAncestryRaw(parent.children[blockIndex - 1], mergeTarget.path);
|
|
355
|
+
}
|
|
356
|
+
const change = deleteNode(parent, blockIndex, sharing);
|
|
357
|
+
return { targetPath: mergeTarget.path, joinOffset, change };
|
|
358
|
+
}
|
|
359
|
+
/** The children array holding `path`'s last slot, walked from `children`. */
|
|
360
|
+
function holderChildrenAt(children, path) {
|
|
361
|
+
let holder = children;
|
|
362
|
+
for (const index of path.slice(0, -1))
|
|
363
|
+
holder = holder[index].children;
|
|
364
|
+
return holder;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The deep-leaf merge's verdict: the absorbed bytes cross the kind's own rule and a fragment
|
|
368
|
+
* reparse. Null when they read as several blocks — the leaf is one slot, and writing them
|
|
369
|
+
* whole leaves a tree its own reload disagrees with (G1.35).
|
|
370
|
+
*/
|
|
371
|
+
function mergedLeafFor(target, raw, grammar) {
|
|
372
|
+
const written = normalizeOwnRaw(target, raw);
|
|
373
|
+
// A context-dependent kind has no standalone recognizer, so its bytes are never read back
|
|
374
|
+
// as blocks and the write keeps the kind.
|
|
375
|
+
if (tryGetBlockKindDescriptor(target.kind)?.contextDependentKind) {
|
|
376
|
+
return { written, blocks: [] };
|
|
377
|
+
}
|
|
378
|
+
const blocks = parse(written, { grammar, scope: 'fragment' }).children;
|
|
379
|
+
return blocks.length > 1 ? null : { written, blocks };
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Refresh a node in place from its own reparse, keeping its object identity. The id resync is
|
|
383
|
+
* unconditional: the reparse can change the child count while the caller reports `noop`, and the
|
|
384
|
+
* fresh children carry no `childIds` of their own.
|
|
385
|
+
*/
|
|
386
|
+
function adoptReparsedFields(target, parsed) {
|
|
387
|
+
target.metadata = parsed?.metadata;
|
|
388
|
+
target.children = parsed?.children;
|
|
389
|
+
resyncChildIds(target);
|
|
390
|
+
assignChildIdsDeep(target);
|
|
391
|
+
target.innerPrefix = parsed?.innerPrefix;
|
|
392
|
+
target.innerSuffix = parsed?.innerSuffix;
|
|
393
|
+
}
|
|
394
|
+
/** {@link mergedLeafFor}'s write, over the unshared spine the verdict was taken ahead of. */
|
|
395
|
+
function installMergedLeaf(holderChildren, slot, merged, sharing) {
|
|
396
|
+
const target = holderChildren[slot];
|
|
397
|
+
const { written, blocks } = merged;
|
|
398
|
+
assertSingleNodeSink('mergedLeafFor', blocks);
|
|
399
|
+
const parsed = blocks[0];
|
|
400
|
+
if (parsed && parsed.kind !== target.kind) {
|
|
401
|
+
// Byte-honest over the fragment peel, the single-slot sink's rule.
|
|
402
|
+
parsed.raw = written;
|
|
403
|
+
parsed.leadingTrivia = target.leadingTrivia;
|
|
404
|
+
ensureEditableContainers(parsed);
|
|
405
|
+
if (sharing)
|
|
406
|
+
sharing.stamp(parsed);
|
|
407
|
+
assignChildIdsDeep(parsed);
|
|
408
|
+
holderChildren[slot] = parsed;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
target.raw = written;
|
|
412
|
+
if (!parsed)
|
|
413
|
+
return;
|
|
414
|
+
adoptReparsedFields(target, parsed);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Merge the node at `blockIndex` with its successor; combined raw is re-parsed and the
|
|
418
|
+
* merged block inherits the current block's ID. Noop at the tail.
|
|
419
|
+
*/
|
|
420
|
+
export function mergeWithNext(parent, blockIndex, presentationMode, linkRef) {
|
|
421
|
+
if (blockIndex < 0 || blockIndex >= parent.children.length - 1) {
|
|
422
|
+
return { change: { op: 'noop' }, joinOffset: 0 };
|
|
423
|
+
}
|
|
424
|
+
const curr = parent.children[blockIndex];
|
|
425
|
+
const next = parent.children[blockIndex + 1];
|
|
426
|
+
const { raw: mergedRaw, seam } = joinRaw(curr, next, presentationMode, linkRef);
|
|
427
|
+
const mergedNode = reparseAsNode(mergedRaw, curr.leadingTrivia);
|
|
428
|
+
if (!mergedNode)
|
|
429
|
+
return { change: { op: 'noop' }, joinOffset: 0 };
|
|
430
|
+
const installed = [mergedNode];
|
|
431
|
+
assertSingleNodeSink('mergeWithNext', installed);
|
|
432
|
+
parent.children.splice(blockIndex, 2, ...installed);
|
|
433
|
+
return { change: replacePreservingFirst(blockIndex, 2, 1), joinOffset: seam };
|
|
434
|
+
}
|
|
435
|
+
// ── Separators ──
|
|
436
|
+
/**
|
|
437
|
+
* Settle the separator at `index`: nothing above it needs one at the body head or below a blank
|
|
438
|
+
* block, where the parser would read the extra line as one more empty paragraph. Every splice
|
|
439
|
+
* that changes what precedes a block settles through this family (syntax-tree.md § Blank lines).
|
|
440
|
+
* `sharing` owns the write (G1.9).
|
|
441
|
+
*/
|
|
442
|
+
export function clearRedundantSeparator(parent, index, sharing) {
|
|
443
|
+
retireChildSpans(parent);
|
|
444
|
+
const node = parent.children?.[index];
|
|
445
|
+
if (!node || node.leadingTrivia === '')
|
|
446
|
+
return;
|
|
447
|
+
const bodyStart = bodyStartIndex(parent);
|
|
448
|
+
if (index < bodyStart)
|
|
449
|
+
return;
|
|
450
|
+
const predecessor = index > bodyStart ? parent.children[index - 1] : undefined;
|
|
451
|
+
if (predecessor !== undefined && !isBlankParagraph(predecessor))
|
|
452
|
+
return;
|
|
453
|
+
const owned = sharing ? ensureUnsharedChild(parent, index, sharing) : node;
|
|
454
|
+
const freed = owned.leadingTrivia;
|
|
455
|
+
owned.leadingTrivia = '';
|
|
456
|
+
absorbWrapPrefix(parent, bodyStart, index, freed);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* A blank block IS a blank line, so it and its follower share ONE separator: two of them reload
|
|
460
|
+
* as a second empty paragraph (G2.13). The follower's is the one that stands, so a later fill of
|
|
461
|
+
* this slot (a paste over a cut range) still finds the follower separated. The run-level twin
|
|
462
|
+
* {@link settleSeparatorOnBlank} keeps the first already-standing line instead, the same bytes
|
|
463
|
+
* the other way round.
|
|
464
|
+
*/
|
|
465
|
+
export function dropDoubledSeparator(parent, index, sharing) {
|
|
466
|
+
retireChildSpans(parent);
|
|
467
|
+
const node = parent.children?.[index];
|
|
468
|
+
if (!node || node.leadingTrivia === '' || !isBlankParagraph(node))
|
|
469
|
+
return;
|
|
470
|
+
if ((parent.children?.[index + 1]?.leadingTrivia ?? '') === '')
|
|
471
|
+
return;
|
|
472
|
+
const owned = sharing ? ensureUnsharedChild(parent, index, sharing) : node;
|
|
473
|
+
owned.leadingTrivia = '';
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* The separator a block takes back when it stops being blank: its own blank line was what stood
|
|
477
|
+
* between it and a non-blank predecessor. Call at the fill — {@link clearRedundantSeparator}
|
|
478
|
+
* frees a separator in exactly the cases this declines, and a block that was never blank keeps
|
|
479
|
+
* whatever its bytes earned (a paragraph under a heading needs none).
|
|
480
|
+
*/
|
|
481
|
+
export function restoreSeparatorOnFill(parent, index, sharing) {
|
|
482
|
+
retireChildSpans(parent);
|
|
483
|
+
const node = parent.children?.[index];
|
|
484
|
+
if (!node || node.leadingTrivia !== '' || isBlankParagraph(node))
|
|
485
|
+
return;
|
|
486
|
+
mintSeparator(parent, index, sharing);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* The separator the block BELOW a consumed blank line takes back — the same mint minus the
|
|
490
|
+
* blank-self guard, since a blank follower needs the line as much as a prose one. It takes one
|
|
491
|
+
* only where its own follower holds none: two would reload as a second empty paragraph
|
|
492
|
+
* ({@link dropDoubledSeparator}'s rule, declined here rather than undone after).
|
|
493
|
+
*/
|
|
494
|
+
function restoreSeparatorAfterBlank(parent, index, sharing) {
|
|
495
|
+
retireChildSpans(parent);
|
|
496
|
+
const children = parent.children;
|
|
497
|
+
const node = children?.[index];
|
|
498
|
+
if (!children || !node || node.leadingTrivia !== '')
|
|
499
|
+
return;
|
|
500
|
+
if (isBlankParagraph(node) && (children[index + 1]?.leadingTrivia ?? '') !== '')
|
|
501
|
+
return;
|
|
502
|
+
mintSeparator(parent, index, sharing);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* The settle a block turning INTO a blank line owes: it joins the blank run around it, and a run
|
|
506
|
+
* carries exactly ONE separating line — across every block in it AND its follower, since a blank
|
|
507
|
+
* block is the follower's line too. The line already standing is the one kept, wherever in the
|
|
508
|
+
* run it sits; a mint lands at the run's head, the only slot one may take.
|
|
509
|
+
*/
|
|
510
|
+
export function settleSeparatorOnBlank(parent, index, sharing) {
|
|
511
|
+
retireChildSpans(parent);
|
|
512
|
+
const children = parent.children;
|
|
513
|
+
const node = children?.[index];
|
|
514
|
+
if (!children || !node || !isBlankParagraph(node))
|
|
515
|
+
return;
|
|
516
|
+
const bodyStart = bodyStartIndex(parent);
|
|
517
|
+
let start = index;
|
|
518
|
+
while (start > bodyStart && isBlankParagraph(children[start - 1]))
|
|
519
|
+
start--;
|
|
520
|
+
let end = index;
|
|
521
|
+
while (end + 1 < children.length && isBlankParagraph(children[end + 1]))
|
|
522
|
+
end++;
|
|
523
|
+
const standing = [];
|
|
524
|
+
for (let i = start; i <= Math.min(end + 1, children.length - 1); i++) {
|
|
525
|
+
if (children[i].leadingTrivia !== '')
|
|
526
|
+
standing.push(i);
|
|
527
|
+
}
|
|
528
|
+
// A chrome line bounding the run beside PROSE eats one line as the wrap's peel
|
|
529
|
+
// (`innerPrefix`/`innerSuffix`), on top of the run's own count; an all-blank body owes none.
|
|
530
|
+
const wrap = bodyWrapOf(parent);
|
|
531
|
+
const slots = wrapSlotsOf(parent);
|
|
532
|
+
const bodyEnd = children.length - 1;
|
|
533
|
+
// A run of two or more that IS the whole body sits against both chrome lines, and the reload
|
|
534
|
+
// peels one line into each slot before it materializes a block — so it owes BOTH, where an
|
|
535
|
+
// arm bounded by prose on one side grants at most one.
|
|
536
|
+
const twoPeelBody = !!slots &&
|
|
537
|
+
wrap?.afterOpenerLine === true &&
|
|
538
|
+
wrap.beforeCloserLine === true &&
|
|
539
|
+
start === bodyStart &&
|
|
540
|
+
end === bodyEnd &&
|
|
541
|
+
start < end;
|
|
542
|
+
const tailBelowProse = start > bodyStart && end === bodyEnd;
|
|
543
|
+
if (slots && wrap?.beforeCloserLine && (tailBelowProse || twoPeelBody) && !slots.innerSuffix) {
|
|
544
|
+
slots.innerSuffix = trailingLineEnding(children[end].raw);
|
|
545
|
+
}
|
|
546
|
+
// The reverse: a deletion can leave a lone blank as the WHOLE body, where the closer
|
|
547
|
+
// peel no longer engages beside the opener's — the run gives the extra line back.
|
|
548
|
+
const loneBlankBody = start === bodyStart && end === bodyEnd && start === end;
|
|
549
|
+
if (slots && loneBlankBody && slots.innerSuffix && (slots.innerPrefix || standing.length > 0)) {
|
|
550
|
+
slots.innerSuffix = '';
|
|
551
|
+
}
|
|
552
|
+
const headUnderWrap = !!slots && wrap?.afterOpenerLine === true && start === bodyStart && end < bodyEnd;
|
|
553
|
+
// A line already standing IS the opener's peel on reload, so taking one as well would add a
|
|
554
|
+
// line — but a two-peel body's count comes out of the slots, not out of the run.
|
|
555
|
+
const takesOpenerPeel = twoPeelBody || (headUnderWrap && standing.length === 0);
|
|
556
|
+
if (slots && takesOpenerPeel && !slots.innerPrefix) {
|
|
557
|
+
slots.innerPrefix = trailingLineEnding(children[start].raw);
|
|
558
|
+
}
|
|
559
|
+
// Under the wrap the run keeps exactly the one peel line — in `innerPrefix` or still
|
|
560
|
+
// standing; elsewhere a run with no LINE above it (the document head, a plain container's
|
|
561
|
+
// body head) separates from nothing and materializes in full.
|
|
562
|
+
let wanted;
|
|
563
|
+
if (twoPeelBody)
|
|
564
|
+
wanted = 0;
|
|
565
|
+
else if (headUnderWrap)
|
|
566
|
+
wanted = slots?.innerPrefix ? 0 : 1;
|
|
567
|
+
else
|
|
568
|
+
wanted = start > 0 || wrap?.afterOpenerLine ? 1 : 0;
|
|
569
|
+
if (standing.length < wanted) {
|
|
570
|
+
mintSeparator(parent, start, sharing);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
for (const at of standing.slice(wanted)) {
|
|
574
|
+
const owned = sharing ? ensureUnsharedChild(parent, at, sharing) : children[at];
|
|
575
|
+
owned.leadingTrivia = '';
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
materializeTailSuffix(parent, sharing);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* The give-back twin of {@link settleSeparatorOnBlank}'s closer peel: a blank run reaching the
|
|
582
|
+
* body tail borrows a line into `innerSuffix` so the reload keeps the block, and a tail that
|
|
583
|
+
* stops being blank owes it back or the wrap emits a line nobody typed. A trailing blank the
|
|
584
|
+
* author wrote against the closer reaches the same shape and is spent here too — one cosmetic
|
|
585
|
+
* line, against a stray one after every tail split.
|
|
586
|
+
*/
|
|
587
|
+
function releaseWrapPeel(parent, index) {
|
|
588
|
+
retireChildSpans(parent);
|
|
589
|
+
const children = parent.children;
|
|
590
|
+
const slots = wrapSlotsOf(parent);
|
|
591
|
+
if (!children || children.length === 0 || !slots?.innerSuffix)
|
|
592
|
+
return;
|
|
593
|
+
if (!bodyWrapOf(parent)?.beforeCloserLine)
|
|
594
|
+
return;
|
|
595
|
+
if (index < children.length - 1)
|
|
596
|
+
return;
|
|
597
|
+
if (isBlankParagraph(children[children.length - 1]))
|
|
598
|
+
return;
|
|
599
|
+
slots.innerSuffix = '';
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* The parse folds the document's one trailing blank line into `suffix` only while the tail
|
|
603
|
+
* block is non-blank (`parseBlocks`' separator-spent rule); once the tail turns blank the
|
|
604
|
+
* reload reads that line as its own empty paragraph, so the settle materializes it.
|
|
605
|
+
* Document-level slot only — a container's `innerSuffix` twin stays with the wrap arms.
|
|
606
|
+
* Returns the blocks appended.
|
|
607
|
+
*/
|
|
608
|
+
function materializeTailSuffix(parent, sharing) {
|
|
609
|
+
retireChildSpans(parent);
|
|
610
|
+
const children = parent.children;
|
|
611
|
+
const suffix = parent.suffix;
|
|
612
|
+
if (!children || !suffix)
|
|
613
|
+
return 0;
|
|
614
|
+
// An emptied parent has no tail for the line to fold against, so it is the document's whole
|
|
615
|
+
// content and the reload reads it as the one block there is.
|
|
616
|
+
if (children.length > 0 && !isBlankParagraph(children[children.length - 1]))
|
|
617
|
+
return 0;
|
|
618
|
+
const minted = { kind: 'paragraph', leadingTrivia: '', raw: suffix };
|
|
619
|
+
if (sharing)
|
|
620
|
+
sharing.stamp(minted);
|
|
621
|
+
children.push(minted);
|
|
622
|
+
parent.suffix = '';
|
|
623
|
+
return 1;
|
|
624
|
+
}
|
|
625
|
+
// ── The splice settle funnel ──
|
|
626
|
+
/**
|
|
627
|
+
* The settle every splice owes its neighbourhood: the vacated separating line hands down, a
|
|
628
|
+
* now-redundant one frees, the two debts a consumed blank line leaves are paid (syntax-tree.md
|
|
629
|
+
* § Blank lines), the folded tail line materializes, and the window's joins are asked. `removed`
|
|
630
|
+
* is the pre-splice span, the only place was-blank survives a splice. `tracked` rides the folds
|
|
631
|
+
* for a door landing a caret in the spliced bytes. Returns `change` widened by everything the
|
|
632
|
+
* settle itself did.
|
|
633
|
+
*/
|
|
634
|
+
function settleSplicedWindow(parent, at, removed, added, change, sharing, tracked) {
|
|
635
|
+
if (!parent.children)
|
|
636
|
+
return change;
|
|
637
|
+
// Ahead of the arms: `settleSeparatorOnBlank` materializes the tail line itself, so a count
|
|
638
|
+
// read after it would leave that growth outside the window the sink reports.
|
|
639
|
+
const beforeMint = parent.children.length;
|
|
640
|
+
handDownVacatedSeparator(parent, at, removed[0]?.leadingTrivia ?? '', sharing);
|
|
641
|
+
clearRedundantSeparator(parent, at, sharing);
|
|
642
|
+
if (removed.some(isBlankParagraph)) {
|
|
643
|
+
// Both ends: the line was the slot's own AND the one below it stood on.
|
|
644
|
+
restoreSeparatorAfterBlank(parent, at, sharing);
|
|
645
|
+
if (added > 0)
|
|
646
|
+
restoreSeparatorAfterBlank(parent, at + added, sharing);
|
|
647
|
+
releaseWrapPeel(parent, at + Math.max(added - 1, 0));
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
settleSeparatorOnBlank(parent, at + Math.max(added - 1, 0), sharing);
|
|
651
|
+
}
|
|
652
|
+
// Unconditional, and the funnel's only home for it: every arm above probes a slot inside the
|
|
653
|
+
// window, and a delete window at the tail has none — the question is about the parent's LAST
|
|
654
|
+
// block, which no window position answers.
|
|
655
|
+
materializeTailSuffix(parent, sharing);
|
|
656
|
+
const widened = widenForTailMint(change, beforeMint, parent.children.length);
|
|
657
|
+
// The seam question is part of SETTLING, not a rule each door carries: paste, replace, delete
|
|
658
|
+
// and fill all splice through here, so door N+1 inherits it. A byte-shaped write
|
|
659
|
+
// reporting `noop` splices no window and is asked nothing.
|
|
660
|
+
return absorbWindowSeams(parent, at, added, at, widened, sharing, tracked,
|
|
661
|
+
// A one-slot window names the one block whose bytes changed, which is what lets the seam
|
|
662
|
+
// above decline on its first line; a plural one names no single block.
|
|
663
|
+
added === 1 ? at : undefined).change;
|
|
664
|
+
}
|
|
665
|
+
/** Whoever takes the slot inherits its line, having none of its own — {@link deleteNode}'s rule. */
|
|
666
|
+
function handDownVacatedSeparator(parent, at, vacated, sharing) {
|
|
667
|
+
retireChildSpans(parent);
|
|
668
|
+
const heir = parent.children?.[at];
|
|
669
|
+
if (vacated === '' || !heir || heir.leadingTrivia !== '')
|
|
670
|
+
return;
|
|
671
|
+
const owned = sharing ? ensureUnsharedChild(parent, at, sharing) : heir;
|
|
672
|
+
owned.leadingTrivia = vacated;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* The commit ceremony's settle door: derive the spliced window from the change and settle it
|
|
676
|
+
* against `before`, the pre-mutate children the ceremony still holds. Nodes surviving inside the
|
|
677
|
+
* window are not removals, so a coarse descriptor over an in-place write settles as one.
|
|
678
|
+
* `tracked` is the committing door's caret, carried through the folds. Returns the change widened
|
|
679
|
+
* by a tail line the settle materialized.
|
|
680
|
+
*/
|
|
681
|
+
export function settleSeparator(parent, before, change, sharing, tracked) {
|
|
682
|
+
const window = splicedWindow(change);
|
|
683
|
+
const children = parent.children;
|
|
684
|
+
if (!window || !children)
|
|
685
|
+
return change;
|
|
686
|
+
// At the funnel's door, ahead of every arm: a window the mutate mis-derived reads `before`
|
|
687
|
+
// out of bounds and hands the arms a negative span, which each would clamp into silence.
|
|
688
|
+
assertInvariant('structural-descriptor', () => checkStructuralDescriptor(change, before.length));
|
|
689
|
+
const survivors = new Set(children.slice(window.at, window.at + window.added));
|
|
690
|
+
const removed = before
|
|
691
|
+
.slice(window.at, window.at + window.removed)
|
|
692
|
+
.filter((node) => !survivors.has(node));
|
|
693
|
+
return settleSplicedWindow(parent, window.at, removed, window.added, change, sharing, tracked);
|
|
694
|
+
}
|
|
695
|
+
function splicedWindow(change) {
|
|
696
|
+
switch (change.op) {
|
|
697
|
+
case 'noop':
|
|
698
|
+
return null;
|
|
699
|
+
case 'insert':
|
|
700
|
+
return { at: change.at, removed: 0, added: change.count };
|
|
701
|
+
case 'delete':
|
|
702
|
+
return { at: change.at, removed: change.count, added: 0 };
|
|
703
|
+
case 'replace':
|
|
704
|
+
return { at: change.at, removed: change.count, added: change.newCount };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* The out-of-commit-scope twin of {@link settleSeparator}, for a container discovered by walking
|
|
709
|
+
* the live tree: it splices through the `childIds` door and reads the pre-splice span itself.
|
|
710
|
+
*/
|
|
711
|
+
export function spliceChildrenSettled(parent, at, removeCount, replacement, sharing) {
|
|
712
|
+
const children = parent.children;
|
|
713
|
+
if (!children || at < 0 || at > children.length)
|
|
714
|
+
return;
|
|
715
|
+
const removed = children.slice(at, at + removeCount);
|
|
716
|
+
spliceChildren(parent, at, removeCount, replacement);
|
|
717
|
+
// `noop` in, so what comes back describes the SETTLE alone: `spliceChildren` already carried
|
|
718
|
+
// the door's own splice into `childIds`, and out of commit scope nothing else publishes.
|
|
719
|
+
const settled = settleSplicedWindow(parent, at, removed, replacement.length, { op: 'noop' }, sharing);
|
|
720
|
+
const ids = parent.childIds;
|
|
721
|
+
if (ids)
|
|
722
|
+
applyStructuralChangeToIdsRefs(settled, ids, new Array(ids.length));
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* A splice can leave neighbours whose adjacent bytes re-read as fewer blocks on reload — a list
|
|
726
|
+
* standing above indented code absorbs it, and no separator can hold indentation apart.
|
|
727
|
+
* Absorb while the window's own bytes parse to fewer blocks, which is the reload's reading;
|
|
728
|
+
* byte-identical by construction. A blank run is transparent to a container's continuation, so the
|
|
729
|
+
* window anchors at the nearest non-blank block above the seam, never below `floor`, and cascades.
|
|
730
|
+
*/
|
|
731
|
+
function absorbSeamReading(parent, seamLeft, floor, sharing, tracked, headProbe, onBeforeSplice) {
|
|
732
|
+
const children = parent.children;
|
|
733
|
+
if (seamLeft < 0)
|
|
734
|
+
return { at: 0, span: 0, eaten: 0, spliced: false };
|
|
735
|
+
let left = seamLeft;
|
|
736
|
+
while (left > floor && isBlankParagraph(children[left]))
|
|
737
|
+
left--;
|
|
738
|
+
const at = left;
|
|
739
|
+
let span = seamLeft - at + 1;
|
|
740
|
+
let eaten = 0;
|
|
741
|
+
let spliced = false;
|
|
742
|
+
// Only the first pass: a fold re-tiles the window, so the probe's absolute index is stale.
|
|
743
|
+
let probe = headProbe;
|
|
744
|
+
for (;;) {
|
|
745
|
+
// The candidate edge crosses a blank run too: the absorbed content sits on the
|
|
746
|
+
// run's far side (a list continues into indented code across any number of blanks).
|
|
747
|
+
let right = at + span;
|
|
748
|
+
while (right < children.length && isBlankParagraph(children[right]))
|
|
749
|
+
right++;
|
|
750
|
+
const window = children.slice(at, Math.min(right + 1, children.length));
|
|
751
|
+
if (window.length <= span || window.length < 2)
|
|
752
|
+
break;
|
|
753
|
+
// A context-dependent kind has no standalone reading, so its seam is not askable.
|
|
754
|
+
if (window.some((node) => tryGetBlockKindDescriptor(node.kind)?.contextDependentKind))
|
|
755
|
+
break;
|
|
756
|
+
if (probe !== undefined && declinesOnHeadLine(window, probe - at))
|
|
757
|
+
break;
|
|
758
|
+
probe = undefined;
|
|
759
|
+
const reparsed = parse(joinedWindowBytes(window, window.length), { scope: 'fragment' });
|
|
760
|
+
const blocks = reparsed.children;
|
|
761
|
+
if (blocks.length === 0 || blocks.length >= window.length)
|
|
762
|
+
break;
|
|
763
|
+
// A fold may PROMOTE the head past what its bytes carry alone (a paragraph under the
|
|
764
|
+
// setext underline below it), so what must survive is the head's own reading, not its kind.
|
|
765
|
+
if (blocks[0].kind !== window[0].kind && !readsAsItselfAlone(window[0]))
|
|
766
|
+
break;
|
|
767
|
+
onBeforeSplice?.();
|
|
768
|
+
absorbFragmentPeel(parent, at + window.length, reparsed.suffix, blocks, sharing);
|
|
769
|
+
blocks[0].leadingTrivia = window[0].leadingTrivia;
|
|
770
|
+
for (const block of blocks) {
|
|
771
|
+
ensureEditableContainers(block);
|
|
772
|
+
if (sharing)
|
|
773
|
+
sharing.stamp(block);
|
|
774
|
+
assignChildIdsDeep(block);
|
|
775
|
+
}
|
|
776
|
+
if (tracked)
|
|
777
|
+
retrackThroughFold(tracked, at, window, blocks);
|
|
778
|
+
spliceMany(children, at, window.length, blocks);
|
|
779
|
+
eaten += window.length - blocks.length;
|
|
780
|
+
span = blocks.length;
|
|
781
|
+
spliced = true;
|
|
782
|
+
}
|
|
783
|
+
return { at, span, eaten, spliced };
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Whether a block's own bytes read back as that block. A structured container's children fail
|
|
787
|
+
* this by construction — one list item's bytes read as a LIST — which is how a scope whose
|
|
788
|
+
* children a document parse does NOT reproduce declines the seam question. Asked only where the
|
|
789
|
+
* fold changed the head's kind, so the kind-preserving folds pay nothing.
|
|
790
|
+
*/
|
|
791
|
+
function readsAsItselfAlone(node) {
|
|
792
|
+
const alone = parse(node.raw, { scope: 'fragment' }).children;
|
|
793
|
+
return alone.length === 1 && alone[0].kind === node.kind;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Decline-only pre-parse for a window whose LAST member is the block that changed: join the
|
|
797
|
+
* others with only that block's first line. Block parsing is a left-to-right line scan, so the
|
|
798
|
+
* state entering that line is the same in both strings — if it opens a block here it opens one
|
|
799
|
+
* in the full join and no fold is possible. A pass falls through to the real parse, so no fold
|
|
800
|
+
* verdict is ever taken from truncated bytes. Costs the window minus the changed block, which is
|
|
801
|
+
* what keeps a keystroke inside a giant container off its own bytes.
|
|
802
|
+
*/
|
|
803
|
+
function declinesOnHeadLine(window, member) {
|
|
804
|
+
if (member <= 0 || member !== window.length - 1)
|
|
805
|
+
return false;
|
|
806
|
+
const raw = window[member].raw;
|
|
807
|
+
const nl = raw.indexOf('\n');
|
|
808
|
+
const joined = joinedWindowBytes(window, member) +
|
|
809
|
+
window[member].leadingTrivia +
|
|
810
|
+
(nl < 0 ? raw : raw.slice(0, nl + 1));
|
|
811
|
+
return parse(joined, { scope: 'fragment' }).children.length >= window.length;
|
|
812
|
+
}
|
|
813
|
+
/** How a fold reads a window: the head's raw, then each of the next `count - 1` members'
|
|
814
|
+
* leading trivia and raw. */
|
|
815
|
+
function joinedWindowBytes(window, count) {
|
|
816
|
+
let joined = window[0].raw;
|
|
817
|
+
for (let i = 1; i < count; i++)
|
|
818
|
+
joined += window[i].leadingTrivia + window[i].raw;
|
|
819
|
+
return joined;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Where a byte position inside the folded window lands: the fold's own reparse re-tiles the
|
|
823
|
+
* joined bytes, and a position past the window only shifts by what the fold ate.
|
|
824
|
+
*/
|
|
825
|
+
function retrackThroughFold(tracked, at, window, blocks) {
|
|
826
|
+
const member = tracked.index - at;
|
|
827
|
+
if (member < 0)
|
|
828
|
+
return;
|
|
829
|
+
if (member >= window.length) {
|
|
830
|
+
tracked.index -= window.length - blocks.length;
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
// The offset walk mirrors {@link joinedWindowBytes}.
|
|
834
|
+
let joined = tracked.offset;
|
|
835
|
+
for (let i = 0; i < member; i++) {
|
|
836
|
+
joined += (i === 0 ? 0 : window[i].leadingTrivia.length) + window[i].raw.length;
|
|
837
|
+
}
|
|
838
|
+
if (member > 0)
|
|
839
|
+
joined += window[member].leadingTrivia.length;
|
|
840
|
+
const landed = focusTargetInReplacement(blocks, joined);
|
|
841
|
+
tracked.index = at + landed.index;
|
|
842
|
+
tracked.offset = landed.offset;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* The seam question at every join the splice at `at` disturbed — its window's two edges and the
|
|
846
|
+
* joins inside it — since a move can invalidate a join that was already correct. Each
|
|
847
|
+
* fold cascades downward, so the next seam is asked past what that one ate. `tracked` rides the
|
|
848
|
+
* folds for a caller placing a caret in bytes an absorb can move. `headProbe` names the one block
|
|
849
|
+
* whose bytes changed, letting each ask decline on its first line alone; dropped once anything
|
|
850
|
+
* folds, since the index it names has moved by then.
|
|
851
|
+
*/
|
|
852
|
+
export function absorbWindowSeams(parent, at, added, landing, change, sharing, tracked, headProbe, onBeforeSplice) {
|
|
853
|
+
let settled = null;
|
|
854
|
+
let moved = landing;
|
|
855
|
+
let seamLeft = at - 1;
|
|
856
|
+
let last = at + added - 1;
|
|
857
|
+
while (seamLeft <= last) {
|
|
858
|
+
const seam = absorbSeamReading(parent, seamLeft, 0, sharing, tracked, settled ? undefined : headProbe, onBeforeSplice);
|
|
859
|
+
if (!seam.spliced) {
|
|
860
|
+
seamLeft++;
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
settled = settled ? unionAbsorptions(settled, seam) : seam;
|
|
864
|
+
moved = indexAfterAbsorb(moved, seam);
|
|
865
|
+
last = indexAfterAbsorb(last, seam);
|
|
866
|
+
seamLeft = seam.at + seam.span;
|
|
867
|
+
}
|
|
868
|
+
if (!settled)
|
|
869
|
+
return { change, landing: moved };
|
|
870
|
+
return { change: foldAbsorbIntoChange(change, settled), landing: moved };
|
|
871
|
+
}
|
|
872
|
+
/** Two folds as ONE window, which is what a change descriptor reports. The walk is left to
|
|
873
|
+
* right, so `later` never opens above `earlier`'s post-splice span. */
|
|
874
|
+
function unionAbsorptions(earlier, later) {
|
|
875
|
+
return {
|
|
876
|
+
at: earlier.at,
|
|
877
|
+
span: later.at + later.span - earlier.at,
|
|
878
|
+
eaten: earlier.eaten + later.eaten,
|
|
879
|
+
spliced: true
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
/** Where `index` sits once `seam` folded: a slot inside the absorbed span collapses into it. */
|
|
883
|
+
function indexAfterAbsorb(index, seam) {
|
|
884
|
+
if (index < seam.at)
|
|
885
|
+
return index;
|
|
886
|
+
const absorbedTo = seam.at + seam.span + seam.eaten;
|
|
887
|
+
return index >= absorbedTo ? index - seam.eaten : Math.min(index, seam.at + seam.span - 1);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Where the fragment parse's peeled trailing blank run goes. At the parent's tail it stays in the
|
|
891
|
+
* last block's raw, the single-slot sink's rule; mid-document it joins the follower's run, where
|
|
892
|
+
* one line separates and every later one is a block of its own (syntax-tree.md § Blank lines).
|
|
893
|
+
*/
|
|
894
|
+
function absorbFragmentPeel(parent, followerIndex, peel, blocks, sharing) {
|
|
895
|
+
const follower = parent.children[followerIndex];
|
|
896
|
+
if (!follower) {
|
|
897
|
+
blocks[blocks.length - 1].raw += peel;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (peel === '')
|
|
901
|
+
return;
|
|
902
|
+
const lines = blankLinesOf(peel + follower.leadingTrivia);
|
|
903
|
+
const owned = sharing ? ensureUnsharedChild(parent, followerIndex, sharing) : follower;
|
|
904
|
+
owned.leadingTrivia = lines.length > 1 ? '' : lines[0];
|
|
905
|
+
for (let i = 1; i < lines.length; i++) {
|
|
906
|
+
blocks.push({ kind: 'paragraph', leadingTrivia: i === 1 ? lines[0] : '', raw: lines[i] });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/** A blank run split back into the lines it is made of, each keeping its own ending. */
|
|
910
|
+
const blankLinesOf = (run) => run.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
911
|
+
/** The materialized tail reported inside the sink's one contiguous window. */
|
|
912
|
+
export function widenForTailMint(change, before, after) {
|
|
913
|
+
const grown = after - before;
|
|
914
|
+
if (grown === 0)
|
|
915
|
+
return change;
|
|
916
|
+
if (change.op === 'noop')
|
|
917
|
+
return { op: 'insert', at: before, count: grown };
|
|
918
|
+
if (change.op === 'insert' && change.at + change.count === before) {
|
|
919
|
+
return { ...change, count: change.count + grown };
|
|
920
|
+
}
|
|
921
|
+
if (change.op === 'replace' && change.at + change.newCount === before) {
|
|
922
|
+
return { ...change, newCount: change.newCount + grown };
|
|
923
|
+
}
|
|
924
|
+
// A delete that took the tail vacated the slot the mint lands in, so the two are one window.
|
|
925
|
+
if (change.op === 'delete' && change.at === before) {
|
|
926
|
+
return { op: 'replace', at: change.at, count: change.count, newCount: grown };
|
|
927
|
+
}
|
|
928
|
+
// The mint landed past a window that does not reach the tail, so no single contiguous
|
|
929
|
+
// span describes both; the parallel arrays would drift either way this widened it.
|
|
930
|
+
devWarn('tree-ops', 'a tail suffix materialized outside the reported window');
|
|
931
|
+
return change;
|
|
932
|
+
}
|
|
933
|
+
/** A blank line off the node's own bytes (G4.20), where one does structural work at all. */
|
|
934
|
+
function mintSeparator(parent, index, sharing) {
|
|
935
|
+
const children = parent.children;
|
|
936
|
+
if (!children || index <= bodyStartIndex(parent))
|
|
937
|
+
return;
|
|
938
|
+
if (isBlankParagraph(children[index - 1]))
|
|
939
|
+
return;
|
|
940
|
+
const owned = sharing
|
|
941
|
+
? ensureUnsharedChild(parent, index, sharing)
|
|
942
|
+
: children[index];
|
|
943
|
+
owned.leadingTrivia = trailingLineEnding(owned.raw);
|
|
944
|
+
}
|
|
945
|
+
/** Reserved chrome is not a body block, so the body window opens past it. */
|
|
946
|
+
function bodyStartIndex(parent) {
|
|
947
|
+
return bodyStartFor(ownerKindNameOf(parent));
|
|
948
|
+
}
|
|
949
|
+
/** The container's declared body wrap, whichever shape names the owner. */
|
|
950
|
+
function bodyWrapOf(parent) {
|
|
951
|
+
const kind = ownerKindNameOf(parent);
|
|
952
|
+
if (kind === undefined)
|
|
953
|
+
return undefined;
|
|
954
|
+
return tryGetBlockKindDescriptor(kind)?.bodyWrap;
|
|
955
|
+
}
|
|
956
|
+
/** The node carrying the wrap's peel slots: the sink's answer, or the parent when it IS the node. */
|
|
957
|
+
function wrapSlotsOf(parent) {
|
|
958
|
+
return ownerNodeOf(parent);
|
|
959
|
+
}
|
|
960
|
+
/** The container node these children belong to, where the caller answered for one. */
|
|
961
|
+
function ownerNodeOf(parent) {
|
|
962
|
+
return parent.owner ?? ('raw' in parent ? parent : undefined);
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Every settle door rewrites bytes the owner's child spans describe (a sibling's separating line,
|
|
966
|
+
* a wrap slot) while leaving the children's shape alone, so the spans retire at the doors and the
|
|
967
|
+
* next rebuild re-derives (`schema/child-spans.ts`). The retire census in
|
|
968
|
+
* `lint/separator-write-doors.test.ts` names every door and reds the one that forgets.
|
|
969
|
+
*/
|
|
970
|
+
function retireChildSpans(parent) {
|
|
971
|
+
const owner = ownerNodeOf(parent);
|
|
972
|
+
if (owner)
|
|
973
|
+
dropChildSpans(owner);
|
|
974
|
+
}
|
|
975
|
+
/** The kind whose body these children are: the sink's answer, or the owner node's own. */
|
|
976
|
+
function ownerKindNameOf(parent) {
|
|
977
|
+
return 'ownerKind' in parent ? parent.ownerKind : parent.kind;
|
|
978
|
+
}
|
|
979
|
+
function bodyStartFor(kind) {
|
|
980
|
+
if (kind === undefined)
|
|
981
|
+
return 0;
|
|
982
|
+
return tryGetBlockKindDescriptor(kind)?.reservedChrome ? 1 : 0;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* A chrome-wrapped container's parse peels the blank line against its opener into `innerPrefix`
|
|
986
|
+
* (`core/parser.parseContainerBody`), so a separator freed above the body head is that line
|
|
987
|
+
* rather than dead bytes: hand it over, or the peel eats the head block instead.
|
|
988
|
+
*/
|
|
989
|
+
function absorbWrapPrefix(parent, bodyStart, index, freed) {
|
|
990
|
+
const slots = wrapSlotsOf(parent);
|
|
991
|
+
if (!slots || (slots.innerPrefix ?? '') !== '')
|
|
992
|
+
return;
|
|
993
|
+
if (!bodyWrapOf(parent)?.afterOpenerLine)
|
|
994
|
+
return;
|
|
995
|
+
const head = parent.children?.[bodyStart];
|
|
996
|
+
if (!head || head.leadingTrivia !== '')
|
|
997
|
+
return;
|
|
998
|
+
if (index !== bodyStart && !isBlankParagraph(head))
|
|
999
|
+
return;
|
|
1000
|
+
slots.innerPrefix = trailingLineEnding(freed);
|
|
1001
|
+
}
|
|
1002
|
+
// ── Delete ──
|
|
1003
|
+
/**
|
|
1004
|
+
* Remove the node at `blockIndex`, leaving the next sibling separated from its new predecessor
|
|
1005
|
+
* and no more. Takes {@link BodyParentArg} because the settle can hand a freed line to the
|
|
1006
|
+
* owner's wrap slots. Pass `sharing` to unshare the nodes written — the successor's trivia is
|
|
1007
|
+
* the op's only in-place write.
|
|
1008
|
+
*/
|
|
1009
|
+
export function deleteNode(parent, blockIndex, sharing) {
|
|
1010
|
+
if (blockIndex < 0 || blockIndex >= parent.children.length)
|
|
1011
|
+
return { op: 'noop' };
|
|
1012
|
+
const deleted = parent.children[blockIndex];
|
|
1013
|
+
if (blockIndex + 1 < parent.children.length) {
|
|
1014
|
+
const successor = sharing
|
|
1015
|
+
? ensureUnsharedChild(parent, blockIndex + 1, sharing)
|
|
1016
|
+
: parent.children[blockIndex + 1];
|
|
1017
|
+
// The successor inherits the deleted separator only when it has none of its own:
|
|
1018
|
+
// concatenating both leaves behind a blank line the delete should have taken.
|
|
1019
|
+
successor.leadingTrivia = successor.leadingTrivia || deleted.leadingTrivia;
|
|
1020
|
+
}
|
|
1021
|
+
parent.children.splice(blockIndex, 1);
|
|
1022
|
+
clearRedundantSeparator(parent, blockIndex, sharing);
|
|
1023
|
+
// BOTH the survivor's edges: the delete puts it beside a new follower, and a merge door that
|
|
1024
|
+
// rewrote its bytes can equally have stopped it interrupting the block above.
|
|
1025
|
+
const survivor = Math.max(blockIndex - 1, 0);
|
|
1026
|
+
return absorbWindowSeams(parent, survivor, blockIndex - survivor, blockIndex, { op: 'delete', at: blockIndex, count: 1 }, sharing).change;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Update raw and re-parse. The sole re-parse transfer funnel: a kind change mints the reparsed
|
|
1030
|
+
* block into the slot rather than reassigning `kind` in place, and multi-block text mints every
|
|
1031
|
+
* parsed block. Only a same-kind single-block edit writes fields in place, so routine typing keeps
|
|
1032
|
+
* the node's object identity; `replacePreservingFirst` carries the id/ref across a mint. `sharing`
|
|
1033
|
+
* owns the separator settle's writes, which land on the run's OTHER blocks.
|
|
1034
|
+
*/
|
|
1035
|
+
export function updateNodeContent(parent, blockIndex, text, grammar, sharing) {
|
|
1036
|
+
const wasBlank = isBlankParagraph(parent.children[blockIndex]);
|
|
1037
|
+
const change = writeParsedContent(parent, blockIndex, text, grammar);
|
|
1038
|
+
const lastWritten = lastMintedIndex(change, blockIndex);
|
|
1039
|
+
// One blank line served BOTH sides: it separated this block from the one above and stood in
|
|
1040
|
+
// as the separator of the block beneath it. Ending it owes each their own.
|
|
1041
|
+
if (wasBlank && !isBlankParagraph(parent.children[blockIndex])) {
|
|
1042
|
+
restoreSeparatorOnFill(parent, blockIndex, sharing);
|
|
1043
|
+
restoreSeparatorAfterBlank(parent, followerIndexAfter(change, blockIndex), sharing);
|
|
1044
|
+
releaseWrapPeel(parent, lastWritten);
|
|
1045
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, change, sharing);
|
|
1046
|
+
}
|
|
1047
|
+
// The reverse transition: the block IS the separating line now, so the run it joins gives
|
|
1048
|
+
// back the second one. The last block minted is the one that meets the follower.
|
|
1049
|
+
if (!wasBlank && isBlankParagraph(parent.children[lastWritten])) {
|
|
1050
|
+
const settled = parent.children.length;
|
|
1051
|
+
settleSeparatorOnBlank(parent, lastWritten, sharing);
|
|
1052
|
+
const widened = widenForTailMint(change, settled, parent.children.length);
|
|
1053
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, widened, sharing);
|
|
1054
|
+
}
|
|
1055
|
+
// Both blank transitions settle above whatever their change op says; this guard is for
|
|
1056
|
+
// same-kind typing INSIDE content, which must never pay a neighbour reparse.
|
|
1057
|
+
if (change.op === 'noop')
|
|
1058
|
+
return { change, textStart: 0 };
|
|
1059
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, change, sharing);
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Ask every join the write disturbed, both edges of its window included, and report where the
|
|
1063
|
+
* written text ended up: an absorb ABOVE leaves the predecessor standing, so its bytes and the
|
|
1064
|
+
* join newline now sit in front of the text the caret was typed into.
|
|
1065
|
+
*/
|
|
1066
|
+
function settleWriteSeams(parent, blockIndex, lastWritten, change, sharing) {
|
|
1067
|
+
const tracked = { index: blockIndex, offset: 0 };
|
|
1068
|
+
const settled = absorbWindowSeams(parent, blockIndex, lastWritten - blockIndex + 1, blockIndex, change, sharing, tracked);
|
|
1069
|
+
return {
|
|
1070
|
+
change: settled.change,
|
|
1071
|
+
textStart: textOffsetInWindow(parent.children, settled.change, tracked)
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* The tracked position as an offset in the settled window's committed text — the space every
|
|
1076
|
+
* caret door measures in ({@link focusTargetInReplacement}), where the head block's own
|
|
1077
|
+
* leading trivia is outside the window.
|
|
1078
|
+
*/
|
|
1079
|
+
function textOffsetInWindow(children, change, tracked) {
|
|
1080
|
+
const at = change.op === 'noop' ? tracked.index : change.at;
|
|
1081
|
+
let pos = 0;
|
|
1082
|
+
for (let i = at; i < tracked.index; i++) {
|
|
1083
|
+
pos += (i === at ? 0 : children[i].leadingTrivia.length) + children[i].raw.length;
|
|
1084
|
+
}
|
|
1085
|
+
const ownTrivia = tracked.index > at ? children[tracked.index].leadingTrivia.length : 0;
|
|
1086
|
+
return pos + ownTrivia + tracked.offset;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* The absorbed window folded into the write's own, as the ONE contiguous window the sink
|
|
1090
|
+
* reports: `count` counts pre-write slots, so the union's span converts back across whatever
|
|
1091
|
+
* the write itself added or removed.
|
|
1092
|
+
*/
|
|
1093
|
+
function foldAbsorbIntoChange(change, seam) {
|
|
1094
|
+
const absorbedTo = seam.at + seam.span + seam.eaten;
|
|
1095
|
+
if (change.op === 'noop') {
|
|
1096
|
+
return {
|
|
1097
|
+
op: 'replace',
|
|
1098
|
+
at: seam.at,
|
|
1099
|
+
count: absorbedTo - seam.at,
|
|
1100
|
+
newCount: seam.span,
|
|
1101
|
+
idMap: { 0: 0 }
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
const written = change.op === 'insert' ? change.count : change.op === 'delete' ? 0 : change.newCount;
|
|
1105
|
+
const removed = change.op === 'insert' ? 0 : change.count;
|
|
1106
|
+
const lo = Math.min(seam.at, change.at);
|
|
1107
|
+
const hi = Math.max(absorbedTo, change.at + written);
|
|
1108
|
+
const count = hi - lo - written + removed;
|
|
1109
|
+
const newCount = hi - lo - seam.eaten;
|
|
1110
|
+
return {
|
|
1111
|
+
op: 'replace',
|
|
1112
|
+
at: lo,
|
|
1113
|
+
count,
|
|
1114
|
+
newCount,
|
|
1115
|
+
idMap: composeFoldIdMap(change, seam, { lo, count, newCount, written, removed })
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Identity through the fold: a slot the absorb did not re-mint still holds the block the change
|
|
1120
|
+
* put there, so its id composes through both steps instead of resetting. Slot 0 keeps the head
|
|
1121
|
+
* mapping wherever the walk has none — a fold EXTENDS its head, kind promotion included, which
|
|
1122
|
+
* is the identity `replacePreservingFirst` reports.
|
|
1123
|
+
*/
|
|
1124
|
+
function composeFoldIdMap(change, seam, window) {
|
|
1125
|
+
const idMap = {};
|
|
1126
|
+
for (let slot = 0; slot < window.newCount; slot++) {
|
|
1127
|
+
const index = window.lo + slot;
|
|
1128
|
+
if (index >= seam.at && index < seam.at + seam.span)
|
|
1129
|
+
continue;
|
|
1130
|
+
// Back through the fold: a slot past the absorbed span sat `eaten` further down before it.
|
|
1131
|
+
const spliced = index < seam.at ? index : index + seam.eaten;
|
|
1132
|
+
const old = preChangeIndex(change, spliced, window);
|
|
1133
|
+
if (old === null)
|
|
1134
|
+
continue;
|
|
1135
|
+
const oldSlot = old - window.lo;
|
|
1136
|
+
if (oldSlot >= 0 && oldSlot < window.count)
|
|
1137
|
+
idMap[slot] = oldSlot;
|
|
1138
|
+
}
|
|
1139
|
+
if (idMap[0] === undefined)
|
|
1140
|
+
idMap[0] = 0;
|
|
1141
|
+
return idMap;
|
|
1142
|
+
}
|
|
1143
|
+
/** Where `spliced` (a post-change index) stood before the change, or null for a slot it minted. */
|
|
1144
|
+
function preChangeIndex(change, spliced, window) {
|
|
1145
|
+
if (change.op === 'noop')
|
|
1146
|
+
return spliced;
|
|
1147
|
+
if (spliced < change.at)
|
|
1148
|
+
return spliced;
|
|
1149
|
+
if (spliced >= change.at + window.written)
|
|
1150
|
+
return spliced - window.written + window.removed;
|
|
1151
|
+
const inherited = change.op === 'replace' ? change.idMap?.[spliced - change.at] : undefined;
|
|
1152
|
+
return inherited === undefined ? null : change.at + inherited;
|
|
1153
|
+
}
|
|
1154
|
+
/** Where the filled block's follower ended up: a multi-block reparse pushes it down. */
|
|
1155
|
+
function followerIndexAfter(change, blockIndex) {
|
|
1156
|
+
return change.op === 'replace' ? change.at + change.newCount : blockIndex + 1;
|
|
1157
|
+
}
|
|
1158
|
+
/** The last block the write left in the slot: a multi-block reparse mints past the first. */
|
|
1159
|
+
function lastMintedIndex(change, blockIndex) {
|
|
1160
|
+
return change.op === 'replace' ? change.at + change.newCount - 1 : blockIndex;
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* The written bytes parsed, with the construct they leave OPEN closed off first. An unterminated
|
|
1164
|
+
* construct reads every block below it as its body at the next parse, and the seam settle
|
|
1165
|
+
* converges the live tree to exactly that reading. Declines where nothing is at stake: a
|
|
1166
|
+
* kind-stable single block never reaches the settle, and a construct with no follower eats
|
|
1167
|
+
* nothing.
|
|
1168
|
+
*/
|
|
1169
|
+
function closeWrittenConstruct(parent, blockIndex, text, oldKind, grammar) {
|
|
1170
|
+
// An absent grammar defaults to the global one. Fragment scope: this is one block's
|
|
1171
|
+
// bytes, whatever its position, so a position-scoped kind must not mint here.
|
|
1172
|
+
const parsed = parse(text, { grammar, scope: 'fragment' });
|
|
1173
|
+
if (blockIndex + 1 >= parent.children.length)
|
|
1174
|
+
return { text, parsed };
|
|
1175
|
+
if (parsed.children.length === 1 && parsed.children[0].kind === oldKind)
|
|
1176
|
+
return { text, parsed };
|
|
1177
|
+
const terminator = openConstructTerminator(text, parsed.children, grammar);
|
|
1178
|
+
if (!terminator)
|
|
1179
|
+
return { text, parsed };
|
|
1180
|
+
const closed = text + terminator;
|
|
1181
|
+
return { text: closed, parsed: parse(closed, { grammar, scope: 'fragment' }) };
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* The terminator written bytes owe when their last construct absorbs to EOF, asked of the grammar
|
|
1185
|
+
* rather than a kind list: swallowing a blank line AND a prose line is what separates an absorber
|
|
1186
|
+
* from a paragraph continuing onto the next line. Null when the bytes terminate themselves, or
|
|
1187
|
+
* when no fence opener explains the absorb — the one family whose closer its opener determines.
|
|
1188
|
+
*/
|
|
1189
|
+
function openConstructTerminator(text, blocks, grammar) {
|
|
1190
|
+
// The terminator is a line of its own, so bytes whose last line is still open have none to
|
|
1191
|
+
// append to — G4.20's unterminated tail slice, which absorbs nothing while it stands alone.
|
|
1192
|
+
if (!text.endsWith('\n') || blocks.length === 0)
|
|
1193
|
+
return null;
|
|
1194
|
+
const ending = trailingLineEnding(text);
|
|
1195
|
+
const probe = parse(text + ending + NEXT_PROSE_LINE + ending, { grammar, scope: 'fragment' });
|
|
1196
|
+
if (probe.children.length !== blocks.length)
|
|
1197
|
+
return null;
|
|
1198
|
+
const raw = blocks[blocks.length - 1].raw;
|
|
1199
|
+
const nl = raw.indexOf('\n');
|
|
1200
|
+
const opener = matchFenceOpen(nl < 0 ? raw : raw.slice(0, nl));
|
|
1201
|
+
if (!opener)
|
|
1202
|
+
return null;
|
|
1203
|
+
const body = nl < 0 ? '' : raw.slice(nl + 1);
|
|
1204
|
+
const run = escalatedFenceLength(body, opener.marker, opener.length);
|
|
1205
|
+
return opener.indent + opener.marker.repeat(run) + ending;
|
|
1206
|
+
}
|
|
1207
|
+
function writeParsedContent(parent, blockIndex, text, grammar) {
|
|
1208
|
+
const node = parent.children[blockIndex];
|
|
1209
|
+
const oldKind = node.kind;
|
|
1210
|
+
const oldDescriptor = getBlockKindDescriptor(oldKind);
|
|
1211
|
+
// Ahead of every reparse below, so a write lands on the kind its committed bytes
|
|
1212
|
+
// describe, not the kind the pre-escape text would parse to (`bodyWrite`).
|
|
1213
|
+
const bodyText = forBody(parent, text);
|
|
1214
|
+
// A context-dependent kind (tableCell, plugin chrome) has no standalone recognizer, so
|
|
1215
|
+
// reparsing would downgrade it: keep the kind and write raw through the kind's own
|
|
1216
|
+
// legality pass, since a delimiter arriving bare would restructure the container.
|
|
1217
|
+
if (oldDescriptor.contextDependentKind) {
|
|
1218
|
+
writeOwnRaw(node, bodyText, grammar);
|
|
1219
|
+
return { op: 'noop' };
|
|
1220
|
+
}
|
|
1221
|
+
const { text: newText, parsed: reparsed } = closeWrittenConstruct(parent, blockIndex, bodyText, oldKind, grammar);
|
|
1222
|
+
const parsed = reparsed.children;
|
|
1223
|
+
const first = parsed[0];
|
|
1224
|
+
// A container whose empty body will be backfilled: a marker-consuming container (a
|
|
1225
|
+
// GitHub alert) needs its raw rebuilt from that body, or G1.1 stale-raw fires.
|
|
1226
|
+
const firstBackfilled = !!first && isEmptyEditableContainer(first);
|
|
1227
|
+
if (first)
|
|
1228
|
+
ensureEditableContainers(first);
|
|
1229
|
+
// Text-leading blanks fold into the first block's raw (the single-block shape); the
|
|
1230
|
+
// rest keep their own trivia.
|
|
1231
|
+
if (parsed.length > 1) {
|
|
1232
|
+
const rest = parsed.slice(1);
|
|
1233
|
+
for (const sibling of rest)
|
|
1234
|
+
ensureEditableContainers(sibling);
|
|
1235
|
+
first.raw = first.leadingTrivia + first.raw;
|
|
1236
|
+
first.leadingTrivia = node.leadingTrivia;
|
|
1237
|
+
if (firstBackfilled)
|
|
1238
|
+
reconcileBackfilledRaw(first);
|
|
1239
|
+
// The peeled line has no follower inside the splice, so it stays in raw.
|
|
1240
|
+
rest[rest.length - 1].raw += reparsed.suffix;
|
|
1241
|
+
spliceMany(parent.children, blockIndex, 1, parsed);
|
|
1242
|
+
return replacePreservingFirst(blockIndex, 1, parsed.length);
|
|
1243
|
+
}
|
|
1244
|
+
const newKind = first?.kind ?? 'paragraph';
|
|
1245
|
+
// In-place refresh keeps the node's object identity: component, IME state, and inline
|
|
1246
|
+
// cache are all keyed on it.
|
|
1247
|
+
if (newKind === oldKind) {
|
|
1248
|
+
node.raw = newText;
|
|
1249
|
+
adoptReparsedFields(node, first);
|
|
1250
|
+
if (firstBackfilled)
|
|
1251
|
+
reconcileBackfilledRaw(node);
|
|
1252
|
+
return { op: 'noop' };
|
|
1253
|
+
}
|
|
1254
|
+
const replacement = first ?? { kind: 'paragraph', leadingTrivia: '', raw: newText };
|
|
1255
|
+
replacement.raw = newText;
|
|
1256
|
+
replacement.leadingTrivia = node.leadingTrivia;
|
|
1257
|
+
if (firstBackfilled)
|
|
1258
|
+
reconcileBackfilledRaw(replacement);
|
|
1259
|
+
parent.children.splice(blockIndex, 1, replacement);
|
|
1260
|
+
return replacePreservingFirst(blockIndex, 1, 1);
|
|
1261
|
+
}
|
|
1262
|
+
// ── Container kind re-derivation ──
|
|
1263
|
+
/**
|
|
1264
|
+
* What the grammar opens `line` as, read in isolation — the opener-significance test, asked of
|
|
1265
|
+
* the opener registry and never a kind list, so a kind registered later is covered the day it
|
|
1266
|
+
* registers.
|
|
1267
|
+
*/
|
|
1268
|
+
export function lineOpensAs(line, grammar) {
|
|
1269
|
+
return parse(`${line}\n`, { grammar, scope: 'fragment' }).children[0]?.kind ?? 'paragraph';
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Re-derive the container at `index` from its own (already rebuilt) raw, replacing it in
|
|
1273
|
+
* the slot when that raw now opens as a different kind. The container twin of
|
|
1274
|
+
* `updateNodeContent`'s kind-change arm (editor.md § 8), which no leaf reparse can see.
|
|
1275
|
+
* Eligibility is the opener registry, not a kind list — registering an opener is exactly
|
|
1276
|
+
* the claim that `parse(raw)` reproduces the kind, so re-deriving one without would destroy it.
|
|
1277
|
+
*/
|
|
1278
|
+
export function reclassifyContainer(parent, index, grammar) {
|
|
1279
|
+
const node = parent.children[index];
|
|
1280
|
+
if (!node)
|
|
1281
|
+
return null;
|
|
1282
|
+
const descriptor = tryGetBlockKindDescriptor(node.kind);
|
|
1283
|
+
if (!descriptor?.isContainer || !isBlockOpenerRegistered(node.kind))
|
|
1284
|
+
return null;
|
|
1285
|
+
if (perfEnabled())
|
|
1286
|
+
recordContainerKindReparse();
|
|
1287
|
+
const parsed = parse(node.raw, { grammar, scope: 'fragment' }).children;
|
|
1288
|
+
// A container's raw is one block by construction; a multi-block reparse means bytes
|
|
1289
|
+
// this seam has no slot for — leave it to the gesture that owns the mutation.
|
|
1290
|
+
if (parsed.length !== 1 || parsed[0].kind === node.kind)
|
|
1291
|
+
return null;
|
|
1292
|
+
const replacement = parsed[0];
|
|
1293
|
+
const backfilled = isEmptyEditableContainer(replacement);
|
|
1294
|
+
ensureEditableContainers(replacement);
|
|
1295
|
+
// The slot's trivia is authoritative, so restore the bytes before overwriting it or
|
|
1296
|
+
// anything the parse split off the front vanishes with it.
|
|
1297
|
+
replacement.raw = node.raw;
|
|
1298
|
+
replacement.leadingTrivia = node.leadingTrivia;
|
|
1299
|
+
if (backfilled)
|
|
1300
|
+
reconcileBackfilledRaw(replacement);
|
|
1301
|
+
// A freshly-parsed node carries no childIds and this swap publishes under the slot's
|
|
1302
|
+
// reused component instance, so undefined keys would reach the nested keyed `{#each}`.
|
|
1303
|
+
assignChildIdsDeep(replacement);
|
|
1304
|
+
parent.children[index] = replacement;
|
|
1305
|
+
// Write-then-re-read (tree-operations/unshare.ts header).
|
|
1306
|
+
return parent.children[index];
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Map a post-edit caret offset (in the committed text) to the parsed block it falls in,
|
|
1310
|
+
* as a local display offset. The first block's body starts at 0; an offset inside
|
|
1311
|
+
* inter-block trivia lands at the next block's start; past-the-end clamps to the last.
|
|
1312
|
+
*/
|
|
1313
|
+
export function focusTargetInReplacement(nodes, offset) {
|
|
1314
|
+
let pos = 0;
|
|
1315
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1316
|
+
const bodyStart = i === 0 ? 0 : pos + nodes[i].leadingTrivia.length;
|
|
1317
|
+
const bodyEnd = bodyStart + trimTrailingLineEnding(nodes[i].raw).length;
|
|
1318
|
+
if (offset <= bodyEnd) {
|
|
1319
|
+
return { index: i, offset: Math.max(0, offset - bodyStart) };
|
|
1320
|
+
}
|
|
1321
|
+
pos = bodyStart + nodes[i].raw.length;
|
|
1322
|
+
}
|
|
1323
|
+
const last = nodes.length - 1;
|
|
1324
|
+
return { index: last, offset: trimTrailingLineEnding(nodes[last].raw).length };
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Where a caret at `offset` in the written text lands once {@link updateNodeContent}'s folds
|
|
1328
|
+
* settled: an absorb ABOVE leaves the predecessor holding the bytes, so the slot the gesture
|
|
1329
|
+
* named is gone and the offset carries what that predecessor put in front of it. Every caret
|
|
1330
|
+
* door onto the content funnel asks this, so the settle is answered in one place.
|
|
1331
|
+
*/
|
|
1332
|
+
export function settledCaretTarget(settled, at, offset, children) {
|
|
1333
|
+
const { change, textStart } = settled;
|
|
1334
|
+
if (change.op !== 'replace')
|
|
1335
|
+
return { index: at, offset };
|
|
1336
|
+
const shifted = offset + textStart;
|
|
1337
|
+
if (change.newCount <= 1)
|
|
1338
|
+
return { index: change.at, offset: shifted };
|
|
1339
|
+
const blocks = children.slice(change.at, change.at + change.newCount);
|
|
1340
|
+
const target = focusTargetInReplacement(blocks, shifted);
|
|
1341
|
+
return { index: change.at + target.index, offset: target.offset };
|
|
1342
|
+
}
|
|
1343
|
+
// ── Reparse helper (private) ──
|
|
1344
|
+
/**
|
|
1345
|
+
* Reparse a half's bytes as the blocks they hold, plus the trailing blank line the fragment
|
|
1346
|
+
* parse peels into `doc.suffix` — every sink answers for it, or the bytes are lost. Plural
|
|
1347
|
+
* because a half stands in a position its bytes never occupied, where a construct boundary
|
|
1348
|
+
* can newly materialize.
|
|
1349
|
+
*/
|
|
1350
|
+
function reparseAsNodes(raw, leadingTrivia) {
|
|
1351
|
+
const doc = parse(raw, { scope: 'fragment' });
|
|
1352
|
+
if (doc.children.length === 0) {
|
|
1353
|
+
return { nodes: [{ kind: 'paragraph', leadingTrivia, raw }], suffix: doc.suffix };
|
|
1354
|
+
}
|
|
1355
|
+
doc.children[0].leadingTrivia = leadingTrivia;
|
|
1356
|
+
for (const node of doc.children)
|
|
1357
|
+
ensureEditableContainers(node);
|
|
1358
|
+
return { nodes: doc.children, suffix: doc.suffix };
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* The merge sinks' single-block twin, and their decline: a join whose bytes read as several
|
|
1362
|
+
* blocks has no home in one slot, so it is refused rather than truncated (G1.35). Null is the
|
|
1363
|
+
* refusal; the door returns noop and its caller falls back to move-focus.
|
|
1364
|
+
*/
|
|
1365
|
+
function reparseAsNode(raw, leadingTrivia) {
|
|
1366
|
+
const { nodes, suffix } = reparseAsNodes(raw, leadingTrivia);
|
|
1367
|
+
if (nodes.length > 1)
|
|
1368
|
+
return null;
|
|
1369
|
+
// A single-block sink has no follower slot, so the peeled line stays in the block's bytes.
|
|
1370
|
+
nodes[0].raw += suffix;
|
|
1371
|
+
return nodes[0];
|
|
1372
|
+
}
|
|
1373
|
+
// ── Replacement normalization ──
|
|
1374
|
+
/** First node inherits the original block's leadingTrivia; subsequent nodes keep theirs. */
|
|
1375
|
+
export function normalizeReplacementTrivia(original, replacement) {
|
|
1376
|
+
const originalTrivia = original.leadingTrivia ?? '';
|
|
1377
|
+
return replacement.map((node, i) => {
|
|
1378
|
+
const copy = { ...node };
|
|
1379
|
+
copy.leadingTrivia = i === 0 ? originalTrivia : (copy.leadingTrivia ?? '');
|
|
1380
|
+
return copy;
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
// ── Editable container backfill ──
|
|
1384
|
+
/**
|
|
1385
|
+
* A container `ensureEditableContainers` will backfill. Read BEFORE the backfill runs —
|
|
1386
|
+
* afterwards it holds the synthesized paragraph and no longer qualifies.
|
|
1387
|
+
*/
|
|
1388
|
+
function isEmptyEditableContainer(node) {
|
|
1389
|
+
const d = getBlockKindDescriptor(node.kind);
|
|
1390
|
+
return d.isContainer && d.blockFocus !== 'whole-block' && (node.children?.length ?? 0) === 0;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Sync a just-backfilled container's `raw` to its synthesized body. Needed only for a
|
|
1394
|
+
* marker-consuming container whose typed raw lacks a blank body line (`> [!TYPE]`);
|
|
1395
|
+
* blockquote/listItem already strip to the blank, so their rebuild is a no-op.
|
|
1396
|
+
*/
|
|
1397
|
+
function reconcileBackfilledRaw(node) {
|
|
1398
|
+
getBlockKindDescriptor(node.kind).rebuildRaw?.(node);
|
|
1399
|
+
}
|
|
1400
|
+
/** Ensure every container has at least one child block, so the cursor always has a target. */
|
|
1401
|
+
export function ensureEditableContainers(node) {
|
|
1402
|
+
// A whole-block-focus kind is childless by design — the block itself is the caret
|
|
1403
|
+
// target, and a backfilled paragraph its raw can't account for trips opaque-stale-raw.
|
|
1404
|
+
if (getBlockKindDescriptor(node.kind).blockFocus === 'whole-block')
|
|
1405
|
+
return;
|
|
1406
|
+
if (node.children !== undefined) {
|
|
1407
|
+
if (node.children.length === 0) {
|
|
1408
|
+
// discovered-descendant mutation, see file header
|
|
1409
|
+
const chromeKind = reservedChromeKindOf(node.kind);
|
|
1410
|
+
// Backfilled lines take the container's own ending (G4.20) — they are pure
|
|
1411
|
+
// line ending, so a literal LF strands one inside a CRLF container.
|
|
1412
|
+
const lineEnding = trailingLineEnding(node.raw);
|
|
1413
|
+
// A chrome-declaring container must re-mint its child-0 leaf too, or the
|
|
1414
|
+
// backfilled paragraph would occupy the reserved slot and violate G1.14.
|
|
1415
|
+
if (chromeKind !== undefined) {
|
|
1416
|
+
// Runtime chrome kind, so the mint takes the generic cast.
|
|
1417
|
+
node.children.push({ kind: chromeKind, leadingTrivia: '', raw: lineEnding });
|
|
1418
|
+
}
|
|
1419
|
+
node.children.push(emptyParagraph('', lineEnding));
|
|
1420
|
+
// The synthesized paragraph's ending already represents the blank `parseBlocks`
|
|
1421
|
+
// routed into innerPrefix; keeping both double-counts the line on rebuild.
|
|
1422
|
+
node.innerPrefix = '';
|
|
1423
|
+
}
|
|
1424
|
+
for (const child of node.children) {
|
|
1425
|
+
ensureEditableContainers(child);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|