@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,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — `unwrap-invisible-color`
|
|
3
|
+
*
|
|
4
|
+
* Removes a `[color]` whose entire content is whitespace.
|
|
5
|
+
*
|
|
6
|
+
* [color=#6A4C93]BARCA[/color][color=#6F518D] [/color][color=#755687]·[/color]
|
|
7
|
+
* ╰── 24 bytes to colour a space ──╯
|
|
8
|
+
*
|
|
9
|
+
* A space has no glyph, and `[color]` sets `color` and nothing else — no
|
|
10
|
+
* background, no decoration — so painting one is a guaranteed no-op. Deleting
|
|
11
|
+
* the two delimiters turns 24 bytes into 1 and cannot change a pixel.
|
|
12
|
+
*
|
|
13
|
+
* This is the dominant waste in expanded gradients, and it is easy to see why:
|
|
14
|
+
* the tool that expands `[gradient]` into per-character `[color]` spans walks
|
|
15
|
+
* characters without asking which of them draw ink, so every space in the ramp
|
|
16
|
+
* gets its own fully-spelled tag.
|
|
17
|
+
*
|
|
18
|
+
* ## Only colour
|
|
19
|
+
*
|
|
20
|
+
* The same shape is *not* removable for other tags, and the difference is not
|
|
21
|
+
* subtle:
|
|
22
|
+
*
|
|
23
|
+
* - `[u]` / `[s]` draw a line through the gap — visible on a space.
|
|
24
|
+
* - `[mark]` paints a background — visible on a space.
|
|
25
|
+
* - `[size]` and `[font]` change the advance width of the space itself.
|
|
26
|
+
*
|
|
27
|
+
* Only `color` is invisible on whitespace, so only `color` is unwrapped.
|
|
28
|
+
*/
|
|
29
|
+
import type { GreenNode } from '../../Syntax/GreenNode';
|
|
30
|
+
import type { PlannedEdit } from '../EditPlan';
|
|
31
|
+
import { type OptimizationRule, type RuleContext } from './Rule';
|
|
32
|
+
export declare const UNWRAP_INVISIBLE_COLOR_PRIORITY = 85;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a node contributes nothing but whitespace.
|
|
35
|
+
*
|
|
36
|
+
* Recursive through nested colours, so `[color=A][color=B] [/color][/color]`
|
|
37
|
+
* counts. `spacing` is whitespace too — a newline inside a colour tag is as
|
|
38
|
+
* invisible as a space.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isInvisibleWhitespace(node: GreenNode): boolean;
|
|
41
|
+
export declare class UnwrapInvisibleColorRule implements OptimizationRule {
|
|
42
|
+
readonly id = "unwrap-invisible-color";
|
|
43
|
+
readonly priority = 85;
|
|
44
|
+
readonly label = "Unwrap colour tags around whitespace";
|
|
45
|
+
run(context: RuleContext): PlannedEdit[];
|
|
46
|
+
private scan;
|
|
47
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — applying a resolved edit set to source text
|
|
3
|
+
*
|
|
4
|
+
* The string-rewriting half of the two appliers. Monaco is the other: it takes
|
|
5
|
+
* the very same `SurgicalEdit[]` and resolves the offsets against its model
|
|
6
|
+
* itself, which is why neither side ever re-serializes the document and the
|
|
7
|
+
* two cannot drift.
|
|
8
|
+
*/
|
|
9
|
+
import type { SurgicalEdit } from '../Reconciler/SurgicalReconciler';
|
|
10
|
+
/**
|
|
11
|
+
* Apply edits carrying ORIGINAL offsets, in one pass.
|
|
12
|
+
*
|
|
13
|
+
* The obvious implementation splices the string once per edit:
|
|
14
|
+
*
|
|
15
|
+
* for (const edit of backToFront) out = out.slice(0, s) + text + out.slice(e)
|
|
16
|
+
*
|
|
17
|
+
* which is correct and quadratic — every splice copies the whole document, so
|
|
18
|
+
* a 250.000-character page with ~3.800 edits copies roughly a billion
|
|
19
|
+
* characters and takes about half a second. Measured, not guessed. Collecting
|
|
20
|
+
* the untouched spans and joining them once is linear and turns that into a
|
|
21
|
+
* few milliseconds, which is the difference between a minify command that
|
|
22
|
+
* feels instant and one that visibly stalls the editor.
|
|
23
|
+
*
|
|
24
|
+
* Going forwards rather than backwards is what makes the single pass possible,
|
|
25
|
+
* and it costs nothing: each edit is measured against the original string
|
|
26
|
+
* either way.
|
|
27
|
+
*
|
|
28
|
+
* ## Contract
|
|
29
|
+
*
|
|
30
|
+
* The edits must be pairwise conflict-free — run them through
|
|
31
|
+
* `resolveEditConflicts` first. An edit that reaches back into a span already
|
|
32
|
+
* written is a contract violation with no meaningful answer, so it is skipped
|
|
33
|
+
* rather than allowed to interleave into nonsense; the result stays a valid
|
|
34
|
+
* document that is merely missing one change.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyEditsToSource(source: string, edits: readonly SurgicalEdit[]): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { classifyOverlap, editsConflict, compareEditPriority, resolveEditConflicts, } from './EditPlan';
|
|
2
|
+
export type { PlannedEdit, RejectedEdit, ResolvedEditPlan, EditRejectionReason, OverlapRelation, } from './EditPlan';
|
|
3
|
+
export { applyEditsToSource } from './applyEdits';
|
|
4
|
+
export { optimizeBBCode, optimizeTree, defaultRules, allRules, } from './Optimizer';
|
|
5
|
+
export type { OptimizationResult, OptimizeOptions, RuleStat } from './Optimizer';
|
|
6
|
+
export * from './Rules';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — EventBus
|
|
3
|
+
*
|
|
4
|
+
* Publish/subscribe event system for document changes.
|
|
5
|
+
* Each DocumentModel has its own EventBus instance.
|
|
6
|
+
*
|
|
7
|
+
* Events include:
|
|
8
|
+
* - document_changed (rebuild, edit, undo/redo)
|
|
9
|
+
* - node_changed (insert, delete, update, move)
|
|
10
|
+
* - diagnostic_updated
|
|
11
|
+
* - transaction_applied
|
|
12
|
+
* - cursor_moved
|
|
13
|
+
* - selection_changed
|
|
14
|
+
*/
|
|
15
|
+
import type { DocumentChangeKind, NodeId } from '../Types/core';
|
|
16
|
+
import type { Operation } from '../Types/operations';
|
|
17
|
+
import type { NodeMatch } from '../Syntax/NodeMatcher';
|
|
18
|
+
import type { TextChange } from '../Incremental/ChangeTracker';
|
|
19
|
+
export type DocumentEventType = 'document_changed' | 'node_inserted' | 'node_deleted' | 'node_updated' | 'diagnostics_updated' | 'transaction_applied' | 'undo_performed' | 'redo_performed' | 'cursor_moved' | 'selection_changed';
|
|
20
|
+
export interface DocumentEvent {
|
|
21
|
+
type: DocumentEventType;
|
|
22
|
+
kind?: DocumentChangeKind;
|
|
23
|
+
version?: number;
|
|
24
|
+
source?: string;
|
|
25
|
+
nodeId?: NodeId;
|
|
26
|
+
nodeMatch?: NodeMatch | null;
|
|
27
|
+
change?: TextChange;
|
|
28
|
+
operations?: Operation[];
|
|
29
|
+
/**
|
|
30
|
+
* Where the change came from: `'local'` for user editing, anything else for
|
|
31
|
+
* programmatic/synced sources. Lets a collaboration layer ignore the echo
|
|
32
|
+
* of changes it applied itself. Absent on events with no single cause.
|
|
33
|
+
*/
|
|
34
|
+
origin?: string;
|
|
35
|
+
/**
|
|
36
|
+
* On `diagnostics_updated`: how the analysis that produced them was
|
|
37
|
+
* obtained — `'full'` (every node revalidated) or `'window'` (only the
|
|
38
|
+
* nodes the edit could have changed; every other node kept its verdict).
|
|
39
|
+
*
|
|
40
|
+
* The `diagnostics` on the event are the WHOLE document's either way, so a
|
|
41
|
+
* subscriber renders them the same in both cases. This is here for the
|
|
42
|
+
* cases where the difference is the point: a performance overlay, a test
|
|
43
|
+
* that must notice the incremental path going away, a log line explaining a
|
|
44
|
+
* slow frame. See `AnalyzeScope`.
|
|
45
|
+
*/
|
|
46
|
+
analysisScope?: 'full' | 'window';
|
|
47
|
+
/**
|
|
48
|
+
* On `diagnostics_updated` with `analysisScope === 'window'`: the span of
|
|
49
|
+
* the current source that was re-validated, in current coordinates.
|
|
50
|
+
* `null`/absent when the whole document was.
|
|
51
|
+
*/
|
|
52
|
+
analysisWindow?: {
|
|
53
|
+
start: number;
|
|
54
|
+
end: number;
|
|
55
|
+
} | null;
|
|
56
|
+
timestamp: number;
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
export type DocumentEventHandler = (event: DocumentEvent) => void;
|
|
60
|
+
export declare class DocumentEventBus {
|
|
61
|
+
private listeners;
|
|
62
|
+
private history;
|
|
63
|
+
private maxHistory;
|
|
64
|
+
/**
|
|
65
|
+
* Record emitted events so `getHistory()` returns them. Off by default:
|
|
66
|
+
* nothing reads the history today, and a retained event pins its `source`
|
|
67
|
+
* string — and, through the lazy `nodeMatch` accessor, the entire previous
|
|
68
|
+
* red/green tree — so an always-on history held megabytes per document
|
|
69
|
+
* for nobody. Turn it on for debugging or replay tooling.
|
|
70
|
+
*/
|
|
71
|
+
recordHistory: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to an event type.
|
|
74
|
+
*/
|
|
75
|
+
on(type: DocumentEventType, handler: DocumentEventHandler): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Whether any handler is subscribed to `type` (or to anything, if omitted).
|
|
78
|
+
*
|
|
79
|
+
* Emitters use this to skip building event payloads nobody will see — the
|
|
80
|
+
* common case, since a headless model has no subscribers at all.
|
|
81
|
+
*/
|
|
82
|
+
hasListeners(type?: DocumentEventType): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Subscribe to all events.
|
|
85
|
+
*/
|
|
86
|
+
onAny(handler: DocumentEventHandler): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Emit an event.
|
|
89
|
+
*/
|
|
90
|
+
emit(event: DocumentEvent): void;
|
|
91
|
+
/**
|
|
92
|
+
* Remove a specific handler.
|
|
93
|
+
*/
|
|
94
|
+
off(type: DocumentEventType, handler: DocumentEventHandler): void;
|
|
95
|
+
/**
|
|
96
|
+
* Get event history.
|
|
97
|
+
*/
|
|
98
|
+
getHistory(): DocumentEvent[];
|
|
99
|
+
/**
|
|
100
|
+
* Clear all listeners and history.
|
|
101
|
+
*/
|
|
102
|
+
clear(): void;
|
|
103
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats the document tree into "pretty" BBCode.
|
|
5
|
+
* Normalizes indentation, spacing, and whitespace.
|
|
6
|
+
*
|
|
7
|
+
* Uses the TagRegistry and Visitor pattern.
|
|
8
|
+
* Plugins can register custom formatters for custom tags.
|
|
9
|
+
*/
|
|
10
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
11
|
+
export interface FormatOptions {
|
|
12
|
+
/** Whether to add newlines between block elements */
|
|
13
|
+
blockNewlines?: boolean;
|
|
14
|
+
/** Whether to normalize whitespace inside inline elements */
|
|
15
|
+
normalizeInlineWhitespace?: boolean;
|
|
16
|
+
/** Maximum line width (0 = no limit) */
|
|
17
|
+
maxLineWidth?: number;
|
|
18
|
+
/** Whether to collapse consecutive empty lines */
|
|
19
|
+
collapseEmptyLines?: boolean;
|
|
20
|
+
/** Indentation string */
|
|
21
|
+
indent?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class Formatter {
|
|
24
|
+
/**
|
|
25
|
+
* Format a RedNode tree (extracts text while preserving exact layout/spacing).
|
|
26
|
+
*/
|
|
27
|
+
format(root: RedNode): string;
|
|
28
|
+
/**
|
|
29
|
+
* Format a single node at a given depth.
|
|
30
|
+
*/
|
|
31
|
+
private formatNode;
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DocumentModel, type DocumentModelOptions } from '../Model/DocumentModel';
|
|
2
|
+
import { GreenNode } from '../Syntax/GreenNode';
|
|
3
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
4
|
+
export interface HTMLDocumentModelOptions extends DocumentModelOptions {
|
|
5
|
+
source?: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class HTMLDocumentModel extends DocumentModel {
|
|
9
|
+
constructor(options?: HTMLDocumentModelOptions);
|
|
10
|
+
static fromHTML(source: string): HTMLDocumentModel;
|
|
11
|
+
protected parseToGreen(source: string): GreenNode;
|
|
12
|
+
protected buildRedFromGreen(green: GreenNode): RedNode;
|
|
13
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — BracketDepthIndex
|
|
3
|
+
*
|
|
4
|
+
* Answers the incremental parser's one question about the text OUTSIDE its
|
|
5
|
+
* window — "does every `[` before this offset find its `]` before it too?" —
|
|
6
|
+
* without reading the text before the offset.
|
|
7
|
+
*
|
|
8
|
+
* ─── Why the scan had to go ─────────────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* `bracketsCloseBefore` walked the whole prefix on every keystroke: with the
|
|
11
|
+
* caret at the end of the 547 KB fixture that is 547.000 `charCodeAt` calls
|
|
12
|
+
* per key, measured at 2.2 ms — more than the parse, the splice and the red
|
|
13
|
+
* build of that same keystroke put together. Caching was declined once on the
|
|
14
|
+
* grounds that the reasoning is subtle: an edit can expose a `]` that a
|
|
15
|
+
* deleted `[` used to claim, so any cache keyed on "the prefix has not
|
|
16
|
+
* changed" is wrong the moment it is asked about a prefix that has.
|
|
17
|
+
*
|
|
18
|
+
* ─── Why this is not that cache ─────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* The scan computes a CLAMPED running depth — `d ← max(0, d + a)` where `a`
|
|
21
|
+
* is +1 for `[`, −1 for `]`, 0 otherwise — and a clamped running sum has an
|
|
22
|
+
* exact, composable summary. Over any stretch of text with prefix sums
|
|
23
|
+
* P₁…Pₙ, the depth after the stretch is a function of the depth before it:
|
|
24
|
+
*
|
|
25
|
+
* d' = Pₙ + max(d, −min(0, P₁, …, Pₙ))
|
|
26
|
+
*
|
|
27
|
+
* so a stretch is fully described by two integers, its net sum and its lowest
|
|
28
|
+
* dip. The index keeps the source as a sequence of PIECES (a few KB each),
|
|
29
|
+
* each carrying that pair. An edit re-summarises only the piece(s) it touched,
|
|
30
|
+
* from the NEW text, and every other piece keeps a summary that is still
|
|
31
|
+
* exactly true of its (unchanged, merely displaced) characters. A query walks
|
|
32
|
+
* the piece summaries up to the offset and scans only the tail of the piece
|
|
33
|
+
* the offset falls in. Nothing here is a guess about what the edit exposed:
|
|
34
|
+
* the touched piece is re-read verbatim, and composition is arithmetic.
|
|
35
|
+
*
|
|
36
|
+
* Cost per keystroke: one summary of ≤ 8 KB plus one walk over ~140 piece
|
|
37
|
+
* summaries at 547 KB — a few microseconds, and independent of where the
|
|
38
|
+
* caret is. Verified against the plain scan by `BracketIndex.test.ts`: 2.000
|
|
39
|
+
* random edits over documents full of stray brackets, every offset agreeing.
|
|
40
|
+
*
|
|
41
|
+
* The index does not know which document it describes; the parser keys it on
|
|
42
|
+
* the green root it was last synchronised with and rebuilds it (one scan) when
|
|
43
|
+
* the root it is handed is not that one. See `IncrementalParser.reparse`.
|
|
44
|
+
*/
|
|
45
|
+
export declare class BracketDepthIndex {
|
|
46
|
+
/** Length of each piece, in characters. Pieces partition `[0, length)`. */
|
|
47
|
+
private lengths;
|
|
48
|
+
/** Net bracket sum of each piece: `[` counts +1, `]` counts −1. */
|
|
49
|
+
private sums;
|
|
50
|
+
/** Lowest prefix sum inside each piece, capped at 0 (see the header). */
|
|
51
|
+
private dips;
|
|
52
|
+
private _length;
|
|
53
|
+
/**
|
|
54
|
+
* Characters read by the last `depthAt` — the tail of one piece, never the
|
|
55
|
+
* prefix. Exposed so a regression back to an O(prefix) scan is something a
|
|
56
|
+
* test can assert on, rather than something a profile has to notice.
|
|
57
|
+
*/
|
|
58
|
+
lastScanned: number;
|
|
59
|
+
/** The length of the text this index currently describes. */
|
|
60
|
+
get length(): number;
|
|
61
|
+
/** Number of pieces — for tests; it should stay ~`length / TARGET_PIECE`. */
|
|
62
|
+
get pieceCount(): number;
|
|
63
|
+
/** Describe `source` from scratch. O(n) — one scan, the same the query used to be. */
|
|
64
|
+
rebuild(source: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Bring the index from the text before an edit to `newSource`, the text
|
|
67
|
+
* after it. `start`/`endOld` bound the replaced region in OLD coordinates;
|
|
68
|
+
* `insertedLength` is the length of the text that replaced it.
|
|
69
|
+
*
|
|
70
|
+
* Only the pieces overlapping the replaced region are re-read. They are
|
|
71
|
+
* re-summarised as one piece over the same span in NEW coordinates, and
|
|
72
|
+
* that piece is split if it has grown past `MAX_PIECE` — so a long paste
|
|
73
|
+
* ends up as several normal pieces, not one giant one that every later
|
|
74
|
+
* query near it would have to scan.
|
|
75
|
+
*/
|
|
76
|
+
applyChange(newSource: string, start: number, endOld: number, insertedLength: number): void;
|
|
77
|
+
/**
|
|
78
|
+
* The clamped bracket depth just before `end` — what the plain scan of
|
|
79
|
+
* `[0, end)` would return. `end === 0` is depth 0; `end > length` is
|
|
80
|
+
* clamped to the text.
|
|
81
|
+
*/
|
|
82
|
+
depthAt(source: string, end: number): number;
|
|
83
|
+
/** Summarise `[from, to)` of `source` and append it as a piece. */
|
|
84
|
+
private pushPiece;
|
|
85
|
+
/**
|
|
86
|
+
* Replace `count` pieces at `index` with the summaries of `[from, to)` of
|
|
87
|
+
* `source`, cut into pieces of at most `MAX_PIECE`.
|
|
88
|
+
*/
|
|
89
|
+
private spliceSummaries;
|
|
90
|
+
/**
|
|
91
|
+
* Merge the piece at `index` (and the one after it) into a neighbour when
|
|
92
|
+
* it has shrunk below `MIN_PIECE` and the pair stays under `MAX_PIECE`.
|
|
93
|
+
*
|
|
94
|
+
* Merging two summaries is the composition from the header applied to the
|
|
95
|
+
* pair: the sum adds, and the dip of the second is measured from the end of
|
|
96
|
+
* the first. No text is read.
|
|
97
|
+
*/
|
|
98
|
+
private mergeSmallAround;
|
|
99
|
+
/** Fold piece `i + 1` into piece `i`. */
|
|
100
|
+
private mergePair;
|
|
101
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — ChangeTracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks text changes made to the document source.
|
|
5
|
+
* Used by IncrementalParser to determine what needs to be re-parsed.
|
|
6
|
+
*/
|
|
7
|
+
export interface TextChange {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Source range of a text edit, in BOTH coordinate systems.
|
|
14
|
+
*
|
|
15
|
+
* An edit sits between two documents: `start`/`end` describe the changed
|
|
16
|
+
* region in NEW-source coordinates (the current document), while `endOld` is
|
|
17
|
+
* the end of the replaced region in OLD-source coordinates. The incremental
|
|
18
|
+
* preview (`BlockPatcher`) uses `start`/`endOld` to locate the affected blocks
|
|
19
|
+
* in the PREVIOUS tree (whose offsets are pre-edit) and `start`/`end` in the
|
|
20
|
+
* new one — insertions or deletions between the two make the naive single-`end`
|
|
21
|
+
* wrong, which is exactly why both ends are kept.
|
|
22
|
+
*/
|
|
23
|
+
export interface TextChangeRange {
|
|
24
|
+
/** Start of the edited region (identical in both coordinate systems). */
|
|
25
|
+
start: number;
|
|
26
|
+
/** End of the edited region, in new-source coordinates. */
|
|
27
|
+
end: number;
|
|
28
|
+
/** End of the replaced region, in old-source coordinates. */
|
|
29
|
+
endOld: number;
|
|
30
|
+
}
|
|
31
|
+
export interface TextChangeStats {
|
|
32
|
+
totalChanges: number;
|
|
33
|
+
totalInserted: number;
|
|
34
|
+
totalDeleted: number;
|
|
35
|
+
lastChange: TextChange | null;
|
|
36
|
+
}
|
|
37
|
+
export declare class ChangeTracker {
|
|
38
|
+
private changes;
|
|
39
|
+
private maxHistory;
|
|
40
|
+
constructor(maxHistory?: number);
|
|
41
|
+
/**
|
|
42
|
+
* Track a text change.
|
|
43
|
+
*/
|
|
44
|
+
track(change: TextChange): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get all tracked changes.
|
|
47
|
+
*/
|
|
48
|
+
getAll(): TextChange[];
|
|
49
|
+
/**
|
|
50
|
+
* Get the most recent change.
|
|
51
|
+
*/
|
|
52
|
+
getLast(): TextChange | null;
|
|
53
|
+
/**
|
|
54
|
+
* Get the affected range for the last N changes.
|
|
55
|
+
* Returns null if no changes have been tracked.
|
|
56
|
+
*/
|
|
57
|
+
getAffectedRange(count?: number): {
|
|
58
|
+
start: number;
|
|
59
|
+
end: number;
|
|
60
|
+
} | null;
|
|
61
|
+
/**
|
|
62
|
+
* Clear change history.
|
|
63
|
+
*/
|
|
64
|
+
clear(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Get statistics about tracked changes.
|
|
67
|
+
*/
|
|
68
|
+
getStats(): TextChangeStats;
|
|
69
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — IncrementalParser
|
|
3
|
+
*
|
|
4
|
+
* Re-parses the smallest region a text change can have affected, instead of
|
|
5
|
+
* the whole document.
|
|
6
|
+
*
|
|
7
|
+
* ─── Why this was rewritten (roadmap S5 / point 9) ──────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* The previous implementation spliced RED nodes in place and never touched
|
|
10
|
+
* ranges. Three consequences, all measured:
|
|
11
|
+
*
|
|
12
|
+
* - The re-parsed subtree came from `newSource.slice(start, end)`, so its
|
|
13
|
+
* ranges were based at 0 and were grafted in without rebasing. Every offset
|
|
14
|
+
* inside the edited region was wrong by `start`.
|
|
15
|
+
* - Siblings after the splice and every ancestor kept their old ranges, so
|
|
16
|
+
* the tree silently disagreed with the text it claimed to describe.
|
|
17
|
+
* - It returned `newRootRed.green` — the OLD green root — so the model's
|
|
18
|
+
* `_greenRoot` and `_redRoot` desynchronised permanently after the first
|
|
19
|
+
* incremental edit.
|
|
20
|
+
*
|
|
21
|
+
* Rendering the result and comparing it against a full rebuild of the same
|
|
22
|
+
* final text: 3 of 6 realistic editing scenarios produced DIFFERENT HTML, and
|
|
23
|
+
* one grew 14 phantom nodes (1752 vs 1738).
|
|
24
|
+
*
|
|
25
|
+
* ─── How it works now ───────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* Everything happens on the GREEN tree, and the red tree is derived from it.
|
|
28
|
+
* That is not a stylistic choice: green nodes carry widths and no position, so
|
|
29
|
+
* a position is something only a red node has — and therefore only a red node
|
|
30
|
+
* can be wrong about.
|
|
31
|
+
*
|
|
32
|
+
* 1. Descend to the deepest node whose inner span (the part between its
|
|
33
|
+
* delimiters) contains the change, refusing to enter the kinds whose
|
|
34
|
+
* children depend on context outside them (see `OPAQUE_KINDS`).
|
|
35
|
+
* 2. Take the run of that node's children the change touches, widened by one
|
|
36
|
+
* on each side, and re-parse just those.
|
|
37
|
+
* 3. Splice the result back over that run and rebuild the ancestor spine,
|
|
38
|
+
* sharing every untouched subtree by reference.
|
|
39
|
+
*
|
|
40
|
+
* The unit is a RUN OF SIBLINGS, not a whole node. Re-parsing a container's
|
|
41
|
+
* entire contents because one character changed inside it meant typing into a
|
|
42
|
+
* 13 KB `[notice]` re-lexed 66% of the document per keystroke — and an edit at
|
|
43
|
+
* document level had no enclosing container at all, so it fell back to a full
|
|
44
|
+
* rebuild, which is exactly where the caret sits while you write the end of a
|
|
45
|
+
* post.
|
|
46
|
+
*
|
|
47
|
+
* Step 1 relies on the partition invariant from point 14: without it, "the
|
|
48
|
+
* part between the delimiters" is not a well-defined range, which is exactly
|
|
49
|
+
* why this repair was blocked on that work.
|
|
50
|
+
*
|
|
51
|
+
* When any precondition fails the parser returns a full rebuild rather than a
|
|
52
|
+
* plausible-looking wrong tree. `path` says which happened, `reason` says why.
|
|
53
|
+
*/
|
|
54
|
+
import { RedNode } from '../Syntax/RedNode';
|
|
55
|
+
import { GreenNode } from '../Syntax/GreenNode';
|
|
56
|
+
import type { TextChange } from './ChangeTracker';
|
|
57
|
+
/**
|
|
58
|
+
* A span of the source, `[start, end)`, in the coordinates of the text the
|
|
59
|
+
* result describes.
|
|
60
|
+
*/
|
|
61
|
+
export interface SourceSpan {
|
|
62
|
+
start: number;
|
|
63
|
+
end: number;
|
|
64
|
+
}
|
|
65
|
+
export interface EditOperation {
|
|
66
|
+
kind: 'insert' | 'delete' | 'replace';
|
|
67
|
+
start: number;
|
|
68
|
+
end: number;
|
|
69
|
+
text: string;
|
|
70
|
+
/** The minimal range that needs re-parsing */
|
|
71
|
+
affectedStart: number;
|
|
72
|
+
affectedEnd: number;
|
|
73
|
+
}
|
|
74
|
+
/** Options a caller's parse callback must understand. */
|
|
75
|
+
export interface ReparseParseOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Whether the text being parsed is document-level content. Inner spans of
|
|
78
|
+
* containers are not, and must not be grouped into paragraphs.
|
|
79
|
+
*/
|
|
80
|
+
normalizeParagraphs: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface ReparseResult {
|
|
83
|
+
green: GreenNode;
|
|
84
|
+
red: RedNode;
|
|
85
|
+
/** Nodes that were affected by the change */
|
|
86
|
+
affectedNodes: RedNode[];
|
|
87
|
+
/** Time taken in ms (total) */
|
|
88
|
+
duration: number;
|
|
89
|
+
/** Per-phase timing breakdown in ms */
|
|
90
|
+
timings: {
|
|
91
|
+
findAffected: number;
|
|
92
|
+
safeBoundary: number;
|
|
93
|
+
parse: number;
|
|
94
|
+
buildRed: number;
|
|
95
|
+
mutate: number;
|
|
96
|
+
other: number;
|
|
97
|
+
};
|
|
98
|
+
/** Which path was used */
|
|
99
|
+
path: 'incremental' | 'full_rebuild';
|
|
100
|
+
/** When `full_rebuild`, why the incremental path was declined. */
|
|
101
|
+
reason?: FallbackReason;
|
|
102
|
+
/**
|
|
103
|
+
* The span of `newSource` that went through the parser, in NEW coordinates,
|
|
104
|
+
* or `null` after a full rebuild (where the answer is "all of it").
|
|
105
|
+
*
|
|
106
|
+
* This is the contract the incremental semantic analysis is built on. The
|
|
107
|
+
* red tree that comes back is derived from a green tree that shares every
|
|
108
|
+
* subtree outside this span by reference with the previous one, so — with
|
|
109
|
+
* red-subtree reuse — every red node outside the span is the SAME object it
|
|
110
|
+
* was before the edit, and everything anyone computed about it (its
|
|
111
|
+
* diagnostics, above all) is still true of it, up to a shift in position.
|
|
112
|
+
* The nodes inside the span, plus the ancestors on the path down to it
|
|
113
|
+
* (rebuilt because their child lists changed), are the only ones that are
|
|
114
|
+
* new. `SemanticAnalyzer.analyzeWindow` re-validates exactly those.
|
|
115
|
+
*
|
|
116
|
+
* The span is closed on the widened window, not on the edit: the parser
|
|
117
|
+
* re-parses the sibling on each side of the change too (see
|
|
118
|
+
* `findReparseWindow`), and those siblings are new nodes as well.
|
|
119
|
+
*/
|
|
120
|
+
window: SourceSpan | null;
|
|
121
|
+
}
|
|
122
|
+
export type FallbackReason =
|
|
123
|
+
/** No sibling window could be formed around the change. */
|
|
124
|
+
'no-window'
|
|
125
|
+
/** The change touches a container's own delimiter. */
|
|
126
|
+
| 'touches-delimiter'
|
|
127
|
+
/** The region cannot be lexed in isolation — see `regionIsSelfContained`. */
|
|
128
|
+
| 'region-not-isolated'
|
|
129
|
+
/** An unclosed `[` before the region could claim a `]` the edit creates. */
|
|
130
|
+
| 'open-bracket-before'
|
|
131
|
+
/**
|
|
132
|
+
* The window changed what the parser's `autoClosed` set holds when the text
|
|
133
|
+
* after it is read — see `pendingAutoClosePreserved`.
|
|
134
|
+
*/
|
|
135
|
+
| 'pending-auto-close'
|
|
136
|
+
/** The container covers so much of the document that a rebuild is cheaper. */
|
|
137
|
+
| 'region-too-large'
|
|
138
|
+
/** The document is small enough that rebuilding it outright costs less. */
|
|
139
|
+
| 'document-too-small'
|
|
140
|
+
/** The tree's ranges disagree with the source it is supposed to describe. */
|
|
141
|
+
| 'stale-ranges';
|
|
142
|
+
export interface IncrementalParserOptions {
|
|
143
|
+
/** Override `MIN_SOURCE_LENGTH`. Set to 0 to always attempt a splice. */
|
|
144
|
+
minSourceLength?: number;
|
|
145
|
+
/** Override `MAX_REGION_FRACTION`. */
|
|
146
|
+
maxRegionFraction?: number;
|
|
147
|
+
}
|
|
148
|
+
export declare class IncrementalParser {
|
|
149
|
+
private readonly minSourceLength;
|
|
150
|
+
private readonly maxRegionFraction;
|
|
151
|
+
/**
|
|
152
|
+
* Bracket-depth summary of the source, for the boundary check below.
|
|
153
|
+
*
|
|
154
|
+
* Keyed on the green root it was last synchronised with: a reparse whose
|
|
155
|
+
* `oldGreen` is that root brings the index across the edit by re-reading a
|
|
156
|
+
* few KB around it; any other root (a `rebuild`, a model handed a foreign
|
|
157
|
+
* tree) rebuilds it with one scan — the same scan every keystroke used to
|
|
158
|
+
* pay. See `BracketDepthIndex` for why the summary is exact.
|
|
159
|
+
*/
|
|
160
|
+
private readonly brackets;
|
|
161
|
+
private bracketsRoot;
|
|
162
|
+
/**
|
|
163
|
+
* Whether, within the current `reparse` call, the index has been brought to
|
|
164
|
+
* describe `newSource`. Explicit rather than inferred: a length comparison
|
|
165
|
+
* would confuse an unsynchronised index over an older text of the same
|
|
166
|
+
* length (insert one character, delete one) with a synchronised one.
|
|
167
|
+
*/
|
|
168
|
+
private bracketsSynced;
|
|
169
|
+
/**
|
|
170
|
+
* Where the document carries a pending `autoClosed` name, memoised on the
|
|
171
|
+
* green root it was measured over — see {@link PendingSpans}.
|
|
172
|
+
*
|
|
173
|
+
* It survives an incremental splice by construction: the window guard that
|
|
174
|
+
* consults it only lets through edits that leave the crossings on both
|
|
175
|
+
* sides of the window as they were, so the spans need only be moved. Any
|
|
176
|
+
* other outcome drops the key and the next edit pays one walk.
|
|
177
|
+
*/
|
|
178
|
+
private pendingRoot;
|
|
179
|
+
private pendingSpans;
|
|
180
|
+
/**
|
|
181
|
+
* The thresholds are constructor options because they are performance
|
|
182
|
+
* tuning, not semantics: the tree that comes out is the same either way, so
|
|
183
|
+
* a caller with a different document profile — or a test that wants to
|
|
184
|
+
* exercise the splice on a two-line document — can move them without
|
|
185
|
+
* changing what the parser means.
|
|
186
|
+
*/
|
|
187
|
+
constructor(options?: IncrementalParserOptions);
|
|
188
|
+
/**
|
|
189
|
+
* Characters the last boundary check actually read — the tail of one index
|
|
190
|
+
* piece, never the prefix. Exposed so a test can pin the bound.
|
|
191
|
+
*/
|
|
192
|
+
get lastBoundaryScan(): number;
|
|
193
|
+
/**
|
|
194
|
+
* Does every `[` before `end` find its `]` before `end` too?
|
|
195
|
+
*
|
|
196
|
+
* If one does not, the lexer's bracket matching for it scans onward into
|
|
197
|
+
* the region we are about to re-parse — and an edit that adds a `]` there
|
|
198
|
+
* (or deletes a `[` that was keeping the nesting depth up) changes what that
|
|
199
|
+
* OUTSIDE bracket means. The region would be re-parsed correctly and the
|
|
200
|
+
* text before it would silently become something else.
|
|
201
|
+
*
|
|
202
|
+
* A clamped depth count is exact for this question: the lexer pairs
|
|
203
|
+
* brackets with a stack, so a `[` is unmatched precisely when the depth
|
|
204
|
+
* never returns to its level. The count used to be a scan of the whole
|
|
205
|
+
* prefix on every keystroke — 22% of a keystroke with the caret at the end
|
|
206
|
+
* of a post, 1.4 ms on the 547 KB fixture. The index answers it from piece
|
|
207
|
+
* summaries, reading at most one piece of text.
|
|
208
|
+
*
|
|
209
|
+
* The index is brought across the edit here, not earlier: the paths that
|
|
210
|
+
* return before this point never needed it, and on the next call the
|
|
211
|
+
* root-key mismatch simply rebuilds it. The caller re-keys it on whatever
|
|
212
|
+
* green root it returns.
|
|
213
|
+
*/
|
|
214
|
+
private bracketsCloseBefore;
|
|
215
|
+
/**
|
|
216
|
+
* Reparse a tree after a text change.
|
|
217
|
+
*
|
|
218
|
+
* Always returns a result — either an incremental splice or a full rebuild.
|
|
219
|
+
* It never returns a tree whose ranges do not describe `newSource`.
|
|
220
|
+
*/
|
|
221
|
+
reparse(oldRed: RedNode, oldGreen: GreenNode, change: TextChange, newSource: string, parseCallback: (text: string, options?: ReparseParseOptions) => GreenNode, buildRedCallback: (green: GreenNode) => RedNode): ReparseResult;
|
|
222
|
+
/**
|
|
223
|
+
* Find the run of sibling children a change can have affected.
|
|
224
|
+
*
|
|
225
|
+
* Two steps. First descend to the deepest node whose INNER span contains the
|
|
226
|
+
* change — inner rather than full, so a container's own delimiters never go
|
|
227
|
+
* back through the parser: an edit that touches `[colo|r=red]` changes what
|
|
228
|
+
* that element IS, and is handled by re-parsing it as part of its parent's
|
|
229
|
+
* window instead.
|
|
230
|
+
*
|
|
231
|
+
* Then pick the children that the change touches, widened by one on each
|
|
232
|
+
* side. The widening is what lets a deletion MERGE two nodes: removing the
|
|
233
|
+
* blank line between two paragraphs changes only the node in between, and
|
|
234
|
+
* without a neighbour on each side the re-parse could not see that the two
|
|
235
|
+
* survivors have to become one.
|
|
236
|
+
*/
|
|
237
|
+
private findReparseWindow;
|
|
238
|
+
/**
|
|
239
|
+
* Nodes on the path from the root down to the change.
|
|
240
|
+
*
|
|
241
|
+
* Kept because it is part of the public surface and is genuinely useful for
|
|
242
|
+
* callers that want to know what an edit touched; the reparse itself no
|
|
243
|
+
* longer needs it.
|
|
244
|
+
*/
|
|
245
|
+
findAffectedNodes(root: RedNode, change: TextChange): RedNode[];
|
|
246
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|