@miliastry/quasar 1.0.6 → 1.1.0
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/dist/Visuals/lyne.css +117 -6
- package/dist/Visuals/osu.css +36 -5
- package/dist/index.js +9664 -3282
- package/dist/index.mjs +9535 -3283
- package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
- package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
- package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
- package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
- package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
- package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
- package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
- package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
- package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
- package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
- package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
- package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
- package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
- package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
- package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
- package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
- package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
- package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
- package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
- package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
- package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
- package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
- package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
- package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
- package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
- package/dist/types/Analysis/index.d.ts +40 -0
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
- package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
- package/dist/types/BBCode/Parser.d.ts +78 -0
- package/dist/types/BBCode/index.d.ts +4 -0
- package/dist/types/Collab/positions.d.ts +55 -0
- package/dist/types/Commands/Command.d.ts +37 -0
- package/dist/types/Commands/CommandRegistry.d.ts +48 -0
- package/dist/types/Commands/DeleteNode.d.ts +7 -0
- package/dist/types/Commands/InsertText.d.ts +8 -0
- package/dist/types/Commands/SplitMerge.d.ts +9 -0
- package/dist/types/Commands/WrapInTag.d.ts +8 -0
- package/dist/types/Commands/index.d.ts +6 -0
- package/dist/types/Diff/TreeDiffer.d.ts +52 -0
- package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
- package/dist/types/Diff/index.d.ts +2 -0
- package/dist/types/Edits/EditPlan.d.ts +144 -0
- package/dist/types/Edits/Optimizer.d.ts +78 -0
- package/dist/types/Edits/Rules/Rule.d.ts +91 -0
- package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
- package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
- package/dist/types/Edits/Rules/index.d.ts +9 -0
- package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
- package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
- package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
- package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
- package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
- package/dist/types/Edits/applyEdits.d.ts +36 -0
- package/dist/types/Edits/index.d.ts +6 -0
- package/dist/types/Events/EventBus.d.ts +103 -0
- package/dist/types/Events/index.d.ts +2 -0
- package/dist/types/Formatter/Formatter.d.ts +32 -0
- package/dist/types/Formatter/index.d.ts +2 -0
- package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
- package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
- package/dist/types/Incremental/BracketIndex.d.ts +101 -0
- package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
- package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
- package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
- package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
- package/dist/types/Incremental/index.d.ts +4 -0
- package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
- package/dist/types/Lexer/Lexer.d.ts +54 -0
- package/dist/types/Lexer/index.d.ts +4 -0
- package/dist/types/Linter/Linter.d.ts +58 -0
- package/dist/types/Linter/index.d.ts +2 -0
- package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
- package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
- package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
- package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
- package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
- package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
- package/dist/types/Model/DocumentModel.d.ts +318 -0
- package/dist/types/Model/NodeFactory.d.ts +29 -0
- package/dist/types/Model/TagRegistry.d.ts +165 -0
- package/dist/types/Model/index.d.ts +5 -0
- package/dist/types/Plugins/PluginAPI.d.ts +44 -0
- package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
- package/dist/types/Plugins/index.d.ts +3 -0
- package/dist/types/Queries/QueryEngine.d.ts +32 -0
- package/dist/types/Queries/index.d.ts +1 -0
- package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
- package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
- package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
- package/dist/types/RenderPipeline/index.d.ts +4 -0
- package/dist/types/Repair/NestingRepair.d.ts +48 -0
- package/dist/types/Repair/index.d.ts +1 -0
- package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
- package/dist/types/Semantic/index.d.ts +2 -0
- package/dist/types/Symbols/SymbolTable.d.ts +47 -0
- package/dist/types/Symbols/index.d.ts +1 -0
- package/dist/types/Syntax/GreenNode.d.ts +154 -0
- package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
- package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
- package/dist/types/Syntax/RedNode.d.ts +280 -0
- package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
- package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
- package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
- package/dist/types/Syntax/greenEdit.d.ts +64 -0
- package/dist/types/Syntax/hash.d.ts +17 -0
- package/dist/types/Syntax/index.d.ts +8 -0
- package/dist/types/Syntax/nodeAttr.d.ts +63 -0
- package/dist/types/Syntax/partition.d.ts +67 -0
- package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
- package/dist/types/Tokens/TokenResolver.d.ts +25 -0
- package/dist/types/Tokens/index.d.ts +1 -0
- package/dist/types/Transactions/Transaction.d.ts +36 -0
- package/dist/types/Transactions/UndoManager.d.ts +63 -0
- package/dist/types/Transactions/index.d.ts +3 -0
- package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
- package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
- package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
- package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
- package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
- package/dist/types/Transformers/Transformer.d.ts +17 -0
- package/dist/types/Types/core.d.ts +104 -0
- package/dist/types/Types/diagnostics.d.ts +85 -0
- package/dist/types/Types/index.d.ts +11 -0
- package/dist/types/Types/operations.d.ts +109 -0
- package/dist/types/Types/queries.d.ts +67 -0
- package/dist/types/Types/symbols.d.ts +42 -0
- package/dist/types/Types/tokens.d.ts +75 -0
- package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
- package/dist/types/Utils/ColorMath.d.ts +92 -0
- package/dist/types/Utils/EffectMath.d.ts +643 -0
- package/dist/types/Utils/TagVocabulary.d.ts +104 -0
- package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
- package/dist/types/Utils/color.d.ts +29 -0
- package/dist/types/Utils/dom-to-svg.d.ts +28 -0
- package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
- package/dist/types/Utils/treeTransformers.d.ts +160 -0
- package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
- package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
- package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
- package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
- package/dist/types/Visitors/JSONExporter.d.ts +42 -0
- package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
- package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
- package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
- package/dist/types/Visitors/Visitor.d.ts +35 -0
- package/dist/types/Visitors/index.d.ts +9 -0
- package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
- package/dist/types/Visuals/index.d.ts +38 -0
- package/dist/types/index.d.ts +154 -0
- package/package.json +14 -10
- package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
- package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
- package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
- package/src/Analysis/Pipeline/Pipeline.ts +15 -2
- package/src/Analysis/index.ts +8 -2
- package/src/BBCode/BBCodeDocumentModel.ts +3 -0
- package/src/BBCode/BBCodeToGreenNode.ts +18 -12
- package/src/BBCode/Parser.ts +38 -1
- package/src/Edits/EditPlan.ts +290 -0
- package/src/Edits/Optimizer.ts +185 -0
- package/src/Edits/Rules/Rule.ts +145 -0
- package/src/Edits/Rules/dropEmptyTags.ts +110 -0
- package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
- package/src/Edits/Rules/index.ts +28 -0
- package/src/Edits/Rules/mergeAdjacent.ts +224 -0
- package/src/Edits/Rules/reorderWrappers.ts +154 -0
- package/src/Edits/Rules/shortenHex.ts +79 -0
- package/src/Edits/Rules/tagValue.ts +105 -0
- package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
- package/src/Edits/applyEdits.ts +54 -0
- package/src/Edits/index.ts +22 -0
- package/src/Events/EventBus.ts +18 -0
- package/src/HTML/HTMLToGreenNode.ts +185 -37
- package/src/Incremental/BracketIndex.ts +249 -0
- package/src/Incremental/IncrementalParser.ts +464 -41
- package/src/Lexer/BBCodeLexer.ts +25 -3
- package/src/Lexer/index.ts +1 -1
- package/src/Markdown/MarkdownAST.ts +65 -2
- package/src/Markdown/MarkdownLexer.ts +69 -14
- package/src/Markdown/MarkdownParser.ts +749 -62
- package/src/Markdown/MarkdownToGreenNode.ts +148 -27
- package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
- package/src/Model/DocumentModel.ts +138 -5
- package/src/Model/TagRegistry.ts +129 -134
- package/src/Reconciler/SurgicalReconciler.ts +460 -0
- package/src/Repair/NestingRepair.ts +116 -0
- package/src/Repair/index.ts +1 -0
- package/src/Semantic/SemanticAnalyzer.ts +1802 -65
- package/src/Syntax/GreenNodePool.ts +12 -12
- package/src/Syntax/RedNode.ts +17 -3
- package/src/Syntax/RedNodeStore.ts +9 -1
- package/src/Syntax/nodeAttr.ts +172 -0
- package/src/Tokens/TokenResolver.ts +55 -0
- package/src/Tokens/index.ts +1 -0
- package/src/Types/core.ts +15 -0
- package/src/Utils/ColorMath.ts +276 -55
- package/src/Utils/EffectMath.ts +2462 -0
- package/src/Utils/TagVocabulary.ts +141 -0
- package/src/Utils/dom-to-svg.ts +28 -12
- package/src/Visitors/BBBlocksExporter.ts +15 -1
- package/src/Visitors/BBCodeExporter.ts +319 -32
- package/src/Visitors/BlockPatcher.ts +87 -11
- package/src/Visitors/DOMMorpher.ts +15 -5
- package/src/Visitors/HTMLRenderer.ts +374 -151
- package/src/Visitors/MarkdownExporter.ts +98 -41
- package/src/Visitors/TiptapExporter.ts +1 -0
- package/src/Visuals/index.ts +7 -4
- package/src/Visuals/lyne.css +117 -6
- package/src/Visuals/osu.css +36 -5
- package/src/index.ts +172 -5
- package/dist/index.d.mts +0 -4612
- package/dist/index.d.ts +0 -4612
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
- package/src/Analysis/__tests__/Integration.test.ts +0 -162
- package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
- package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
- package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
- package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
- package/src/Syntax/__tests__/RedNode.test.ts +0 -81
- package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
- package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
- package/src/Tests/BlockPatcher.test.ts +0 -437
- package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
- package/src/Tests/BoxDrawer.test.ts +0 -217
- package/src/Tests/BoxRichTitle.test.ts +0 -105
- package/src/Tests/Chars500kBenchmark.test.ts +0 -151
- package/src/Tests/Chars500kEdits.test.ts +0 -321
- package/src/Tests/CollabPositions.test.ts +0 -146
- package/src/Tests/CompilerPathProfiling.test.ts +0 -186
- package/src/Tests/DOMMorpher.test.ts +0 -142
- package/src/Tests/DomPatchPerf.test.ts +0 -60
- package/src/Tests/EffectSegments.snapshot.json +0 -616
- package/src/Tests/EffectSegments.test.ts +0 -68
- package/src/Tests/FindNodeAtOffset.test.ts +0 -65
- package/src/Tests/Fuzzer.test.ts +0 -166
- package/src/Tests/GreenNodePool.test.ts +0 -153
- package/src/Tests/Lexer.test.ts +0 -238
- package/src/Tests/LyneMode.test.ts +0 -291
- package/src/Tests/ModelCoherence.test.ts +0 -180
- package/src/Tests/Partition.test.ts +0 -238
- package/src/Tests/PluginTags.test.ts +0 -150
- package/src/Tests/ProblematicSection.test.ts +0 -46
- package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
- package/src/Tests/RedReuse.test.ts +0 -134
- package/src/Tests/ReproDelete20k.test.ts +0 -62
- package/src/Tests/SemanticValidators.test.ts +0 -136
- package/src/Tests/StableNodeIds.test.ts +0 -210
- package/src/Tests/StudioColorBloat.test.ts +0 -25
- package/src/Tests/StudioDebugText.test.ts +0 -27
- package/src/Tests/StudioTrailingChar.test.ts +0 -25
- package/src/Tests/StudioValidText.test.ts +0 -25
- package/src/Tests/UrlImgBug.test.ts +0 -23
- package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
- package/src/Tests/referenceDocument.ts +0 -119
- package/src/Utils/dom-to-svg.test.ts +0 -86
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — GreenNode (Immutable Syntax Tree)
|
|
3
|
+
*
|
|
4
|
+
* The Green Tree is IMMUTABLE. It never changes after creation.
|
|
5
|
+
* This is the "compiled" representation of the source text.
|
|
6
|
+
*
|
|
7
|
+
* Properties:
|
|
8
|
+
* - Fully immutable (readonly)
|
|
9
|
+
* - Contains only structural information (kind, text, children, widths)
|
|
10
|
+
* - **No absolute position** — see below
|
|
11
|
+
* - No parent references
|
|
12
|
+
* - No identity (IDs live in Red Tree)
|
|
13
|
+
* - Can be shared across document versions
|
|
14
|
+
* - Safe to cache
|
|
15
|
+
*
|
|
16
|
+
* ─── Why there is no `range` ────────────────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* A green node knows how WIDE it is, not WHERE it is. That is the whole point:
|
|
19
|
+
* the two `[color=#e8b04b]` runs in a gradient are the same structure, and if
|
|
20
|
+
* each carried its own offsets they could never be the same object. Measured on
|
|
21
|
+
* the reference document: 1736 nodes, but only 702 distinct structures — 59.6%
|
|
22
|
+
* of the tree is duplicate.
|
|
23
|
+
*
|
|
24
|
+
* Position is a property of a node's PLACE in a tree, so it lives on the red
|
|
25
|
+
* node, which is the thing that has a place. `RedNode.range` computes it once
|
|
26
|
+
* at construction by accumulating widths from the root and caches it, so
|
|
27
|
+
* consumers see exactly the same `{start, end}` they always did.
|
|
28
|
+
*
|
|
29
|
+
* This also makes incremental editing cheaper: text inserted before a subtree
|
|
30
|
+
* changes where it is, not what it is, so nothing about it needs rebuilding.
|
|
31
|
+
* `shiftGreen` — which used to deep-copy every following sibling to bump its
|
|
32
|
+
* offsets — no longer exists, because there is nothing left to shift.
|
|
33
|
+
*
|
|
34
|
+
* ─── Why `width` is derived and never passed ────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* `width = leadingWidth + (children ? Σ child widths : ownWidth) + trailingWidth`
|
|
37
|
+
*
|
|
38
|
+
* The partition invariant of roadmap point 14 — every character owned exactly
|
|
39
|
+
* once — used to be a runtime check that a careless caller could violate. Now
|
|
40
|
+
* it is arithmetic the constructor performs, so a tree that does not partition
|
|
41
|
+
* its source cannot be built. `Syntax/partition.ts` still exists to verify the
|
|
42
|
+
* one thing this cannot: that the root's width matches the source length.
|
|
43
|
+
*
|
|
44
|
+
* Inspired by Roslyn's Green Tree and SwiftSyntax.
|
|
45
|
+
*/
|
|
46
|
+
export declare class GreenNode {
|
|
47
|
+
/** Syntax kind — maps to TokenKind for leaves, NodeKind for internals */
|
|
48
|
+
readonly kind: string;
|
|
49
|
+
/** Raw text of this node (empty for internal nodes) */
|
|
50
|
+
readonly text: string;
|
|
51
|
+
/** Child nodes */
|
|
52
|
+
readonly children: readonly GreenNode[];
|
|
53
|
+
/** Total width of this node in characters, delimiters included */
|
|
54
|
+
readonly width: number;
|
|
55
|
+
/** Whether this is a leaf (token) or internal node */
|
|
56
|
+
readonly isLeaf: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Characters this node owns BEFORE its first child — its opening delimiter.
|
|
59
|
+
* For `[b]hola[/b]` that is the 3 characters of `[b]`.
|
|
60
|
+
*
|
|
61
|
+
* `declare` is load-bearing here for the same reason as `_hashCache` below:
|
|
62
|
+
* a plain field declaration would emit a `defineProperty` per construction.
|
|
63
|
+
*/
|
|
64
|
+
readonly leadingWidth: number;
|
|
65
|
+
/** Characters this node owns AFTER its last child — its closing delimiter. */
|
|
66
|
+
readonly trailingWidth: number;
|
|
67
|
+
/**
|
|
68
|
+
* Memoized structural hash — computed on first access, never invalidated.
|
|
69
|
+
*
|
|
70
|
+
* `declare` is load-bearing: with `target: ES2022`, TypeScript defaults
|
|
71
|
+
* `useDefineForClassFields` to true, so a plain field declaration emits a
|
|
72
|
+
* per-construction `defineProperty`. Measured on a 19.6 KB document that
|
|
73
|
+
* cost 23% of the parse phase (0.583 ms → 0.718 ms) for a slot most nodes
|
|
74
|
+
* never read. `declare` emits nothing; the constructor assigns it directly,
|
|
75
|
+
* which keeps the object shape monomorphic without the field-init overhead.
|
|
76
|
+
*/
|
|
77
|
+
private _hashCache;
|
|
78
|
+
/**
|
|
79
|
+
* @param ownWidth Width of a childless node's own content: the length of a
|
|
80
|
+
* `text` token, or the 1-2 characters of a newline. Ignored
|
|
81
|
+
* when there are children, whose widths sum to the same
|
|
82
|
+
* thing. An empty element like `[b][/b]` passes 0 and gets
|
|
83
|
+
* its width from its two delimiters.
|
|
84
|
+
*/
|
|
85
|
+
constructor(kind: string, text: string, children?: GreenNode[], leadingWidth?: number, trailingWidth?: number, ownWidth?: number);
|
|
86
|
+
/**
|
|
87
|
+
* Structural hash of this subtree: `(kind, text, widths, children hashes)`.
|
|
88
|
+
*
|
|
89
|
+
* Two subtrees that render identically at different offsets ARE structurally
|
|
90
|
+
* identical — that is the entire premise of structural sharing, and now that
|
|
91
|
+
* green nodes carry no position it is simply true rather than something the
|
|
92
|
+
* hash had to be careful to arrange.
|
|
93
|
+
*
|
|
94
|
+
* The widths ARE folded in: they are structure, not position. A `text` leaf
|
|
95
|
+
* of width 4 and one of width 5 are different nodes.
|
|
96
|
+
*
|
|
97
|
+
* Computed lazily: parsing paths that never intern pay nothing. Safe to
|
|
98
|
+
* memoize because a GreenNode is immutable once constructed.
|
|
99
|
+
*/
|
|
100
|
+
get _hash(): number;
|
|
101
|
+
/** Get child at index */
|
|
102
|
+
childAt(index: number): GreenNode | undefined;
|
|
103
|
+
/** Find first child matching predicate */
|
|
104
|
+
findChild(predicate: (n: GreenNode) => boolean): GreenNode | undefined;
|
|
105
|
+
/** Count of direct children */
|
|
106
|
+
get childCount(): number;
|
|
107
|
+
/** Check if this node has a specific kind */
|
|
108
|
+
isKind(kind: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Offset of this node's first child, relative to this node's own start.
|
|
111
|
+
* Equal to `leadingWidth`; named for the places that read it as a position.
|
|
112
|
+
*/
|
|
113
|
+
get innerOffset(): number;
|
|
114
|
+
/** Width of the span between this node's delimiters. */
|
|
115
|
+
get innerWidth(): number;
|
|
116
|
+
/** Walk all descendants in pre-order */
|
|
117
|
+
walk(visitor: (node: GreenNode, depth: number) => void | 'skip', depth?: number): void;
|
|
118
|
+
/**
|
|
119
|
+
* Walk all descendants in pre-order, with each node's absolute offset.
|
|
120
|
+
*
|
|
121
|
+
* The replacement for the walks that used to read `node.range`. Offsets are
|
|
122
|
+
* accumulated on the way down, which is the only place they exist.
|
|
123
|
+
*/
|
|
124
|
+
walkWithOffset(visitor: (node: GreenNode, start: number, depth: number) => void | 'skip', start?: number, depth?: number): void;
|
|
125
|
+
/** Convert to a debug string */
|
|
126
|
+
toString(depth?: number, start?: number): string;
|
|
127
|
+
/** Get the source text for this node, given where it starts. */
|
|
128
|
+
getSourceText(source: string, start: number): string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build an internal node. Its width comes from its children plus its own
|
|
132
|
+
* delimiters — there is no way to declare a width that disagrees with them.
|
|
133
|
+
*/
|
|
134
|
+
export declare function greenNode(kind: string, text: string, children?: GreenNode[], leadingWidth?: number, trailingWidth?: number, ownWidth?: number): GreenNode;
|
|
135
|
+
/**
|
|
136
|
+
* Absolute offsets of `node`'s children, given where `node` itself starts.
|
|
137
|
+
*
|
|
138
|
+
* Returns `children.length + 1` entries: entry `i` is where child `i` begins
|
|
139
|
+
* and entry `i + 1` is where it ends, so a run of children `[a, b)` spans
|
|
140
|
+
* `offsets[a] .. offsets[b]`.
|
|
141
|
+
*
|
|
142
|
+
* For code that walks the green tree and needs source positions — analyzers
|
|
143
|
+
* reporting diagnostics, transforms recording what they collapsed. Green nodes
|
|
144
|
+
* have no positions of their own, so a walk that needs them accumulates them,
|
|
145
|
+
* and this is the one-liner for doing that at a single level.
|
|
146
|
+
*/
|
|
147
|
+
export declare function childOffsets(node: GreenNode, start: number): number[];
|
|
148
|
+
/**
|
|
149
|
+
* Build a token. `width` defaults to the text's own length, which is right for
|
|
150
|
+
* every leaf whose text IS its source (`text` nodes). Leaves whose `text` holds
|
|
151
|
+
* something else — `spacing` and `empty_line` carry `''` but occupy 1-2
|
|
152
|
+
* characters, element nodes carry their attributes — must pass it.
|
|
153
|
+
*/
|
|
154
|
+
export declare function greenLeaf(kind: string, text: string, width?: number): GreenNode;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — GreenNodePool (Structural Sharing via Interning)
|
|
3
|
+
*
|
|
4
|
+
* Roslyn-grade GreenNode Deduplication Pool.
|
|
5
|
+
*
|
|
6
|
+
* Ensures that structurally identical subtrees share the EXACT SAME
|
|
7
|
+
* GreenNode memory address in RAM. This is structural sharing:
|
|
8
|
+
* when the same BBCode block is pasted 10 times, the parser creates
|
|
9
|
+
* GreenNodes for it ONCE, and all 10 copies reference the same objects.
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* - Flyweight Pattern: GreenNodes are pure structural data without parent pointers.
|
|
13
|
+
* - Hash-based lookup: O(1) amortized via _hash property on GreenNode.
|
|
14
|
+
* - Reference-equality verification: identical children = same reference (because
|
|
15
|
+
* children are interned first, bottom-up construction guarantees this).
|
|
16
|
+
* - Per-document scope: interners are created per BBCodeDocumentModel, cleared on dispose.
|
|
17
|
+
*
|
|
18
|
+
* Key insight: because GreenNodes are immutable and children are interned bottom-up,
|
|
19
|
+
* two nodes with the same (kind, text, widths, children-references) are structurally
|
|
20
|
+
* identical. We use _hash for fast lookup, then verify with reference equality on
|
|
21
|
+
* children.
|
|
22
|
+
*
|
|
23
|
+
* ─── Why this used to be broken (roadmap S2) ────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* Green nodes used to carry an absolute `range`, and the two halves of the pool
|
|
26
|
+
* disagreed about what to do with it:
|
|
27
|
+
*
|
|
28
|
+
* - `internLeaf` keyed on `kind:text` alone, so the second `"Hello"` in a
|
|
29
|
+
* document got back the node built for the FIRST one — carrying the first
|
|
30
|
+
* one's offsets. Enabling the interner made every position downstream lie
|
|
31
|
+
* silently: incremental reparse, `findNodeAtOffset`, diagnostics, the
|
|
32
|
+
* Monaco↔AST mapping.
|
|
33
|
+
* - `internNode` did the opposite and required identical start AND end before
|
|
34
|
+
* deduplicating, which no two distinct occurrences can have. The same block
|
|
35
|
+
* pasted ten times gave 0% dedup and 100% of the scanning cost.
|
|
36
|
+
*
|
|
37
|
+
* Neither was fixable while position lived on the green node, because those are
|
|
38
|
+
* the only two options: ignore it and lie, or respect it and never match. The
|
|
39
|
+
* fix was to remove position from the green tree entirely — see `GreenNode.ts`.
|
|
40
|
+
* Now `kind:text:width` genuinely identifies a leaf, and interning is simply
|
|
41
|
+
* correct.
|
|
42
|
+
*/
|
|
43
|
+
import { GreenNode } from './GreenNode';
|
|
44
|
+
export interface GreenNodePoolStats {
|
|
45
|
+
size: number;
|
|
46
|
+
hits: number;
|
|
47
|
+
misses: number;
|
|
48
|
+
deduplicatedCount: number;
|
|
49
|
+
/** Number of hash collisions that were verified as true duplicates */
|
|
50
|
+
collisionHits: number;
|
|
51
|
+
/** Number of hash collisions that were false positives */
|
|
52
|
+
collisionMisses: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* How much of the tree to deduplicate.
|
|
56
|
+
*
|
|
57
|
+
* Measured on the 19.6 KB reference document, against no interning at all:
|
|
58
|
+
*
|
|
59
|
+
* | mode | parse time | green tree memory |
|
|
60
|
+
* |----------|------------|-------------------|
|
|
61
|
+
* | `leaves` | +16% | −42% |
|
|
62
|
+
* | `full` | +110% | −56% |
|
|
63
|
+
*
|
|
64
|
+
* `leaves` is the default because it buys three quarters of the memory for a
|
|
65
|
+
* seventh of the time. Interning an internal node means hashing its whole
|
|
66
|
+
* subtree — `_hash` is lazy, so a parse that never interns never pays for it —
|
|
67
|
+
* and then walking its children to verify a bucket hit. Leaves cost one string
|
|
68
|
+
* key and one Map lookup, and they are where the duplication is: the reference
|
|
69
|
+
* document has 930 leaves and only 122 distinct ones.
|
|
70
|
+
*/
|
|
71
|
+
export type InterningMode = 'leaves' | 'full';
|
|
72
|
+
export declare class GreenNodePool {
|
|
73
|
+
private static _instance;
|
|
74
|
+
private readonly _mode;
|
|
75
|
+
private _leafCache;
|
|
76
|
+
private _nodeCache;
|
|
77
|
+
private _hits;
|
|
78
|
+
private _misses;
|
|
79
|
+
private _collisionHits;
|
|
80
|
+
private _collisionMisses;
|
|
81
|
+
/** Singleton instance accessor (for backward compatibility) */
|
|
82
|
+
static get instance(): GreenNodePool;
|
|
83
|
+
constructor(mode?: InterningMode);
|
|
84
|
+
/**
|
|
85
|
+
* Create a new isolated interner (for per-document use).
|
|
86
|
+
* The returned pool shares no cache with the singleton.
|
|
87
|
+
*/
|
|
88
|
+
static create(mode?: InterningMode): GreenNodePool;
|
|
89
|
+
/**
|
|
90
|
+
* Intern (deduplicate) a leaf GreenNode.
|
|
91
|
+
* If an identical leaf exists in the pool, returns the cached instance.
|
|
92
|
+
* Leaf key: `kind:text` — deterministic, no collision possible.
|
|
93
|
+
*/
|
|
94
|
+
internLeaf(kind: string, text: string, width?: number): GreenNode;
|
|
95
|
+
/**
|
|
96
|
+
* Intern an internal GreenNode (structural sharing).
|
|
97
|
+
*
|
|
98
|
+
* Strategy:
|
|
99
|
+
* 1. Compute hash from (kind, text, children's _hash values) — O(n) where n = children count
|
|
100
|
+
* 2. Look up hash in bucket — O(1) amortized
|
|
101
|
+
* 3. Verify with reference equality on children — O(n) but cache-friendly
|
|
102
|
+
* 4. If verified, return existing (structural sharing achieved)
|
|
103
|
+
* 5. If not, store and return new node
|
|
104
|
+
*
|
|
105
|
+
* Because children are interned bottom-up, identical children are the same reference.
|
|
106
|
+
* This makes reference equality a perfect structural equality check.
|
|
107
|
+
*/
|
|
108
|
+
internNode(kind: string, text: string, children: GreenNode[], leadingWidth?: number, trailingWidth?: number): GreenNode;
|
|
109
|
+
/**
|
|
110
|
+
* Compute a bucket key for internal nodes.
|
|
111
|
+
* Uses (kind, text, children-count, first-child-hash) for fast bucketing.
|
|
112
|
+
* This gives O(1) bucket lookup in the common case.
|
|
113
|
+
*/
|
|
114
|
+
private computeBucketKey;
|
|
115
|
+
private fnv1a;
|
|
116
|
+
private hashCombine;
|
|
117
|
+
/** Clear the pool cache */
|
|
118
|
+
clear(): void;
|
|
119
|
+
/** Get current pool statistics */
|
|
120
|
+
get stats(): GreenNodePoolStats;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convenience helper for interning green leaves, over the shared singleton.
|
|
124
|
+
*
|
|
125
|
+
* The internal-node twin of this helper used to sit alongside it and was
|
|
126
|
+
* removed: nothing imported it, and it could not have worked if anything had.
|
|
127
|
+
* `GreenNodePool.instance` is built with the default `'leaves'` mode, and
|
|
128
|
+
* `internNode` returns a fresh `GreenNode` unconditionally in that mode — so
|
|
129
|
+
* the one thing the helper existed for, structural sharing of internal nodes,
|
|
130
|
+
* was unreachable through the only pool it could reach. Interning internal
|
|
131
|
+
* nodes is done by passing a `GreenNodePool.create('full')` to the parser,
|
|
132
|
+
* which is what `BBCodeDocumentModel` does per document.
|
|
133
|
+
*/
|
|
134
|
+
export declare function internGreenLeaf(kind: string, text: string, width?: number): GreenNode;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — NodeMatcher
|
|
3
|
+
*
|
|
4
|
+
* Matches nodes between old and new syntax trees after edits.
|
|
5
|
+
* Uses stable IDs, fingerprints, and source ranges to determine
|
|
6
|
+
* which nodes were moved, inserted, deleted, or unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Critical for:
|
|
9
|
+
* - Maintaining selection across re-parses
|
|
10
|
+
* - Preserving scroll position
|
|
11
|
+
* - Smooth animations in visual blocks mode
|
|
12
|
+
* - Undo/redo integrity
|
|
13
|
+
*/
|
|
14
|
+
import { RedNode } from './RedNode';
|
|
15
|
+
import type { NodeId } from '../Types/core';
|
|
16
|
+
export type MatchStatus = 'exact' | 'moved' | 'updated' | 'inserted' | 'deleted' | 'merged' | 'split';
|
|
17
|
+
export interface MatchResult {
|
|
18
|
+
oldNode: RedNode | null;
|
|
19
|
+
newNode: RedNode | null;
|
|
20
|
+
status: MatchStatus;
|
|
21
|
+
confidence: number;
|
|
22
|
+
}
|
|
23
|
+
export interface NodeMatch {
|
|
24
|
+
/** Map from old node ID to new node */
|
|
25
|
+
oldToNew: Map<NodeId, RedNode>;
|
|
26
|
+
/** Map from new node ID to old node */
|
|
27
|
+
newToOld: Map<NodeId, RedNode>;
|
|
28
|
+
/** All matches */
|
|
29
|
+
matches: MatchResult[];
|
|
30
|
+
/** Statistics */
|
|
31
|
+
stats: {
|
|
32
|
+
exact: number;
|
|
33
|
+
moved: number;
|
|
34
|
+
updated: number;
|
|
35
|
+
inserted: number;
|
|
36
|
+
deleted: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare class NodeMatcher {
|
|
40
|
+
/**
|
|
41
|
+
* Match nodes between old and new trees.
|
|
42
|
+
*
|
|
43
|
+
* Strategy (in order):
|
|
44
|
+
* 1. Match by stable ID (if present)
|
|
45
|
+
* 2. Match by fingerprint (content hash)
|
|
46
|
+
* 3. Match by source range overlap
|
|
47
|
+
* 4. Fall back to positional heuristic
|
|
48
|
+
*/
|
|
49
|
+
match(oldRoot: RedNode, newRoot: RedNode): NodeMatch;
|
|
50
|
+
/**
|
|
51
|
+
* Phase 0 — descend in lockstep, pairing every subtree that did not change.
|
|
52
|
+
*
|
|
53
|
+
* At each level the common prefix and common suffix are trimmed by comparing
|
|
54
|
+
* `green._hash`, and only the window between them is descended into. That is
|
|
55
|
+
* what makes this robust to an edit that changes the number of children:
|
|
56
|
+
* requiring equal child counts before recursing sounds reasonable but means a
|
|
57
|
+
* single inserted node near the root disables the fast path for the whole
|
|
58
|
+
* tree — measured, it left `match` at 5.11 ms instead of 0.96 ms.
|
|
59
|
+
*
|
|
60
|
+
* Whatever is left in the middle window is handed to the fingerprint
|
|
61
|
+
* heuristic, which is the part that can cope with reordering.
|
|
62
|
+
*/
|
|
63
|
+
private pairUnchanged;
|
|
64
|
+
/**
|
|
65
|
+
* Compute a content fingerprint for a node's subtree.
|
|
66
|
+
*
|
|
67
|
+
* Two nodes share a fingerprint iff their subtrees have the same pre-order
|
|
68
|
+
* sequence of (kind, text) pairs — the same equivalence the previous
|
|
69
|
+
* string-concatenating implementation tested, but as a 64-bit structural
|
|
70
|
+
* hash instead of an O(subtree) string built on every comparison.
|
|
71
|
+
*/
|
|
72
|
+
fingerprint(node: RedNode): string;
|
|
73
|
+
/** Memoized, bottom-up fingerprint. O(n) across a whole tree. */
|
|
74
|
+
private fingerprintInto;
|
|
75
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — RedNode (Mutable Syntax Tree)
|
|
3
|
+
*
|
|
4
|
+
* The Red Tree wraps GreenNode and adds mutable state:
|
|
5
|
+
* - Parent references
|
|
6
|
+
* - Stable Node IDs
|
|
7
|
+
* - Version tracking
|
|
8
|
+
* - Diagnostic storage
|
|
9
|
+
*
|
|
10
|
+
* Multiple Red Trees can reference the same Green Tree
|
|
11
|
+
* (different views, different versions of edits).
|
|
12
|
+
*
|
|
13
|
+
* Inspired by Roslyn's Red Tree.
|
|
14
|
+
*/
|
|
15
|
+
import { GreenNode } from './GreenNode';
|
|
16
|
+
import type { NodeId, DocumentNode, NodeKind, NodeMetadata } from '../Types/core';
|
|
17
|
+
import type { Diagnostic } from '../Types/diagnostics';
|
|
18
|
+
import type { Range } from '../Types/tokens';
|
|
19
|
+
export declare class RedNode {
|
|
20
|
+
/** The underlying immutable green node */
|
|
21
|
+
readonly green: GreenNode;
|
|
22
|
+
/** Stable identity */
|
|
23
|
+
readonly id: NodeId;
|
|
24
|
+
/** Parent reference (null for root) */
|
|
25
|
+
parent: RedNode | null;
|
|
26
|
+
/** Children as red nodes */
|
|
27
|
+
children: RedNode[];
|
|
28
|
+
/** Version counter for change tracking */
|
|
29
|
+
version: number;
|
|
30
|
+
/** Diagnostics for this node */
|
|
31
|
+
diagnostics: Diagnostic[];
|
|
32
|
+
/** The semantic kind */
|
|
33
|
+
kind: NodeKind;
|
|
34
|
+
/** Additional metadata */
|
|
35
|
+
metadata: NodeMetadata;
|
|
36
|
+
/**
|
|
37
|
+
* Cached position of this node inside `parent.children`.
|
|
38
|
+
*
|
|
39
|
+
* `declare` is load-bearing — see the note on `GreenNode._hashCache`: with
|
|
40
|
+
* `target: ES2022` a plain field declaration emits a `defineProperty` on every
|
|
41
|
+
* construction, and this class is instantiated once per node per parse.
|
|
42
|
+
*
|
|
43
|
+
* The cache is *self-validating*: `index` only trusts it when
|
|
44
|
+
* `parent.children[_idxCache] === this`. That single reference compare is what
|
|
45
|
+
* makes it safe against code that mutates the `children` array directly
|
|
46
|
+
* (`treeTransformers` does `cloned.children = []`) instead of going through the
|
|
47
|
+
* mutation methods — a stale entry is detected and recomputed rather than
|
|
48
|
+
* silently returning a wrong index. `-1` never validates, so it is a safe
|
|
49
|
+
* initial value.
|
|
50
|
+
*/
|
|
51
|
+
private _idxCache;
|
|
52
|
+
/**
|
|
53
|
+
* Absolute start offset in the source.
|
|
54
|
+
*
|
|
55
|
+
* This is the red tree's job now: a green node knows its width, not its
|
|
56
|
+
* place, so that identical structures can be the same object (see the header
|
|
57
|
+
* of `GreenNode.ts`). The offset is accumulated on the way down during
|
|
58
|
+
* construction — one addition per node — and stored, so `range` stays the
|
|
59
|
+
* O(1) property every consumer already assumes it is.
|
|
60
|
+
*
|
|
61
|
+
* Held as the `Range` object itself rather than a number plus a getter that
|
|
62
|
+
* builds one: `range` is read from 148 call sites, and allocating there would
|
|
63
|
+
* trade a parse-time win for a read-time cost on every consumer. This is the
|
|
64
|
+
* same shape green nodes used to hold, so nothing downstream changes.
|
|
65
|
+
*
|
|
66
|
+
* `declare` for the usual reason: no `defineProperty` per construction.
|
|
67
|
+
*/
|
|
68
|
+
private _range;
|
|
69
|
+
/**
|
|
70
|
+
* Pending offset shift for this subtree, applied on first read.
|
|
71
|
+
*
|
|
72
|
+
* `setStart` defers the walk: it records how far the subtree moved instead of
|
|
73
|
+
* adding the delta to every node's `_range` on the spot. The shift is a single
|
|
74
|
+
* integer here, on the adopted subtree ROOT — the node `setStart` was called
|
|
75
|
+
* on. Any offset read (`range`, `innerStart`, `innerEnd`, `findNodeAtOffset`)
|
|
76
|
+
* materializes the nearest pending ancestor's subtree via {@link materialize}.
|
|
77
|
+
*
|
|
78
|
+
* Why this is sound: a mid-document edit displaces every adopted block after
|
|
79
|
+
* it by the same delta, so adoption alone (thousands of `setStart` calls) was
|
|
80
|
+
* walking thousands of nodes per keystroke — measured as the dominant phase
|
|
81
|
+
* of `buildRed` (3.5-5.8 ms on the 547 KB fixture). The BlockPatcher locates
|
|
82
|
+
* the edited window by reference identity and only ever reads the few changed
|
|
83
|
+
* blocks' ranges, so the displaced-but-unchanged subtrees stay pending
|
|
84
|
+
* indefinitely — the walk never happens.
|
|
85
|
+
*
|
|
86
|
+
* Contract for future readers: app-layer interaction code (hover, links,
|
|
87
|
+
* selection ranges) DOES read `range` on user interaction. The first such
|
|
88
|
+
* read after a mid-document edit fires the deferred walk for the displaced
|
|
89
|
+
* tail — a one-time cost off the keystroke path, which is exactly the trade
|
|
90
|
+
* the laziness makes. Do not "optimize" by reading ranges back into the
|
|
91
|
+
* per-keystroke pipeline; the patcher stays churn-based by design.
|
|
92
|
+
*
|
|
93
|
+
* Composition across reparses: a node re-adopted while its ancestor is still
|
|
94
|
+
* pending must end up at the SUM of both deltas. `setStart` recomputes the
|
|
95
|
+
* delta from the untouched base `_range`, which is exactly the pending
|
|
96
|
+
* ancestor's base too, so composing during `materialize` (add the ancestor's
|
|
97
|
+
* delta into a child's pending delta) stays consistent.
|
|
98
|
+
*/
|
|
99
|
+
private _lazyShift;
|
|
100
|
+
constructor(green: GreenNode, options?: {
|
|
101
|
+
id?: NodeId;
|
|
102
|
+
parent?: RedNode | null;
|
|
103
|
+
kind?: NodeKind;
|
|
104
|
+
diagnostics?: Diagnostic[];
|
|
105
|
+
metadata?: NodeMetadata;
|
|
106
|
+
/** Absolute start offset. Defaults to the parent's inner offset. */
|
|
107
|
+
start?: number;
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Move this node (and its subtree) to a new absolute offset.
|
|
111
|
+
*
|
|
112
|
+
* Only for the tree-building paths, which create a node before they know
|
|
113
|
+
* where its parent will put it. Callers that mutate a live tree should
|
|
114
|
+
* rebuild instead — a red node's offset must always agree with its place.
|
|
115
|
+
*
|
|
116
|
+
* The move is deferred, not walked: the delta is recorded as a pending
|
|
117
|
+
* shift and applied by any offset read the first time the subtree is
|
|
118
|
+
* actually touched. Adoption calls this once per displaced block (thousands
|
|
119
|
+
* on a mid-document edit), and almost none of those subtrees are read on the
|
|
120
|
+
* same keystroke's hot path — the BlockPatcher locates the edit window by
|
|
121
|
+
* reference identity and only reads the few changed blocks.
|
|
122
|
+
*
|
|
123
|
+
* Re-adoption composes: a node whose subtree was shifted in a previous
|
|
124
|
+
* reparse and never read still carries a pending `_lazyShift`. The new
|
|
125
|
+
* target is absolute, so the delta is measured from the CURRENT effective
|
|
126
|
+
* start (`_range.start + _lazyShift`) and accumulated, not overwritten —
|
|
127
|
+
* otherwise the earlier shift would be applied twice.
|
|
128
|
+
*/
|
|
129
|
+
setStart(start: number): void;
|
|
130
|
+
/**
|
|
131
|
+
* Apply `delta` to `node`'s subtree, composing with any nested pending shift.
|
|
132
|
+
*
|
|
133
|
+
* A node with its own pending shift has a base `_range` the parent's delta is
|
|
134
|
+
* relative to as well (both were computed from the same pre-shift tree), so
|
|
135
|
+
* the parent's delta can be folded into the child's pending delta instead of
|
|
136
|
+
* into its `_range` — the child's later materialization applies the sum. This
|
|
137
|
+
* is what makes nested shifts across reparses compose without double counting.
|
|
138
|
+
*/
|
|
139
|
+
private static applyShift;
|
|
140
|
+
/**
|
|
141
|
+
* Push `delta` into every red node that lives under `node` — its title nodes
|
|
142
|
+
* as well as its children.
|
|
143
|
+
*
|
|
144
|
+
* The title subtree of a rich `[box=[b]heading[/b]]` is stored in
|
|
145
|
+
* `metadata.titleNodes`, never appended to `children`, so a loop over
|
|
146
|
+
* `children` alone leaves it at pre-edit offsets. That is not cosmetic:
|
|
147
|
+
* `findNodeAtOffset` consults `metadata.titleNodes` BEFORE `children`, so a
|
|
148
|
+
* stale title range mis-resolves caret, hover and selection inside the
|
|
149
|
+
* heading. Both shift paths — the recursive one and `materialize`'s ancestor
|
|
150
|
+
* loop — route through here so they cannot drift apart again.
|
|
151
|
+
*/
|
|
152
|
+
private static shiftDescendants;
|
|
153
|
+
/**
|
|
154
|
+
* Materialize every pending shift on the path from here to the root.
|
|
155
|
+
*
|
|
156
|
+
* Walks ancestors top-down (root-most first), applying each pending delta to
|
|
157
|
+
* its subtree. Top-down order matters: an ancestor's delta must land in a
|
|
158
|
+
* child's pending delta BEFORE the child's own materialization runs, or the
|
|
159
|
+
* two shifts would be applied to different bases. After the ancestors are
|
|
160
|
+
* settled, this node itself is materialized if it still carries a shift.
|
|
161
|
+
*
|
|
162
|
+
* The common case — nothing pending anywhere on the path — is one upward
|
|
163
|
+
* pointer walk that allocates nothing: `_range` is read from 148 call sites,
|
|
164
|
+
* and a per-read allocation there would show up in every phase. The chain
|
|
165
|
+
* array is only built after a pending shift is actually found.
|
|
166
|
+
*/
|
|
167
|
+
private materialize;
|
|
168
|
+
private static _isMutating;
|
|
169
|
+
/**
|
|
170
|
+
* Run `fn` inside a mutation boundary, so the structural mutators below are
|
|
171
|
+
* allowed to run. Nests safely; the previous state is restored on exit.
|
|
172
|
+
*
|
|
173
|
+
* Not needed for building a fresh subtree — see {@link initChildren}.
|
|
174
|
+
*/
|
|
175
|
+
static allowMutation<T>(fn: () => T): T;
|
|
176
|
+
private assertMutating;
|
|
177
|
+
get text(): string;
|
|
178
|
+
/** Absolute span in the source. */
|
|
179
|
+
get range(): Range;
|
|
180
|
+
/** Absolute offset of this node's first child, past its opening delimiter. */
|
|
181
|
+
get innerStart(): number;
|
|
182
|
+
/** Absolute offset where this node's closing delimiter begins. */
|
|
183
|
+
get innerEnd(): number;
|
|
184
|
+
get isLeaf(): boolean;
|
|
185
|
+
get childCount(): number;
|
|
186
|
+
childAt(index: number): RedNode | undefined;
|
|
187
|
+
/** Get the root of the tree */
|
|
188
|
+
get root(): RedNode;
|
|
189
|
+
/** Get the depth from root */
|
|
190
|
+
get depth(): number;
|
|
191
|
+
/** Get previous sibling */
|
|
192
|
+
get previousSibling(): RedNode | null;
|
|
193
|
+
/** Get next sibling */
|
|
194
|
+
get nextSibling(): RedNode | null;
|
|
195
|
+
/**
|
|
196
|
+
* Position of this node within its parent, or -1 for a root.
|
|
197
|
+
*
|
|
198
|
+
* O(1) in the steady state. The cached index is seeded by the mutation methods
|
|
199
|
+
* (so the construction path never searches at all) and verified by a single
|
|
200
|
+
* reference compare on read, falling back to `indexOf` only when it is stale.
|
|
201
|
+
*
|
|
202
|
+
* This getter is hot: `HTMLRenderer.isPrevBlockBoundary` walks siblings
|
|
203
|
+
* backwards, so an O(n) implementation here made rendering quadratic —
|
|
204
|
+
* measured 14.11 ms for 4000 root siblings, growing 2.6x per doubling.
|
|
205
|
+
*/
|
|
206
|
+
get index(): number;
|
|
207
|
+
/** Walk all descendants in pre-order */
|
|
208
|
+
walk(visitor: (node: RedNode, depth: number) => void | 'skip', depth?: number): void;
|
|
209
|
+
/** Find a descendant by ID */
|
|
210
|
+
findById(id: NodeId): RedNode | null;
|
|
211
|
+
/**
|
|
212
|
+
* Find the deepest RedNode containing the given text offset.
|
|
213
|
+
*
|
|
214
|
+
* The range is half-open — a node owns `[start, end)` — y eso no es un
|
|
215
|
+
* detalle: con el final inclusivo, un offset que cae justo en una frontera
|
|
216
|
+
* pertenecía a DOS nodos, al que termina ahí y al que empieza. Como los
|
|
217
|
+
* hijos se recorren en orden, ganaba el que termina, así que preguntar por
|
|
218
|
+
* el principio de un nodo devolvía **el nodo anterior**.
|
|
219
|
+
*
|
|
220
|
+
* Se veía en el preview: en un degradado, cada carácter es su propio nodo
|
|
221
|
+
* de color, y hacer clic en la `m` de «Welcome» resaltaba la `o`. Monaco
|
|
222
|
+
* seleccionaba bien —usa el rango del nodo, no esta búsqueda—, así que el
|
|
223
|
+
* desfase era solo del resaltado, que es justo lo que hacía difícil verlo.
|
|
224
|
+
*
|
|
225
|
+
* La única excepción es el final del documento: un cursor aparcado tras el
|
|
226
|
+
* último carácter no tiene carácter que lo contenga, y la raíz lo reclama
|
|
227
|
+
* para que quien pregunte reciba algo con sentido.
|
|
228
|
+
*/
|
|
229
|
+
findNodeAtOffset(offset: number): RedNode | null;
|
|
230
|
+
/**
|
|
231
|
+
* Adopt a fully-built array of children in one shot.
|
|
232
|
+
*
|
|
233
|
+
* Construction-only, and deliberately outside the mutation lock. The tree
|
|
234
|
+
* builders used to call `allowMutation(() => { for (…) appendChild(…) })` once
|
|
235
|
+
* **per node**, which on a 1736-node document meant 1736 closures, 1736
|
|
236
|
+
* `try/finally` frames and 1736 static-flag saves — pure ceremony, since a
|
|
237
|
+
* node that no caller has seen yet cannot be observed mid-mutation. This
|
|
238
|
+
* populates `children`, `parent` and the index cache in a single pass instead.
|
|
239
|
+
*
|
|
240
|
+
* `version` intentionally stays at 1: a freshly built node has not been
|
|
241
|
+
* *edited*, and bumping once per child would make the initial version an
|
|
242
|
+
* accidental child count.
|
|
243
|
+
*
|
|
244
|
+
* Only safe while `this` is still unreachable from the rest of the tree. Use
|
|
245
|
+
* `appendChild` and friends for anything after that.
|
|
246
|
+
*/
|
|
247
|
+
initChildren(children: RedNode[]): void;
|
|
248
|
+
/**
|
|
249
|
+
* Append a child node.
|
|
250
|
+
* This creates a new RedNode wrapping the green node.
|
|
251
|
+
*/
|
|
252
|
+
appendChild(child: RedNode): void;
|
|
253
|
+
/**
|
|
254
|
+
* Refresh the cached index of every child from `from` onwards.
|
|
255
|
+
*
|
|
256
|
+
* Called after a splice, which shifts the tail. This is O(n) but so is the
|
|
257
|
+
* splice it follows, so it costs nothing asymptotically and keeps reads O(1).
|
|
258
|
+
*/
|
|
259
|
+
private reindexFrom;
|
|
260
|
+
/**
|
|
261
|
+
* Insert a child at a specific index.
|
|
262
|
+
*/
|
|
263
|
+
insertChildAt(index: number, child: RedNode): void;
|
|
264
|
+
/**
|
|
265
|
+
* Remove a child by ID.
|
|
266
|
+
*/
|
|
267
|
+
removeChild(id: NodeId): RedNode | null;
|
|
268
|
+
/**
|
|
269
|
+
* Replace a child with a new one by ID.
|
|
270
|
+
*/
|
|
271
|
+
replaceChild(id: NodeId, newChild: RedNode): boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Bump version (when attributes/metadata change without structural change).
|
|
274
|
+
*/
|
|
275
|
+
bumpVersion(): void;
|
|
276
|
+
/** Convert to the public DocumentNode interface */
|
|
277
|
+
toDocumentNode(): DocumentNode;
|
|
278
|
+
/** Debug string */
|
|
279
|
+
toString(depth?: number): string;
|
|
280
|
+
}
|