@voithos-labs/aragonite 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -21
- package/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/a11y-strings.d.ts +19 -0
- package/dist/a11y-strings.js +19 -0
- package/dist/action-contracts.d.ts +7 -1
- package/dist/ambient/ambient-dom.js +5 -1
- package/dist/block-component.d.ts +14 -0
- package/dist/components/BlockDragHandle.svelte +37 -24
- package/dist/components/BlockHost.svelte +18 -11
- package/dist/components/Editor.svelte +434 -198
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +25 -17
- package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
- package/dist/components/TailInsert.svelte +107 -0
- package/dist/components/TailInsert.svelte.d.ts +17 -0
- package/dist/components/block-content-selector.d.ts +6 -2
- package/dist/components/block-content-selector.js +6 -2
- package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/code/CodeBlock.svelte +211 -30
- package/dist/components/blocks/code/CodeBlockRail.svelte +704 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +16 -0
- package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
- package/dist/components/blocks/code/code-context-actions.js +24 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
- package/dist/components/blocks/code/code-fence-exit.js +26 -0
- package/dist/components/blocks/code/code-languages.d.ts +6 -0
- package/dist/components/blocks/code/code-languages.js +22 -3
- package/dist/components/blocks/code/code-renderer.js +11 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
- package/dist/components/blocks/editable-leaf.d.ts +37 -6
- package/dist/components/blocks/editable-leaf.js +248 -30
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListBlock.svelte +1 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +14 -5
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
- package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
- package/dist/components/blocks/list/task-checkbox.js +11 -2
- package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
- package/dist/components/blocks/surface-wiring.svelte.js +8 -1
- package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
- package/dist/components/blocks/table/TableBlock.svelte +205 -186
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
- package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
- package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -1
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
- package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
- package/dist/components/blocks/table/table-cell-paste.js +2 -1
- package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
- package/dist/components/blocks/table/table-menu-model.js +36 -17
- package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
- package/dist/components/blocks/text/click-snap-guard.d.ts +3 -0
- package/dist/components/blocks/text/click-snap-guard.js +8 -0
- package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
- package/dist/components/blocks/text/delimiter-autopair.js +216 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +89 -20
- package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
- package/dist/components/blocks/text/live-selection-edit.js +37 -7
- package/dist/components/blocks/text/text-clipboard.js +2 -2
- package/dist/components/blocks/text/text-keydown.d.ts +7 -1
- package/dist/components/blocks/text/text-keydown.js +11 -1
- package/dist/components/blocks/text/text-render.d.ts +1 -1
- package/dist/components/blocks/text/text-render.js +3 -1
- package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
- package/dist/components/blocks/text/widget-interaction.js +181 -37
- package/dist/components/drag-handle.d.ts +41 -0
- package/dist/components/drag-handle.js +134 -0
- package/dist/components/editor-root-focus.d.ts +19 -0
- package/dist/components/editor-root-focus.js +67 -0
- package/dist/components/editor-root-geometry.d.ts +39 -0
- package/dist/components/editor-root-geometry.js +91 -0
- package/dist/components/editor-root-keydown.d.ts +1 -1
- package/dist/components/editor-root-keydown.js +12 -3
- package/dist/components/editor-root-listeners.d.ts +3 -6
- package/dist/components/editor-root-listeners.js +3 -24
- package/dist/components/editor-root-mode-flip.d.ts +36 -0
- package/dist/components/editor-root-mode-flip.js +92 -0
- package/dist/components/image/ImageOverlayHost.svelte +14 -6
- package/dist/components/image/ImageProperties.svelte +512 -70
- package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
- package/dist/components/image/ImageResizeHandles.svelte +67 -34
- package/dist/components/image/image-crop.d.ts +39 -0
- package/dist/components/image/image-crop.js +74 -0
- package/dist/components/image/image-edit-commit.d.ts +1 -0
- package/dist/components/image/image-edit-commit.js +21 -5
- package/dist/components/image/image-source-bytes.js +10 -3
- package/dist/components/image/image-widget-editing.js +1 -0
- package/dist/components/image/widget-dom.js +5 -1
- package/dist/components/link-card/link-card-commit.js +1 -1
- package/dist/components/lrd-map-gate.js +1 -1
- package/dist/components/menu/BlockMenu.svelte +315 -0
- package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
- package/dist/components/menu/MenuIcon.svelte +153 -0
- package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
- package/dist/components/menu/SelectionToolbar.svelte +385 -0
- package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -0
- package/dist/components/menu/clipboard-actions.d.ts +12 -0
- package/dist/components/menu/clipboard-actions.js +42 -0
- package/dist/components/menu/default-context-actions.d.ts +15 -0
- package/dist/components/menu/default-context-actions.js +76 -0
- package/dist/components/menu/flyout-placement.d.ts +6 -0
- package/dist/components/menu/flyout-placement.js +25 -0
- package/dist/core/inline/format-toggle.d.ts +12 -4
- package/dist/core/inline/format-toggle.js +94 -40
- package/dist/core/inline/image-dimensions.d.ts +3 -0
- package/dist/core/inline/image-dimensions.js +46 -10
- package/dist/core/inline/inline-widgets.d.ts +19 -0
- package/dist/core/inline/inline-widgets.js +5 -0
- package/dist/core/inline/scan/brackets.js +1 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
- package/dist/core/inline/scan/plugin-syntax.js +15 -1
- package/dist/core/inline/transparency.js +3 -3
- package/dist/core/inline-render.d.ts +6 -0
- package/dist/core/inline-render.js +32 -0
- package/dist/core/nodes.d.ts +14 -0
- package/dist/cursor/edge-affinity.js +2 -1
- package/dist/cursor/height-oracle.d.ts +2 -3
- package/dist/cursor/height-oracle.js +0 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/scroll-hold.d.ts +10 -0
- package/dist/cursor/scroll-hold.js +22 -0
- package/dist/cursor/scrollport.d.ts +9 -0
- package/dist/cursor/scrollport.js +30 -1
- package/dist/cursor/visual-lines.d.ts +5 -4
- package/dist/cursor/visual-lines.js +56 -11
- package/dist/cursor/widget-edge-snap.d.ts +28 -0
- package/dist/cursor/widget-edge-snap.js +38 -0
- package/dist/cursor/widget-offset.d.ts +9 -0
- package/dist/cursor/widget-offset.js +71 -4
- package/dist/debug/interaction-trace.d.ts +4 -0
- package/dist/debug/interaction-trace.js +15 -0
- package/dist/decorations/decoration-state.svelte.js +1 -1
- package/dist/decorations/reserved-attrs.js +1 -0
- package/dist/editor-actions/ancestry-folds.d.ts +2 -2
- package/dist/editor-actions/ancestry-folds.js +1 -1
- package/dist/editor-actions/block-edit-scope.js +1 -1
- package/dist/editor-actions/commit/text-batch.d.ts +3 -2
- package/dist/editor-actions/commit/text-batch.js +1 -1
- package/dist/editor-actions/commit/undo-controller.js +4 -2
- package/dist/editor-actions/container-edit.js +2 -1
- package/dist/editor-actions/enter-completion.d.ts +2 -0
- package/dist/editor-actions/enter-completion.js +23 -2
- package/dist/editor-actions/focus/focus-dispatch.js +7 -4
- package/dist/editor-actions/focus/focus-landing.d.ts +10 -1
- package/dist/editor-actions/focus/focus-landing.js +20 -6
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/plugin/container.d.ts +7 -0
- package/dist/editor-actions/plugin/container.js +3 -1
- package/dist/editor-actions/reorder-action.js +19 -10
- package/dist/editor-actions/reorder-drag.js +29 -1
- package/dist/editor-actions/replacement-focus.d.ts +1 -1
- package/dist/editor-actions/replacement-focus.js +1 -1
- package/dist/editor-actions/search-replace.js +1 -1
- package/dist/editor-actions/table-context.d.ts +4 -1
- package/dist/editor-actions/table-context.js +57 -1
- package/dist/editor-events.d.ts +3 -0
- package/dist/editor-keys.d.ts +33 -0
- package/dist/editor-props.d.ts +24 -12
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +16 -0
- package/dist/plugins/latex/BlockMath.svelte +264 -29
- package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
- package/dist/plugins/latex/index.d.ts +2 -1
- package/dist/plugins/latex/latex-kind.js +60 -10
- package/dist/plugins/latex/math-completion.js +4 -1
- package/dist/plugins/latex/math-layout.d.ts +15 -0
- package/dist/plugins/latex/math-layout.js +13 -0
- package/dist/plugins/latex/math-source.d.ts +19 -0
- package/dist/plugins/latex/math-source.js +103 -0
- package/dist/plugins/latex/register.d.ts +11 -2
- package/dist/plugins/latex/register.js +3 -1
- package/dist/plugins/latex/renderer.d.ts +3 -3
- package/dist/plugins/latex/renderer.js +13 -6
- package/dist/plugins/mermaid/MermaidBlock.svelte +19 -9
- package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +75 -30
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/commands.d.ts +9 -5
- package/dist/schema/commands.js +12 -7
- package/dist/schema/context-actions.d.ts +31 -0
- package/dist/schema/context-actions.js +23 -0
- package/dist/schema/fenced-code-raw.js +31 -1
- package/dist/schema/operations.d.ts +8 -1
- package/dist/schema/reserved-chords.js +37 -4
- package/dist/schema/table-cell-raw.d.ts +1 -1
- package/dist/schema/table-cell-raw.js +1 -1
- package/dist/selection/block-hit-test.js +3 -2
- package/dist/selection/char-endpoint-snap.js +1 -1
- package/dist/selection/clipboard-text.js +6 -1
- package/dist/selection/covered-block.d.ts +10 -0
- package/dist/selection/covered-block.js +24 -0
- package/dist/selection/cross-block/dispatch.d.ts +3 -0
- package/dist/selection/cross-block/dispatch.js +13 -1
- package/dist/selection/cross-block/format-range.d.ts +1 -1
- package/dist/selection/cross-block/format-range.js +4 -15
- package/dist/selection/cross-block/format-toggle.js +1 -1
- package/dist/selection/cross-block/keydown.js +3 -29
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +31 -35
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +52 -13
- package/dist/selection/dead-space-caret.d.ts +13 -0
- package/dist/selection/dead-space-caret.js +57 -1
- package/dist/selection/drag-pointer.d.ts +26 -2
- package/dist/selection/drag-pointer.js +103 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.d.ts +3 -2
- package/dist/selection/keyboard-extend.js +4 -3
- package/dist/selection/multi-click.d.ts +42 -0
- package/dist/selection/multi-click.js +203 -0
- package/dist/selection/native-bridge.d.ts +3 -0
- package/dist/selection/native-bridge.js +31 -2
- package/dist/selection/path-lookup.d.ts +10 -2
- package/dist/selection/path-lookup.js +11 -4
- package/dist/selection/pointer-gesture.d.ts +9 -0
- package/dist/selection/pointer-gesture.js +11 -0
- package/dist/selection/primitives.d.ts +7 -0
- package/dist/selection/primitives.js +19 -1
- package/dist/selection/range-delete-ceremony.js +4 -2
- package/dist/selection/range-delete-chrome.js +2 -1
- package/dist/selection/range-delete-table-coverage.js +2 -1
- package/dist/selection/range-delete-table.js +3 -2
- package/dist/selection/range-delete.d.ts +4 -0
- package/dist/selection/range-delete.js +31 -3
- package/dist/selection/selection-drop.d.ts +34 -0
- package/dist/selection/selection-drop.js +253 -0
- package/dist/selection/selection-restore.js +1 -1
- package/dist/selection/selection-state.svelte.d.ts +6 -0
- package/dist/selection/selection-state.svelte.js +36 -1
- package/dist/selection/table-endpoint-snap.js +1 -1
- package/dist/selection/table-rect-extend.js +1 -1
- package/dist/styles/editor-theme.css +75 -31
- package/dist/styles/editor.css +268 -18
- package/dist/testing/container-conformance.js +2 -2
- package/dist/testing/inline-conformance.js +2 -1
- package/dist/tree-operations/blockquote.js +1 -1
- package/dist/tree-operations/chain-rebuild.d.ts +63 -0
- package/dist/tree-operations/chain-rebuild.js +142 -0
- package/dist/tree-operations/children.d.ts +1 -1
- package/dist/tree-operations/children.js +1 -1
- package/dist/tree-operations/cleanup.js +1 -1
- package/dist/tree-operations/content-write.d.ts +50 -0
- package/dist/tree-operations/content-write.js +263 -0
- package/dist/tree-operations/index.d.ts +8 -3
- package/dist/tree-operations/index.js +6 -2
- package/dist/tree-operations/list/exit-replacement.js +1 -1
- package/dist/tree-operations/list/unwrap-merge.js +3 -3
- package/dist/tree-operations/node-ops.d.ts +17 -234
- package/dist/tree-operations/node-ops.js +47 -1113
- package/dist/tree-operations/node-primitives.d.ts +75 -0
- package/dist/tree-operations/node-primitives.js +117 -0
- package/dist/tree-operations/paste/apply.js +1 -1
- package/dist/tree-operations/paste/body-write.d.ts +1 -1
- package/dist/tree-operations/paste/body-write.js +2 -2
- package/dist/tree-operations/paste/container-match.js +4 -2
- package/dist/tree-operations/paste/dispatch.js +2 -1
- package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
- package/dist/tree-operations/paste/focus-target.d.ts +1 -1
- package/dist/tree-operations/paste/list-absorb.js +1 -1
- package/dist/tree-operations/paste/list-break-out.js +1 -1
- package/dist/tree-operations/paste/parent-scope.js +1 -1
- package/dist/tree-operations/paste/paste-replacement.js +1 -1
- package/dist/tree-operations/paste/replace-block-at-parent.d.ts +3 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +5 -2
- package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
- package/dist/tree-operations/paste/replacement-parse.js +22 -0
- package/dist/tree-operations/path-mutate.d.ts +1 -1
- package/dist/tree-operations/path-mutate.js +2 -1
- package/dist/tree-operations/reorder-unit.js +1 -1
- package/dist/tree-operations/reorder.d.ts +5 -2
- package/dist/tree-operations/reorder.js +55 -2
- package/dist/tree-operations/settle.d.ts +105 -0
- package/dist/tree-operations/settle.js +660 -0
- package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
- package/dist/tree-operations/table-grid-clipboard.js +90 -0
- package/dist/tree-operations/unshare.d.ts +15 -77
- package/dist/tree-operations/unshare.js +15 -166
- package/docs/guide/consumer-guide.md +136 -104
- package/docs/guide/plugin-api.md +73 -29
- package/docs/guide/plugin-guide.md +55 -23
- package/package.json +9 -7
- package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
- package/dist/components/blocks/table/TableGrip.svelte +0 -91
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
- package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
- package/dist/components/blocks/table/table-drop-target.js +0 -16
- package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
- package/dist/components/blocks/table/table-reorder-drag.js +0 -97
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ancestry rebuild: raws re-derived along an owned spine chain innermost-first, each
|
|
3
|
+
* container's kind re-derived and the seam at its own slot asked on the way out (editor.md § 9).
|
|
4
|
+
*/
|
|
5
|
+
import { dropChildSpans } from '../schema/child-spans';
|
|
6
|
+
import { trimTrailingLineEnding } from '../core/lines';
|
|
7
|
+
import { perfEnabled, recordRebuildDepth } from '../perf/instruments';
|
|
8
|
+
import { rebuildOwnedContainer, walkUnsharing } from './unshare';
|
|
9
|
+
import { absorbWindowSeams } from './settle';
|
|
10
|
+
import { lineOpensAs, reclassifyContainer } from './content-write';
|
|
11
|
+
import { settleSublistSeparator } from './list/sublist-separator';
|
|
12
|
+
/**
|
|
13
|
+
* Longest prefix of `chain` still attached under `root`, identity-checked level by level. A
|
|
14
|
+
* commit mutation can splice a node out mid-ceremony, and rebuilding the detached node's raw
|
|
15
|
+
* against its emptied children writes `raw: ''`; ancestors above the detachment still rebuild.
|
|
16
|
+
*/
|
|
17
|
+
export function attachedChainPrefix(root, chain) {
|
|
18
|
+
let parentChildren = root.children;
|
|
19
|
+
for (let i = 0; i < chain.length; i++) {
|
|
20
|
+
if (!parentChildren.includes(chain[i]))
|
|
21
|
+
return chain.slice(0, i);
|
|
22
|
+
parentChildren = chain[i].children ?? [];
|
|
23
|
+
}
|
|
24
|
+
return chain;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Rebuild raws along an owned spine chain innermost-first, re-deriving each container's kind and
|
|
28
|
+
* settling the seams at its own slot; chain- rather than path-based so it survives index shifts.
|
|
29
|
+
* Both passes gate on a boundary line of the rebuilt raw moving. `folds` is required-nullable: a
|
|
30
|
+
* fold splices the PARENT's array, so only a caller that reconciles that scope's ids/refs passes
|
|
31
|
+
* a sink.
|
|
32
|
+
*/
|
|
33
|
+
export function rebuildUnsharedChain(root, chain, sharing, folds, grammar, hint) {
|
|
34
|
+
const reclassified = [];
|
|
35
|
+
// The bytes chain[i + 1] held before this pass. It rides up only from a door that named the
|
|
36
|
+
// leaf's own: a caller passing no hint re-derives at every level (editor.md § 9).
|
|
37
|
+
let childPreviousRaw;
|
|
38
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
39
|
+
const node = chain[i];
|
|
40
|
+
const rawBefore = node.raw;
|
|
41
|
+
const child = chain[i + 1];
|
|
42
|
+
rebuildOwnedContainer(node, sharing, child && childPreviousRaw !== undefined
|
|
43
|
+
? childRawChange(node, child, childPreviousRaw, hint?.path[i + 1])
|
|
44
|
+
: undefined);
|
|
45
|
+
if (hint)
|
|
46
|
+
childPreviousRaw = i === chain.length - 1 ? hint.leafPreviousRaw : rawBefore;
|
|
47
|
+
const openerMoved = firstLine(rawBefore) !== firstLine(node.raw);
|
|
48
|
+
const closerMoved = lastLine(rawBefore) !== lastLine(node.raw);
|
|
49
|
+
if (!openerMoved && !closerMoved)
|
|
50
|
+
continue;
|
|
51
|
+
const owner = i === 0 ? null : chain[i - 1];
|
|
52
|
+
const siblings = (owner ?? root).children;
|
|
53
|
+
const index = siblings ? childIndexOf(siblings, node, hint?.path[i]) : -1;
|
|
54
|
+
if (!siblings || index < 0)
|
|
55
|
+
continue;
|
|
56
|
+
if (openerMoved && lineOpensAs(firstLine(node.raw), grammar) !== node.kind) {
|
|
57
|
+
const replacement = reclassifyContainer({ children: siblings }, index, grammar);
|
|
58
|
+
if (replacement) {
|
|
59
|
+
sharing.stamp(replacement);
|
|
60
|
+
reclassified.push({ siblings, index, previous: node, replacement });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Ahead of the seam ask, which then reads the settled bytes: a list rebuilt down to an
|
|
64
|
+
// empty marker owes the paragraph above it a separating line.
|
|
65
|
+
if (openerMoved)
|
|
66
|
+
settleSublistSeparator(siblings, index);
|
|
67
|
+
// After the re-derive: the seam reads whatever occupies the slot now.
|
|
68
|
+
if (folds) {
|
|
69
|
+
const before = folds.length;
|
|
70
|
+
settleSlotSeams({ siblings, owner, depth: i, index, openerMoved, closerMoved }, sharing, folds);
|
|
71
|
+
// A fold re-tiled the owner's children, so its spans describe a shape that is gone.
|
|
72
|
+
if (folds.length > before && owner)
|
|
73
|
+
dropChildSpans(owner);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (perfEnabled())
|
|
77
|
+
recordRebuildDepth(chain.length);
|
|
78
|
+
return reclassified;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The changed-child hint for `node`, or undefined when `child` cannot be placed in it. The path
|
|
82
|
+
* index is a guess; an identity match is what makes it an answer.
|
|
83
|
+
*/
|
|
84
|
+
function childRawChange(node, child, previousRaw, guess) {
|
|
85
|
+
const siblings = node.children;
|
|
86
|
+
if (!siblings)
|
|
87
|
+
return undefined;
|
|
88
|
+
const index = childIndexOf(siblings, child, guess);
|
|
89
|
+
return index < 0 ? undefined : { index, previousRaw };
|
|
90
|
+
}
|
|
91
|
+
/** `indexOf` with a guess first: the scan is O(children) through the `$state` proxy. */
|
|
92
|
+
function childIndexOf(siblings, child, guess) {
|
|
93
|
+
if (guess !== undefined && siblings[guess] === child)
|
|
94
|
+
return guess;
|
|
95
|
+
return siblings.indexOf(child);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ask the joins at a rebuilt container's slot, since its new bytes can stop interrupting a
|
|
99
|
+
* neighbour. `absorbWindowSeams` walks `at - 1 … at + added - 1`, so the two arguments below
|
|
100
|
+
* name exactly the sides whose line moved.
|
|
101
|
+
*/
|
|
102
|
+
function settleSlotSeams(slot, sharing, folds) {
|
|
103
|
+
const { siblings, index, openerMoved, closerMoved } = slot;
|
|
104
|
+
// The rollback snapshot is captured only once a fold is certain: an eager copy here cost
|
|
105
|
+
// O(children) reactive reads on every keystroke inside a large container.
|
|
106
|
+
let before = null;
|
|
107
|
+
const landing = { index, offset: 0 };
|
|
108
|
+
const settled = absorbWindowSeams({ children: siblings }, openerMoved ? index : index + 1, openerMoved && closerMoved ? 1 : 0, index, { op: 'noop' }, sharing, landing, index, () => {
|
|
109
|
+
before ??= siblings.slice();
|
|
110
|
+
});
|
|
111
|
+
if (settled.change.op === 'noop')
|
|
112
|
+
return;
|
|
113
|
+
folds.push({
|
|
114
|
+
depth: slot.depth,
|
|
115
|
+
siblings,
|
|
116
|
+
owner: slot.owner,
|
|
117
|
+
change: settled.change,
|
|
118
|
+
// A non-noop change means a splice ran, so the capture ran first.
|
|
119
|
+
before: before,
|
|
120
|
+
landing
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** The container's opener line. */
|
|
124
|
+
function firstLine(raw) {
|
|
125
|
+
const nl = raw.indexOf('\n');
|
|
126
|
+
return nl < 0 ? raw : raw.slice(0, nl);
|
|
127
|
+
}
|
|
128
|
+
/** The container's closing line: its last line carrying bytes, without the ending. */
|
|
129
|
+
function lastLine(raw) {
|
|
130
|
+
const body = trimTrailingLineEnding(raw);
|
|
131
|
+
const nl = body.lastIndexOf('\n');
|
|
132
|
+
return nl < 0 ? body : body.slice(nl + 1);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Unshare the spine to `path` and rebuild it innermost-first, tolerating paths that ran out of
|
|
136
|
+
* range mid-walk (post-delete rebuild passes). Prefer `rebuildUnsharedChain` when indices may
|
|
137
|
+
* have shifted since the unshare.
|
|
138
|
+
*/
|
|
139
|
+
export function rebuildUnsharedAncestry(root, path, sharing, folds, grammar) {
|
|
140
|
+
const chain = walkUnsharing(root, path, sharing, false);
|
|
141
|
+
return rebuildUnsharedChain(root, chain, sharing, folds, grammar);
|
|
142
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Container children mutations that keep `childIds` in lockstep, for out-of-commit-scope
|
|
3
|
-
* writes (discovered descendants/ancestors — see the `node-
|
|
3
|
+
* writes (discovered descendants/ancestors — see the `node-primitives.ts` header). Inside a
|
|
4
4
|
* commit scope the StructuralChange descriptor owns id/ref sync; here a hand-rolled
|
|
5
5
|
* splice would let the parallel id array drift and break Svelte's keyed-each rendering.
|
|
6
6
|
* The same shape change invalidates `childSpans`, so both parallel arrays settle here.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Container children mutations that keep `childIds` in lockstep, for out-of-commit-scope
|
|
3
|
-
* writes (discovered descendants/ancestors — see the `node-
|
|
3
|
+
* writes (discovered descendants/ancestors — see the `node-primitives.ts` header). Inside a
|
|
4
4
|
* commit scope the StructuralChange descriptor owns id/ref sync; here a hand-rolled
|
|
5
5
|
* splice would let the parallel id array drift and break Svelte's keyed-each rendering.
|
|
6
6
|
* The same shape change invalidates `childSpans`, so both parallel arrays settle here.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content write: every byte that enters a node crosses `updateNodeContent`, the sole
|
|
3
|
+
* re-parse transfer funnel (editor.md § 6, § 8), and the container twin that re-derives a
|
|
4
|
+
* container's kind from its rebuilt raw.
|
|
5
|
+
*/
|
|
6
|
+
import type { AnyBlockKind, CstNode } from '../core/nodes';
|
|
7
|
+
import type { NodeView } from '../core/node-views';
|
|
8
|
+
import { type GrammarView } from '../schema/block-openers';
|
|
9
|
+
import type { SharingState } from './sharing';
|
|
10
|
+
import { type StructuralChange } from './structural-change';
|
|
11
|
+
import { type BodyParentArg, type NodeParent } from './node-primitives';
|
|
12
|
+
/** What a content write settled: its change widened by every fold, and the offset the written
|
|
13
|
+
* text starts at inside that change's window (nonzero once a fold above absorbed it). */
|
|
14
|
+
export interface SettledContent {
|
|
15
|
+
change: StructuralChange;
|
|
16
|
+
textStart: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Update raw and re-parse. A kind change mints the reparsed block into the slot rather than
|
|
20
|
+
* reassigning `kind` in place, and multi-block text mints every parsed block; only a same-kind
|
|
21
|
+
* single-block edit writes in place, so routine typing keeps the node's object identity.
|
|
22
|
+
*/
|
|
23
|
+
export declare function updateNodeContent(parent: BodyParentArg, blockIndex: number, text: string, grammar?: GrammarView, sharing?: SharingState): SettledContent;
|
|
24
|
+
/**
|
|
25
|
+
* Refresh a node in place from its own reparse, keeping its object identity. The id resync is
|
|
26
|
+
* unconditional: the reparse can change the child count while the caller reports `noop`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function adoptReparsedFields(target: CstNode, parsed: CstNode | undefined): void;
|
|
29
|
+
/**
|
|
30
|
+
* What the grammar opens `line` as, read in isolation: asked of the opener registry and never
|
|
31
|
+
* a kind list, so a kind registered later is covered the day it registers.
|
|
32
|
+
*/
|
|
33
|
+
export declare function lineOpensAs(line: string, grammar?: GrammarView): AnyBlockKind;
|
|
34
|
+
/** Whether the ambient grammar still leaves `NEXT_PROSE_LINE` an ordinary paragraph. */
|
|
35
|
+
export declare function probeLineOpensAsProse(grammar?: GrammarView): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Re-derive the container at `index` from its own (already rebuilt) raw, replacing it in the
|
|
38
|
+
* slot when that raw now opens as a different kind (editor.md § 8). Eligibility is the opener
|
|
39
|
+
* registry: registering an opener is exactly the claim that `parse(raw)` reproduces the kind.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reclassifyContainer(parent: NodeParent, index: number, grammar?: GrammarView): CstNode | null;
|
|
42
|
+
/**
|
|
43
|
+
* Where a caret at `offset` in the written text lands once {@link updateNodeContent}'s folds
|
|
44
|
+
* settled: an absorb ABOVE leaves the predecessor holding the bytes, so the slot the gesture
|
|
45
|
+
* named is gone and the offset carries what that predecessor put in front of it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function settledCaretTarget(settled: SettledContent, at: number, offset: number, children: readonly NodeView[]): {
|
|
48
|
+
index: number;
|
|
49
|
+
offset: number;
|
|
50
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content write: every byte that enters a node crosses `updateNodeContent`, the sole
|
|
3
|
+
* re-parse transfer funnel (editor.md § 6, § 8), and the container twin that re-derives a
|
|
4
|
+
* container's kind from its rebuilt raw.
|
|
5
|
+
*/
|
|
6
|
+
import { isBlankParagraph, parse } from '../core/parser';
|
|
7
|
+
import { escalatedFenceLength, matchFenceOpen } from '../core/parsers/fence-syntax';
|
|
8
|
+
import { isBlockOpenerRegistered } from '../schema/block-openers';
|
|
9
|
+
import { trailingLineEnding } from '../core/lines';
|
|
10
|
+
import { assignChildIdsDeep } from '../block-id';
|
|
11
|
+
import { perfEnabled, recordContainerKindReparse } from '../perf/instruments';
|
|
12
|
+
import { getBlockKindDescriptor, tryGetBlockKindDescriptor } from '../schema/block-kind-descriptor';
|
|
13
|
+
import { resyncChildIds } from './children';
|
|
14
|
+
import { spliceMany } from './splice-many';
|
|
15
|
+
import { replacePreservingFirst } from './structural-change';
|
|
16
|
+
import { NEXT_PROSE_LINE, ensureEditableContainers, forBody, writeOwnRaw } from './node-primitives';
|
|
17
|
+
import { absorbWindowSeams, focusTargetInReplacement, releaseWrapPeel, restoreSeparatorAfterBlank, restoreSeparatorOnFill, settleSeparatorOnBlank, widenForTailMint } from './settle';
|
|
18
|
+
/**
|
|
19
|
+
* Update raw and re-parse. A kind change mints the reparsed block into the slot rather than
|
|
20
|
+
* reassigning `kind` in place, and multi-block text mints every parsed block; only a same-kind
|
|
21
|
+
* single-block edit writes in place, so routine typing keeps the node's object identity.
|
|
22
|
+
*/
|
|
23
|
+
export function updateNodeContent(parent, blockIndex, text, grammar, sharing) {
|
|
24
|
+
const wasBlank = isBlankParagraph(parent.children[blockIndex]);
|
|
25
|
+
const change = writeParsedContent(parent, blockIndex, text, grammar);
|
|
26
|
+
const lastWritten = lastMintedIndex(change, blockIndex);
|
|
27
|
+
// One blank line served BOTH sides: it separated this block from the one above and stood in
|
|
28
|
+
// as the separator of the block beneath it. Ending it owes each their own.
|
|
29
|
+
if (wasBlank && !isBlankParagraph(parent.children[blockIndex])) {
|
|
30
|
+
restoreSeparatorOnFill(parent, blockIndex, sharing);
|
|
31
|
+
restoreSeparatorAfterBlank(parent, followerIndexAfter(change, blockIndex), sharing);
|
|
32
|
+
releaseWrapPeel(parent, lastWritten);
|
|
33
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, change, sharing);
|
|
34
|
+
}
|
|
35
|
+
// The reverse transition: the block IS the separating line now, so the run it joins gives
|
|
36
|
+
// back the second one. The last block minted is the one that meets the follower.
|
|
37
|
+
if (!wasBlank && isBlankParagraph(parent.children[lastWritten])) {
|
|
38
|
+
const settled = parent.children.length;
|
|
39
|
+
settleSeparatorOnBlank(parent, lastWritten, sharing);
|
|
40
|
+
const widened = widenForTailMint(change, settled, parent.children.length);
|
|
41
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, widened, sharing);
|
|
42
|
+
}
|
|
43
|
+
// Same-kind typing INSIDE content must never pay a neighbour reparse.
|
|
44
|
+
if (change.op === 'noop')
|
|
45
|
+
return { change, textStart: 0 };
|
|
46
|
+
return settleWriteSeams(parent, blockIndex, lastWritten, change, sharing);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ask every join the write disturbed and report where the written text ended up: an absorb
|
|
50
|
+
* ABOVE leaves the predecessor standing, so its bytes now sit in front of the text.
|
|
51
|
+
*/
|
|
52
|
+
function settleWriteSeams(parent, blockIndex, lastWritten, change, sharing) {
|
|
53
|
+
const tracked = { index: blockIndex, offset: 0 };
|
|
54
|
+
const settled = absorbWindowSeams(parent, blockIndex, lastWritten - blockIndex + 1, blockIndex, change, sharing, tracked);
|
|
55
|
+
return {
|
|
56
|
+
change: settled.change,
|
|
57
|
+
textStart: textOffsetInWindow(parent.children, settled.change, tracked)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The tracked position as an offset in the settled window's committed text, the space every
|
|
62
|
+
* caret door measures in, where the head block's own leading trivia is outside the window.
|
|
63
|
+
*/
|
|
64
|
+
function textOffsetInWindow(children, change, tracked) {
|
|
65
|
+
const at = change.op === 'noop' ? tracked.index : change.at;
|
|
66
|
+
let pos = 0;
|
|
67
|
+
for (let i = at; i < tracked.index; i++) {
|
|
68
|
+
pos += (i === at ? 0 : children[i].leadingTrivia.length) + children[i].raw.length;
|
|
69
|
+
}
|
|
70
|
+
const ownTrivia = tracked.index > at ? children[tracked.index].leadingTrivia.length : 0;
|
|
71
|
+
return pos + ownTrivia + tracked.offset;
|
|
72
|
+
}
|
|
73
|
+
/** Where the filled block's follower ended up: a multi-block reparse pushes it down. */
|
|
74
|
+
function followerIndexAfter(change, blockIndex) {
|
|
75
|
+
return change.op === 'replace' ? change.at + change.newCount : blockIndex + 1;
|
|
76
|
+
}
|
|
77
|
+
/** The last block the write left in the slot: a multi-block reparse mints past the first. */
|
|
78
|
+
function lastMintedIndex(change, blockIndex) {
|
|
79
|
+
return change.op === 'replace' ? change.at + change.newCount - 1 : blockIndex;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The written bytes parsed, with the construct they leave OPEN closed off first: an unterminated
|
|
83
|
+
* construct reads every block below it as its body at the next parse, and the seam settle would
|
|
84
|
+
* converge the live tree to exactly that reading.
|
|
85
|
+
*/
|
|
86
|
+
function closeWrittenConstruct(parent, blockIndex, text, oldKind, grammar) {
|
|
87
|
+
// Fragment scope: this is one block's bytes, whatever its position, so a position-scoped
|
|
88
|
+
// kind must not mint here.
|
|
89
|
+
const parsed = parse(text, { grammar, scope: 'fragment' });
|
|
90
|
+
if (blockIndex + 1 >= parent.children.length)
|
|
91
|
+
return { text, parsed };
|
|
92
|
+
if (parsed.children.length === 1 && parsed.children[0].kind === oldKind)
|
|
93
|
+
return { text, parsed };
|
|
94
|
+
const terminator = openConstructTerminator(text, parsed.children, grammar);
|
|
95
|
+
if (!terminator)
|
|
96
|
+
return { text, parsed };
|
|
97
|
+
const closed = text + terminator;
|
|
98
|
+
return { text: closed, parsed: parse(closed, { grammar, scope: 'fragment' }) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The terminator written bytes owe when their last construct absorbs to EOF, asked of the grammar
|
|
102
|
+
* rather than a kind list. Null when the bytes terminate themselves, or when no fence opener
|
|
103
|
+
* explains the absorb (the one family whose closer its opener determines).
|
|
104
|
+
*/
|
|
105
|
+
function openConstructTerminator(text, blocks, grammar) {
|
|
106
|
+
// The terminator is a line of its own, so bytes whose last line is still open have none to
|
|
107
|
+
// append to (G4.20's unterminated tail slice, which absorbs nothing while it stands alone).
|
|
108
|
+
if (!text.endsWith('\n') || blocks.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
const ending = trailingLineEnding(text);
|
|
111
|
+
const probe = parse(text + ending + NEXT_PROSE_LINE + ending, { grammar, scope: 'fragment' });
|
|
112
|
+
if (probe.children.length !== blocks.length)
|
|
113
|
+
return null;
|
|
114
|
+
const raw = blocks[blocks.length - 1].raw;
|
|
115
|
+
const nl = raw.indexOf('\n');
|
|
116
|
+
const opener = matchFenceOpen(nl < 0 ? raw : raw.slice(0, nl));
|
|
117
|
+
if (!opener)
|
|
118
|
+
return null;
|
|
119
|
+
const body = nl < 0 ? '' : raw.slice(nl + 1);
|
|
120
|
+
const run = escalatedFenceLength(body, opener.marker, opener.length);
|
|
121
|
+
return opener.indent + opener.marker.repeat(run) + ending;
|
|
122
|
+
}
|
|
123
|
+
function writeParsedContent(parent, blockIndex, text, grammar) {
|
|
124
|
+
const node = parent.children[blockIndex];
|
|
125
|
+
const oldKind = node.kind;
|
|
126
|
+
const oldDescriptor = getBlockKindDescriptor(oldKind);
|
|
127
|
+
// Ahead of every reparse below, so a write lands on the kind its committed bytes
|
|
128
|
+
// describe, not the kind the pre-escape text would parse to (`bodyWrite`).
|
|
129
|
+
const bodyText = forBody(parent, text);
|
|
130
|
+
// A context-dependent kind has no standalone recognizer, so reparsing would downgrade it:
|
|
131
|
+
// keep the kind and write raw through its own legality pass.
|
|
132
|
+
if (oldDescriptor.contextDependentKind) {
|
|
133
|
+
writeOwnRaw(node, bodyText, grammar);
|
|
134
|
+
return { op: 'noop' };
|
|
135
|
+
}
|
|
136
|
+
const { text: newText, parsed: reparsed } = closeWrittenConstruct(parent, blockIndex, bodyText, oldKind, grammar);
|
|
137
|
+
const parsed = reparsed.children;
|
|
138
|
+
const first = parsed[0];
|
|
139
|
+
// A marker-consuming container (a GitHub alert) needs its raw rebuilt from the backfilled
|
|
140
|
+
// body, or G1.1 stale-raw fires.
|
|
141
|
+
const firstBackfilled = !!first && isEmptyEditableContainer(first);
|
|
142
|
+
if (first)
|
|
143
|
+
ensureEditableContainers(first);
|
|
144
|
+
// Text-leading blanks fold into the first block's raw (the single-block shape); the
|
|
145
|
+
// rest keep their own trivia.
|
|
146
|
+
if (parsed.length > 1) {
|
|
147
|
+
const rest = parsed.slice(1);
|
|
148
|
+
for (const sibling of rest)
|
|
149
|
+
ensureEditableContainers(sibling);
|
|
150
|
+
first.raw = first.leadingTrivia + first.raw;
|
|
151
|
+
first.leadingTrivia = node.leadingTrivia;
|
|
152
|
+
if (firstBackfilled)
|
|
153
|
+
reconcileBackfilledRaw(first);
|
|
154
|
+
// The peeled line has no follower inside the splice, so it stays in raw.
|
|
155
|
+
rest[rest.length - 1].raw += reparsed.suffix;
|
|
156
|
+
spliceMany(parent.children, blockIndex, 1, parsed);
|
|
157
|
+
return replacePreservingFirst(blockIndex, 1, parsed.length);
|
|
158
|
+
}
|
|
159
|
+
const newKind = first?.kind ?? 'paragraph';
|
|
160
|
+
// In-place refresh keeps the node's object identity: component, IME state, and inline
|
|
161
|
+
// cache are all keyed on it.
|
|
162
|
+
if (newKind === oldKind) {
|
|
163
|
+
node.raw = newText;
|
|
164
|
+
adoptReparsedFields(node, first);
|
|
165
|
+
if (firstBackfilled)
|
|
166
|
+
reconcileBackfilledRaw(node);
|
|
167
|
+
return { op: 'noop' };
|
|
168
|
+
}
|
|
169
|
+
const replacement = first ?? { kind: 'paragraph', leadingTrivia: '', raw: newText };
|
|
170
|
+
replacement.raw = newText;
|
|
171
|
+
replacement.leadingTrivia = node.leadingTrivia;
|
|
172
|
+
if (firstBackfilled)
|
|
173
|
+
reconcileBackfilledRaw(replacement);
|
|
174
|
+
parent.children.splice(blockIndex, 1, replacement);
|
|
175
|
+
return replacePreservingFirst(blockIndex, 1, 1);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Refresh a node in place from its own reparse, keeping its object identity. The id resync is
|
|
179
|
+
* unconditional: the reparse can change the child count while the caller reports `noop`.
|
|
180
|
+
*/
|
|
181
|
+
export function adoptReparsedFields(target, parsed) {
|
|
182
|
+
target.metadata = parsed?.metadata;
|
|
183
|
+
target.children = parsed?.children;
|
|
184
|
+
resyncChildIds(target);
|
|
185
|
+
assignChildIdsDeep(target);
|
|
186
|
+
target.innerPrefix = parsed?.innerPrefix;
|
|
187
|
+
target.innerSuffix = parsed?.innerSuffix;
|
|
188
|
+
}
|
|
189
|
+
/** A container `ensureEditableContainers` will backfill; read BEFORE the backfill runs. */
|
|
190
|
+
function isEmptyEditableContainer(node) {
|
|
191
|
+
const d = getBlockKindDescriptor(node.kind);
|
|
192
|
+
return d.isContainer && d.blockFocus !== 'whole-block' && (node.children?.length ?? 0) === 0;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Sync a just-backfilled container's `raw` to its synthesized body. Needed only for a
|
|
196
|
+
* marker-consuming container whose typed raw lacks a blank body line (`> [!TYPE]`).
|
|
197
|
+
*/
|
|
198
|
+
function reconcileBackfilledRaw(node) {
|
|
199
|
+
getBlockKindDescriptor(node.kind).rebuildRaw?.(node);
|
|
200
|
+
}
|
|
201
|
+
// ── Container kind re-derivation ──
|
|
202
|
+
/**
|
|
203
|
+
* What the grammar opens `line` as, read in isolation: asked of the opener registry and never
|
|
204
|
+
* a kind list, so a kind registered later is covered the day it registers.
|
|
205
|
+
*/
|
|
206
|
+
export function lineOpensAs(line, grammar) {
|
|
207
|
+
return parse(`${line}\n`, { grammar, scope: 'fragment' }).children[0]?.kind ?? 'paragraph';
|
|
208
|
+
}
|
|
209
|
+
/** Whether the ambient grammar still leaves `NEXT_PROSE_LINE` an ordinary paragraph. */
|
|
210
|
+
export function probeLineOpensAsProse(grammar) {
|
|
211
|
+
return lineOpensAs(NEXT_PROSE_LINE, grammar) === 'paragraph';
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Re-derive the container at `index` from its own (already rebuilt) raw, replacing it in the
|
|
215
|
+
* slot when that raw now opens as a different kind (editor.md § 8). Eligibility is the opener
|
|
216
|
+
* registry: registering an opener is exactly the claim that `parse(raw)` reproduces the kind.
|
|
217
|
+
*/
|
|
218
|
+
export function reclassifyContainer(parent, index, grammar) {
|
|
219
|
+
const node = parent.children[index];
|
|
220
|
+
if (!node)
|
|
221
|
+
return null;
|
|
222
|
+
const descriptor = tryGetBlockKindDescriptor(node.kind);
|
|
223
|
+
if (!descriptor?.isContainer || !isBlockOpenerRegistered(node.kind))
|
|
224
|
+
return null;
|
|
225
|
+
if (perfEnabled())
|
|
226
|
+
recordContainerKindReparse();
|
|
227
|
+
const parsed = parse(node.raw, { grammar, scope: 'fragment' }).children;
|
|
228
|
+
// A container's raw is one block by construction; a multi-block reparse means bytes
|
|
229
|
+
// this seam has no slot for, left to the gesture that owns the mutation.
|
|
230
|
+
if (parsed.length !== 1 || parsed[0].kind === node.kind)
|
|
231
|
+
return null;
|
|
232
|
+
const replacement = parsed[0];
|
|
233
|
+
const backfilled = isEmptyEditableContainer(replacement);
|
|
234
|
+
ensureEditableContainers(replacement);
|
|
235
|
+
// The slot's trivia is authoritative, so restore the bytes before overwriting it or
|
|
236
|
+
// anything the parse split off the front vanishes with it.
|
|
237
|
+
replacement.raw = node.raw;
|
|
238
|
+
replacement.leadingTrivia = node.leadingTrivia;
|
|
239
|
+
if (backfilled)
|
|
240
|
+
reconcileBackfilledRaw(replacement);
|
|
241
|
+
// A freshly-parsed node carries no childIds and this swap publishes under the slot's
|
|
242
|
+
// reused component instance, so undefined keys would reach the nested keyed `{#each}`.
|
|
243
|
+
assignChildIdsDeep(replacement);
|
|
244
|
+
parent.children[index] = replacement;
|
|
245
|
+
// Write-then-re-read (tree-operations/unshare.ts header).
|
|
246
|
+
return parent.children[index];
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Where a caret at `offset` in the written text lands once {@link updateNodeContent}'s folds
|
|
250
|
+
* settled: an absorb ABOVE leaves the predecessor holding the bytes, so the slot the gesture
|
|
251
|
+
* named is gone and the offset carries what that predecessor put in front of it.
|
|
252
|
+
*/
|
|
253
|
+
export function settledCaretTarget(settled, at, offset, children) {
|
|
254
|
+
const { change, textStart } = settled;
|
|
255
|
+
if (change.op !== 'replace')
|
|
256
|
+
return { index: at, offset };
|
|
257
|
+
const shifted = offset + textStart;
|
|
258
|
+
if (change.newCount <= 1)
|
|
259
|
+
return { index: change.at, offset: shifted };
|
|
260
|
+
const blocks = children.slice(change.at, change.at + change.newCount);
|
|
261
|
+
const target = focusTargetInReplacement(blocks, shifted);
|
|
262
|
+
return { index: change.at + target.index, offset: target.offset };
|
|
263
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export type { NodeParent
|
|
2
|
-
export {
|
|
1
|
+
export type { NodeParent } from './node-primitives';
|
|
2
|
+
export { nodeAt, emptyParagraph, paragraphNode, ensureEditableContainers, normalizeReplacementTrivia } from './node-primitives';
|
|
3
|
+
export { deleteNode, focusTargetInReplacement, restoreSeparatorOnFill, dropDoubledSeparator } from './settle';
|
|
4
|
+
export { updateNodeContent, reclassifyContainer } from './content-write';
|
|
5
|
+
export type { MergeIntoPrevResult, MergeResult, SplitResult } from './node-ops';
|
|
6
|
+
export { splitNode, assertSplitLanding, assertSingleNodeSink, mergeWithNext, mergeIntoPrevDeepLeaf } from './node-ops';
|
|
3
7
|
export { unwrapFirstItemFromList, mergeListItemIntoPrevious } from './list/unwrap-merge';
|
|
4
8
|
export { renumberOrderedList, normalizeItemMarkerToList } from './list/ordered-markers';
|
|
5
9
|
export { isItemUserEmpty } from './list/empty-check';
|
|
@@ -11,6 +15,7 @@ export { insertEmptyRow, insertEmptyColumn, deleteRow, deleteColumn, cycleAlignm
|
|
|
11
15
|
export { copyRectangleAsSubTable } from './sub-table-copy';
|
|
12
16
|
export type { CellPos } from './sub-table-copy';
|
|
13
17
|
export { cascadeCleanupEmptyAncestors } from './cleanup';
|
|
14
|
-
export { ensureUnsharedPath, ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedChildren, ensureUnsharedSubtree, rebuildOwnedContainer
|
|
18
|
+
export { ensureUnsharedPath, ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedChildren, ensureUnsharedSubtree, rebuildOwnedContainer } from './unshare';
|
|
19
|
+
export { rebuildUnsharedChain, rebuildUnsharedAncestry } from './chain-rebuild';
|
|
15
20
|
export { buildPastedReplacement } from './paste/paste-replacement';
|
|
16
21
|
export { cloneDocument, cloneNode } from './clone';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { nodeAt, emptyParagraph, paragraphNode, ensureEditableContainers, normalizeReplacementTrivia } from './node-primitives';
|
|
2
|
+
export { deleteNode, focusTargetInReplacement, restoreSeparatorOnFill, dropDoubledSeparator } from './settle';
|
|
3
|
+
export { updateNodeContent, reclassifyContainer } from './content-write';
|
|
4
|
+
export { splitNode, assertSplitLanding, assertSingleNodeSink, mergeWithNext, mergeIntoPrevDeepLeaf } from './node-ops';
|
|
2
5
|
export { unwrapFirstItemFromList, mergeListItemIntoPrevious } from './list/unwrap-merge';
|
|
3
6
|
export { renumberOrderedList, normalizeItemMarkerToList } from './list/ordered-markers';
|
|
4
7
|
export { isItemUserEmpty } from './list/empty-check';
|
|
@@ -9,6 +12,7 @@ export { liftFirstChildKeepingContainer } from './container-lift';
|
|
|
9
12
|
export { insertEmptyRow, insertEmptyColumn, deleteRow, deleteColumn, cycleAlignment } from './table-mutations';
|
|
10
13
|
export { copyRectangleAsSubTable } from './sub-table-copy';
|
|
11
14
|
export { cascadeCleanupEmptyAncestors } from './cleanup';
|
|
12
|
-
export { ensureUnsharedPath, ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedChildren, ensureUnsharedSubtree, rebuildOwnedContainer
|
|
15
|
+
export { ensureUnsharedPath, ensureUnsharedChild, ensureUnsharedNode, ensureUnsharedChildren, ensureUnsharedSubtree, rebuildOwnedContainer } from './unshare';
|
|
16
|
+
export { rebuildUnsharedChain, rebuildUnsharedAncestry } from './chain-rebuild';
|
|
13
17
|
export { buildPastedReplacement } from './paste/paste-replacement';
|
|
14
18
|
export { cloneDocument, cloneNode } from './clone';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { metadataOf } from '../../core/nodes';
|
|
2
2
|
import { trailingLineEnding } from '../../core/lines';
|
|
3
3
|
import { cloneNode } from '../clone';
|
|
4
|
-
import { emptyParagraph } from '../node-
|
|
4
|
+
import { emptyParagraph } from '../node-primitives';
|
|
5
5
|
import { assembleListHalf } from './list-builders';
|
|
6
6
|
import { partitionItemChildren } from './item-partition';
|
|
7
7
|
import { orderedBaseOf } from './ordered-markers';
|
|
@@ -110,20 +110,20 @@ function relocateRemainingChildren(list, targetPath, targetItem, currentItem, li
|
|
|
110
110
|
for (let i = 0; i < child.children.length; i++) {
|
|
111
111
|
const item = sharing ? ensureUnsharedChild(child, i, sharing) : child.children[i];
|
|
112
112
|
item.leadingTrivia = '';
|
|
113
|
-
// discovered-descendant mutation, see node-
|
|
113
|
+
// discovered-descendant mutation, see node-primitives.ts header
|
|
114
114
|
pushChild(depthOneList, item);
|
|
115
115
|
}
|
|
116
116
|
rebuildListRaw(depthOneList);
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
|
-
// discovered-descendant mutation, see node-
|
|
119
|
+
// discovered-descendant mutation, see node-primitives.ts header
|
|
120
120
|
pushChild(targetItem, child);
|
|
121
121
|
}
|
|
122
122
|
else {
|
|
123
123
|
// A trailing paragraph keeps its blank-line separator, or the two lazy-continue
|
|
124
124
|
// into one on reload. Other leaves start fresh and need none.
|
|
125
125
|
child.leadingTrivia = child.kind === 'paragraph' ? lineEnding : '';
|
|
126
|
-
// discovered-descendant mutation, see node-
|
|
126
|
+
// discovered-descendant mutation, see node-primitives.ts header
|
|
127
127
|
pushChild(targetItem, child);
|
|
128
128
|
}
|
|
129
129
|
}
|