@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.
Files changed (273) hide show
  1. package/dist/Visuals/lyne.css +117 -6
  2. package/dist/Visuals/osu.css +36 -5
  3. package/dist/index.js +9664 -3282
  4. package/dist/index.mjs +9535 -3283
  5. package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
  6. package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
  7. package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
  8. package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
  9. package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
  10. package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
  11. package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
  12. package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
  13. package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
  14. package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
  15. package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
  16. package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
  17. package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
  18. package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
  19. package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
  20. package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
  21. package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
  22. package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
  23. package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
  24. package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
  25. package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
  26. package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
  27. package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
  28. package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
  29. package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
  30. package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
  31. package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
  32. package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
  33. package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
  34. package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
  35. package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
  36. package/dist/types/Analysis/index.d.ts +40 -0
  37. package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
  38. package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
  39. package/dist/types/BBCode/Parser.d.ts +78 -0
  40. package/dist/types/BBCode/index.d.ts +4 -0
  41. package/dist/types/Collab/positions.d.ts +55 -0
  42. package/dist/types/Commands/Command.d.ts +37 -0
  43. package/dist/types/Commands/CommandRegistry.d.ts +48 -0
  44. package/dist/types/Commands/DeleteNode.d.ts +7 -0
  45. package/dist/types/Commands/InsertText.d.ts +8 -0
  46. package/dist/types/Commands/SplitMerge.d.ts +9 -0
  47. package/dist/types/Commands/WrapInTag.d.ts +8 -0
  48. package/dist/types/Commands/index.d.ts +6 -0
  49. package/dist/types/Diff/TreeDiffer.d.ts +52 -0
  50. package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
  51. package/dist/types/Diff/index.d.ts +2 -0
  52. package/dist/types/Edits/EditPlan.d.ts +144 -0
  53. package/dist/types/Edits/Optimizer.d.ts +78 -0
  54. package/dist/types/Edits/Rules/Rule.d.ts +91 -0
  55. package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
  56. package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
  57. package/dist/types/Edits/Rules/index.d.ts +9 -0
  58. package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
  59. package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
  60. package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
  61. package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
  62. package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
  63. package/dist/types/Edits/applyEdits.d.ts +36 -0
  64. package/dist/types/Edits/index.d.ts +6 -0
  65. package/dist/types/Events/EventBus.d.ts +103 -0
  66. package/dist/types/Events/index.d.ts +2 -0
  67. package/dist/types/Formatter/Formatter.d.ts +32 -0
  68. package/dist/types/Formatter/index.d.ts +2 -0
  69. package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
  70. package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
  71. package/dist/types/Incremental/BracketIndex.d.ts +101 -0
  72. package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
  73. package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
  74. package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
  75. package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
  76. package/dist/types/Incremental/index.d.ts +4 -0
  77. package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
  78. package/dist/types/Lexer/Lexer.d.ts +54 -0
  79. package/dist/types/Lexer/index.d.ts +4 -0
  80. package/dist/types/Linter/Linter.d.ts +58 -0
  81. package/dist/types/Linter/index.d.ts +2 -0
  82. package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
  83. package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
  84. package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
  85. package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
  86. package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
  87. package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
  88. package/dist/types/Model/DocumentModel.d.ts +318 -0
  89. package/dist/types/Model/NodeFactory.d.ts +29 -0
  90. package/dist/types/Model/TagRegistry.d.ts +165 -0
  91. package/dist/types/Model/index.d.ts +5 -0
  92. package/dist/types/Plugins/PluginAPI.d.ts +44 -0
  93. package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
  94. package/dist/types/Plugins/index.d.ts +3 -0
  95. package/dist/types/Queries/QueryEngine.d.ts +32 -0
  96. package/dist/types/Queries/index.d.ts +1 -0
  97. package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
  98. package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
  99. package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
  100. package/dist/types/RenderPipeline/index.d.ts +4 -0
  101. package/dist/types/Repair/NestingRepair.d.ts +48 -0
  102. package/dist/types/Repair/index.d.ts +1 -0
  103. package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
  104. package/dist/types/Semantic/index.d.ts +2 -0
  105. package/dist/types/Symbols/SymbolTable.d.ts +47 -0
  106. package/dist/types/Symbols/index.d.ts +1 -0
  107. package/dist/types/Syntax/GreenNode.d.ts +154 -0
  108. package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
  109. package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
  110. package/dist/types/Syntax/RedNode.d.ts +280 -0
  111. package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
  112. package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
  113. package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
  114. package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
  115. package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
  116. package/dist/types/Syntax/greenEdit.d.ts +64 -0
  117. package/dist/types/Syntax/hash.d.ts +17 -0
  118. package/dist/types/Syntax/index.d.ts +8 -0
  119. package/dist/types/Syntax/nodeAttr.d.ts +63 -0
  120. package/dist/types/Syntax/partition.d.ts +67 -0
  121. package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
  122. package/dist/types/Tokens/TokenResolver.d.ts +25 -0
  123. package/dist/types/Tokens/index.d.ts +1 -0
  124. package/dist/types/Transactions/Transaction.d.ts +36 -0
  125. package/dist/types/Transactions/UndoManager.d.ts +63 -0
  126. package/dist/types/Transactions/index.d.ts +3 -0
  127. package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
  128. package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
  129. package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
  130. package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
  131. package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
  132. package/dist/types/Transformers/Transformer.d.ts +17 -0
  133. package/dist/types/Types/core.d.ts +104 -0
  134. package/dist/types/Types/diagnostics.d.ts +85 -0
  135. package/dist/types/Types/index.d.ts +11 -0
  136. package/dist/types/Types/operations.d.ts +109 -0
  137. package/dist/types/Types/queries.d.ts +67 -0
  138. package/dist/types/Types/symbols.d.ts +42 -0
  139. package/dist/types/Types/tokens.d.ts +75 -0
  140. package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
  141. package/dist/types/Utils/ColorMath.d.ts +92 -0
  142. package/dist/types/Utils/EffectMath.d.ts +643 -0
  143. package/dist/types/Utils/TagVocabulary.d.ts +104 -0
  144. package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
  145. package/dist/types/Utils/color.d.ts +29 -0
  146. package/dist/types/Utils/dom-to-svg.d.ts +28 -0
  147. package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
  148. package/dist/types/Utils/treeTransformers.d.ts +160 -0
  149. package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
  150. package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
  151. package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
  152. package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
  153. package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
  154. package/dist/types/Visitors/JSONExporter.d.ts +42 -0
  155. package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
  156. package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
  157. package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
  158. package/dist/types/Visitors/Visitor.d.ts +35 -0
  159. package/dist/types/Visitors/index.d.ts +9 -0
  160. package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
  161. package/dist/types/Visuals/index.d.ts +38 -0
  162. package/dist/types/index.d.ts +154 -0
  163. package/package.json +14 -10
  164. package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
  165. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
  166. package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
  167. package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
  168. package/src/Analysis/Pipeline/Pipeline.ts +15 -2
  169. package/src/Analysis/index.ts +8 -2
  170. package/src/BBCode/BBCodeDocumentModel.ts +3 -0
  171. package/src/BBCode/BBCodeToGreenNode.ts +18 -12
  172. package/src/BBCode/Parser.ts +38 -1
  173. package/src/Edits/EditPlan.ts +290 -0
  174. package/src/Edits/Optimizer.ts +185 -0
  175. package/src/Edits/Rules/Rule.ts +145 -0
  176. package/src/Edits/Rules/dropEmptyTags.ts +110 -0
  177. package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
  178. package/src/Edits/Rules/index.ts +28 -0
  179. package/src/Edits/Rules/mergeAdjacent.ts +224 -0
  180. package/src/Edits/Rules/reorderWrappers.ts +154 -0
  181. package/src/Edits/Rules/shortenHex.ts +79 -0
  182. package/src/Edits/Rules/tagValue.ts +105 -0
  183. package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
  184. package/src/Edits/applyEdits.ts +54 -0
  185. package/src/Edits/index.ts +22 -0
  186. package/src/Events/EventBus.ts +18 -0
  187. package/src/HTML/HTMLToGreenNode.ts +185 -37
  188. package/src/Incremental/BracketIndex.ts +249 -0
  189. package/src/Incremental/IncrementalParser.ts +464 -41
  190. package/src/Lexer/BBCodeLexer.ts +25 -3
  191. package/src/Lexer/index.ts +1 -1
  192. package/src/Markdown/MarkdownAST.ts +65 -2
  193. package/src/Markdown/MarkdownLexer.ts +69 -14
  194. package/src/Markdown/MarkdownParser.ts +749 -62
  195. package/src/Markdown/MarkdownToGreenNode.ts +148 -27
  196. package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
  197. package/src/Model/DocumentModel.ts +138 -5
  198. package/src/Model/TagRegistry.ts +129 -134
  199. package/src/Reconciler/SurgicalReconciler.ts +460 -0
  200. package/src/Repair/NestingRepair.ts +116 -0
  201. package/src/Repair/index.ts +1 -0
  202. package/src/Semantic/SemanticAnalyzer.ts +1802 -65
  203. package/src/Syntax/GreenNodePool.ts +12 -12
  204. package/src/Syntax/RedNode.ts +17 -3
  205. package/src/Syntax/RedNodeStore.ts +9 -1
  206. package/src/Syntax/nodeAttr.ts +172 -0
  207. package/src/Tokens/TokenResolver.ts +55 -0
  208. package/src/Tokens/index.ts +1 -0
  209. package/src/Types/core.ts +15 -0
  210. package/src/Utils/ColorMath.ts +276 -55
  211. package/src/Utils/EffectMath.ts +2462 -0
  212. package/src/Utils/TagVocabulary.ts +141 -0
  213. package/src/Utils/dom-to-svg.ts +28 -12
  214. package/src/Visitors/BBBlocksExporter.ts +15 -1
  215. package/src/Visitors/BBCodeExporter.ts +319 -32
  216. package/src/Visitors/BlockPatcher.ts +87 -11
  217. package/src/Visitors/DOMMorpher.ts +15 -5
  218. package/src/Visitors/HTMLRenderer.ts +374 -151
  219. package/src/Visitors/MarkdownExporter.ts +98 -41
  220. package/src/Visitors/TiptapExporter.ts +1 -0
  221. package/src/Visuals/index.ts +7 -4
  222. package/src/Visuals/lyne.css +117 -6
  223. package/src/Visuals/osu.css +36 -5
  224. package/src/index.ts +172 -5
  225. package/dist/index.d.mts +0 -4612
  226. package/dist/index.d.ts +0 -4612
  227. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
  228. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
  229. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
  230. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
  231. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
  232. package/src/Analysis/__tests__/Integration.test.ts +0 -162
  233. package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
  234. package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
  235. package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
  236. package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
  237. package/src/Syntax/__tests__/RedNode.test.ts +0 -81
  238. package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
  239. package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
  240. package/src/Tests/BlockPatcher.test.ts +0 -437
  241. package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
  242. package/src/Tests/BoxDrawer.test.ts +0 -217
  243. package/src/Tests/BoxRichTitle.test.ts +0 -105
  244. package/src/Tests/Chars500kBenchmark.test.ts +0 -151
  245. package/src/Tests/Chars500kEdits.test.ts +0 -321
  246. package/src/Tests/CollabPositions.test.ts +0 -146
  247. package/src/Tests/CompilerPathProfiling.test.ts +0 -186
  248. package/src/Tests/DOMMorpher.test.ts +0 -142
  249. package/src/Tests/DomPatchPerf.test.ts +0 -60
  250. package/src/Tests/EffectSegments.snapshot.json +0 -616
  251. package/src/Tests/EffectSegments.test.ts +0 -68
  252. package/src/Tests/FindNodeAtOffset.test.ts +0 -65
  253. package/src/Tests/Fuzzer.test.ts +0 -166
  254. package/src/Tests/GreenNodePool.test.ts +0 -153
  255. package/src/Tests/Lexer.test.ts +0 -238
  256. package/src/Tests/LyneMode.test.ts +0 -291
  257. package/src/Tests/ModelCoherence.test.ts +0 -180
  258. package/src/Tests/Partition.test.ts +0 -238
  259. package/src/Tests/PluginTags.test.ts +0 -150
  260. package/src/Tests/ProblematicSection.test.ts +0 -46
  261. package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
  262. package/src/Tests/RedReuse.test.ts +0 -134
  263. package/src/Tests/ReproDelete20k.test.ts +0 -62
  264. package/src/Tests/SemanticValidators.test.ts +0 -136
  265. package/src/Tests/StableNodeIds.test.ts +0 -210
  266. package/src/Tests/StudioColorBloat.test.ts +0 -25
  267. package/src/Tests/StudioDebugText.test.ts +0 -27
  268. package/src/Tests/StudioTrailingChar.test.ts +0 -25
  269. package/src/Tests/StudioValidText.test.ts +0 -25
  270. package/src/Tests/UrlImgBug.test.ts +0 -23
  271. package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
  272. package/src/Tests/referenceDocument.ts +0 -119
  273. package/src/Utils/dom-to-svg.test.ts +0 -86
@@ -18,16 +18,13 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
18
18
  for (const child of node.children) children.push(convert(child));
19
19
  break;
20
20
  case 'paragraph':
21
- kind = 'text'; // DocumentEngine paragraphs are usually groups or blocks, treating as paragraph block if we want.
22
- // Actually DocumentEngine uses 'text' or 'group'
23
- kind = 'document'; // Let's use group or document as container
21
+ kind = 'paragraph';
24
22
  for (const child of node.children) children.push(convert(child));
25
23
  break;
26
24
  case 'heading':
27
25
  kind = 'heading';
28
- // Faking the '#' text length for the heading level offset
29
26
  currentOffset += node.level;
30
- text = `=${node.level}`; // Use text as metadata like BBCode
27
+ text = `=${node.level}`;
31
28
  for (const child of node.children) children.push(convert(child));
32
29
  break;
33
30
  case 'strong':
@@ -38,6 +35,58 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
38
35
  kind = 'italic';
39
36
  for (const child of node.children) children.push(convert(child));
40
37
  break;
38
+ case 'strikethrough':
39
+ kind = 'strikethrough';
40
+ for (const child of node.children) children.push(convert(child));
41
+ break;
42
+ case 'underline':
43
+ kind = 'underline';
44
+ for (const child of node.children) children.push(convert(child));
45
+ break;
46
+ case 'color':
47
+ kind = 'color';
48
+ text = `=${node.color}`;
49
+ for (const child of node.children) children.push(convert(child));
50
+ break;
51
+ case 'font_size':
52
+ kind = 'font_size';
53
+ text = `=${node.size}`;
54
+ for (const child of node.children) children.push(convert(child));
55
+ break;
56
+ case 'font':
57
+ kind = 'font';
58
+ text = `=${node.font}`;
59
+ for (const child of node.children) children.push(convert(child));
60
+ break;
61
+ case 'center':
62
+ kind = 'center';
63
+ for (const child of node.children) children.push(convert(child));
64
+ break;
65
+ case 'right':
66
+ kind = 'right';
67
+ for (const child of node.children) children.push(convert(child));
68
+ break;
69
+ case 'left':
70
+ kind = 'left';
71
+ for (const child of node.children) children.push(convert(child));
72
+ break;
73
+ case 'align':
74
+ kind = 'align';
75
+ for (const child of node.children) children.push(convert(child));
76
+ break;
77
+ case 'box':
78
+ kind = 'box';
79
+ text = node.title ? `=${node.title}` : '';
80
+ for (const child of node.children) children.push(convert(child));
81
+ break;
82
+ case 'spoilerbox':
83
+ kind = 'spoilerbox';
84
+ text = node.title ? `=${node.title}` : '';
85
+ for (const child of node.children) children.push(convert(child));
86
+ break;
87
+ case 'separator':
88
+ kind = 'separator';
89
+ break;
41
90
  case 'text':
42
91
  kind = 'text';
43
92
  text = node.value;
@@ -48,11 +97,6 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
48
97
  text = `=${node.url}`;
49
98
  for (const child of node.children) children.push(convert(child));
50
99
  break;
51
- case 'image':
52
- kind = 'image';
53
- text = node.url;
54
- currentOffset += text.length;
55
- break;
56
100
  case 'code_block':
57
101
  kind = 'code';
58
102
  children.push(greenLeaf('text', node.value));
@@ -60,6 +104,7 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
60
104
  break;
61
105
  case 'list':
62
106
  kind = 'list';
107
+ text = node.ordered ? '=1' : '';
63
108
  for (const child of node.children) children.push(convert(child));
64
109
  break;
65
110
  case 'list_item':
@@ -72,10 +117,17 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
72
117
  break;
73
118
  case 'notice':
74
119
  kind = 'notice';
120
+ text = node.color ? `=${node.color}` : '';
121
+ for (const child of node.children) children.push(convert(child));
122
+ break;
123
+ case 'wnotice':
124
+ kind = 'wnotice';
125
+ text = node.color ? `=${node.color}` : '';
75
126
  for (const child of node.children) children.push(convert(child));
76
127
  break;
77
128
  case 'blockquote':
78
129
  kind = 'quote';
130
+ text = node.source ? `=${node.source}` : '';
79
131
  for (const child of node.children) children.push(convert(child));
80
132
  break;
81
133
  case 'spacing':
@@ -88,24 +140,23 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
88
140
  text = '\n\n';
89
141
  currentOffset += 2;
90
142
  break;
91
- // `image` and `code_inline` used to land in `default`, which discarded
92
- // them as empty text nodes — Markdown images never survived conversion.
93
143
  case 'image':
94
144
  kind = 'image';
95
- text = `=${node.url}`;
96
- if (node.alt) {
97
- children.push(greenLeaf('text', node.alt));
98
- currentOffset += node.alt.length;
99
- }
145
+ // In osu! BBCode, image content is the URL between [img] and [/img]
146
+ children.push(greenLeaf('text', node.url));
147
+ currentOffset += node.url.length;
100
148
  break;
101
149
  case 'code_inline':
102
150
  kind = 'inline_code';
103
151
  children.push(greenLeaf('text', node.value));
104
152
  currentOffset += node.value.length;
105
153
  break;
154
+ case 'bbcode_tag':
155
+ kind = node.tagName as NodeKind;
156
+ text = node.attrValue ? `=${node.attrValue}` : '';
157
+ for (const child of node.children) children.push(convert(child));
158
+ break;
106
159
  default: {
107
- // Exhaustiveness guard: adding a MarkdownNode variant without a case
108
- // here is now a compile error rather than a silently dropped node.
109
160
  const unhandled: never = node;
110
161
  void unhandled;
111
162
  kind = 'text';
@@ -113,25 +164,95 @@ export function markdownAstToGreenTree(root: MarkdownNode): GreenNode {
113
164
  }
114
165
  }
115
166
 
116
- const end = currentOffset;
117
- // Map text container to group for proper DocumentEngine representation if not text
118
- const actualKind = kind === 'document' && node.type === 'paragraph' ? 'group' : kind;
119
- return greenNode(actualKind as string, text, children);
167
+ return greenNode(kind as string, text, children);
120
168
  }
121
169
 
122
170
  return convert(root);
123
171
  }
124
172
 
173
+ const EFFECT_TAGS = new Set(['glow', 'neon', 'outline', 'shimmer', 'ghost', 'rainbow', 'fire', 'ice', 'emboss', 'engrave']);
174
+ const ANIM_TAGS = new Set(['typewriter', 'wave', 'sparkle', 'glitch', 'levitate', 'pulse', 'bounce', 'shake', 'fade-in', 'fade-out']);
175
+ const CONTAINER_TAGS = new Set(['card', 'glass', 'neon-box', 'neonbox', 'square', 'circle', 'stack', 'flex', 'grid', 'middle']);
176
+
125
177
  export function greenToRedNode(green: GreenNode, parent?: RedNode | null): RedNode {
126
- // Simple metadata extraction based on heading text
127
178
  let metadata: Record<string, unknown> = {};
128
- if (green.kind === 'heading' && green.text.startsWith('=')) {
129
- metadata = { level: parseInt(green.text.slice(1)) || 1 };
179
+ let kind = (green.kind as NodeKind) || 'text';
180
+
181
+ const attrValue = green.text.startsWith('=') ? green.text.slice(1) : undefined;
182
+
183
+ if (EFFECT_TAGS.has(green.kind)) {
184
+ kind = 'effect';
185
+ metadata.effectType = green.kind;
186
+ if (attrValue) metadata.color = attrValue;
187
+ } else if (ANIM_TAGS.has(green.kind)) {
188
+ kind = 'anim';
189
+ metadata.animType = green.kind;
190
+ if (attrValue) metadata.animParam = attrValue;
191
+ } else if (CONTAINER_TAGS.has(green.kind)) {
192
+ kind = 'container';
193
+ metadata.containerType = green.kind;
194
+ if (attrValue) metadata.color = attrValue;
195
+ } else if (green.kind === 'row') {
196
+ kind = 'table_row';
197
+ } else if (green.kind === 'col') {
198
+ kind = 'table_col';
199
+ if (attrValue) metadata.variant = attrValue;
200
+ } else if (green.kind === 'th') {
201
+ kind = 'table_th';
202
+ if (attrValue) metadata.variant = attrValue;
203
+ } else if (green.kind === 'tables') {
204
+ kind = 'tables';
205
+ if (attrValue) metadata.variant = attrValue;
206
+ } else if (green.kind === 'columns') {
207
+ kind = 'columns';
208
+ if (attrValue) metadata.columns = parseInt(attrValue) || 2;
209
+ } else if (green.kind === 'scroll') {
210
+ kind = 'scroll';
211
+ if (attrValue) metadata.height = attrValue;
212
+ } else if (green.kind === 'separator') {
213
+ kind = 'separator';
214
+ if (attrValue) metadata.variant = attrValue;
215
+ } else if (green.kind === 'heading' && attrValue) {
216
+ metadata.level = parseInt(attrValue) || 1;
217
+ } else if (green.kind === 'color' && attrValue) {
218
+ metadata.color = attrValue;
219
+ } else if (green.kind === 'font_size' && attrValue) {
220
+ metadata.size = attrValue;
221
+ } else if (green.kind === 'font' && attrValue) {
222
+ metadata.font = attrValue;
223
+ } else if (green.kind === 'url' && attrValue) {
224
+ metadata.href = attrValue;
225
+ } else if (green.kind === 'list' && attrValue === '1') {
226
+ metadata.ordered = true;
227
+ } else if ((green.kind === 'box' || green.kind === 'boxw' || green.kind === 'spoilerbox') && attrValue) {
228
+ metadata.title = attrValue;
229
+ metadata.rawTitle = attrValue;
230
+ } else if ((green.kind === 'notice' || green.kind === 'wnotice') && attrValue) {
231
+ metadata.color = attrValue;
232
+ } else if (green.kind === 'quote' && attrValue) {
233
+ metadata.source = attrValue;
234
+ } else if (green.kind === 'abbr' && attrValue) {
235
+ metadata.title = attrValue;
236
+ } else if (green.kind === 'tooltip' && attrValue) {
237
+ metadata.tip = attrValue;
238
+ } else if (green.kind === 'style' || green.kind === 'style_tag') {
239
+ kind = 'style_tag';
240
+ if (attrValue) metadata.style = attrValue;
241
+ } else if (green.kind === 'image') {
242
+ if (green.children.length > 0) {
243
+ const first = green.children[0] as GreenNode;
244
+ if (first.kind === 'text') {
245
+ metadata.src = first.text;
246
+ }
247
+ }
248
+ } else if (attrValue) {
249
+ metadata.value = attrValue;
250
+ metadata.raw = attrValue;
130
251
  }
131
252
 
132
253
  const red = new RedNode(green, {
133
254
  parent: parent ?? null,
134
- kind: (green.kind as NodeKind) || 'text',
255
+ kind,
135
256
  metadata
136
257
  });
137
258
 
@@ -0,0 +1,56 @@
1
+ import { DocumentModel, type DocumentModelOptions } from '../Model/DocumentModel';
2
+ import { GreenNode, greenLeaf } from '../Syntax/GreenNode';
3
+ import { RedNode } from '../Syntax/RedNode';
4
+ import { scanMarkdown } from '../Markdown/MarkdownLexer';
5
+ import { parseMarkdown } from '../Markdown/MarkdownParser';
6
+ import { markdownAstToGreenTree, greenToRedNode } from '../Markdown/MarkdownToGreenNode';
7
+
8
+ export interface MilHibriDocumentModelOptions extends DocumentModelOptions {
9
+ source?: string;
10
+ }
11
+
12
+ /**
13
+ * DocumentEngine — MilHibriDocumentModel
14
+ *
15
+ * First-class DocumentModel for the MilHibri language:
16
+ * Unifies 100% of osu! BBCode and 100% of Markdown + Modern Industry Extensions
17
+ * into a single unified, collision-free AST.
18
+ */
19
+ export class MilHibriDocumentModel extends DocumentModel {
20
+ constructor(options: MilHibriDocumentModelOptions = {}) {
21
+ super({ source: '', language: 'milhibri' });
22
+ if (options.source) {
23
+ this.rebuild(options.source);
24
+ }
25
+ }
26
+
27
+ static fromSource(source: string): MilHibriDocumentModel {
28
+ return new MilHibriDocumentModel({ source });
29
+ }
30
+
31
+ protected parseToGreen(source: string): GreenNode {
32
+ try {
33
+ const tokens = scanMarkdown(source);
34
+ const ast = parseMarkdown(tokens);
35
+ return markdownAstToGreenTree(ast);
36
+ } catch (error) {
37
+ console.warn('[MilHibriDocumentModel] Parse error:', error);
38
+ return greenLeaf('text', source);
39
+ }
40
+ }
41
+
42
+ protected buildRedFromGreen(green: GreenNode): RedNode {
43
+ return greenToRedNode(green);
44
+ }
45
+
46
+ applyTextUpdate(newSource: string): void {
47
+ if (this.source === newSource) return;
48
+ this.rebuild(newSource);
49
+ }
50
+
51
+ applyChange(change: any): void {
52
+ const before = this.source.slice(0, change.start);
53
+ const after = this.source.slice(change.end);
54
+ this.rebuild(before + change.text + after);
55
+ }
56
+ }
@@ -27,6 +27,7 @@ import {
27
27
  IncrementalParser,
28
28
  type ReparseParseOptions,
29
29
  type FallbackReason,
30
+ type SourceSpan,
30
31
  } from '../Incremental/IncrementalParser'
31
32
  import { ChangeTracker } from '../Incremental/ChangeTracker'
32
33
  import type { TextChange, TextChangeRange } from '../Incremental/ChangeTracker'
@@ -84,6 +85,34 @@ export interface DocumentModelOptions {
84
85
  reuseRed?: boolean
85
86
  }
86
87
 
88
+ /**
89
+ * A span of the OLD text, in the coordinates of the text `change` produced.
90
+ *
91
+ * An endpoint before the change does not move and one after it moves by the
92
+ * change's delta; one INSIDE it has no image, so it is pushed outwards — the
93
+ * start down to where the change begins, the end up to where the replacement
94
+ * ends. The span this belongs to is a re-validation window, and a window that
95
+ * covers slightly more than it must costs nodes, never correctness.
96
+ */
97
+ function shiftSpan(span: SourceSpan | null, change: TextChange): SourceSpan | null {
98
+ if (span === null) return null
99
+ const delta = change.text.length - (change.end - change.start)
100
+ const newEnd = change.start + change.text.length
101
+ const start = span.start <= change.start
102
+ ? span.start
103
+ : span.start >= change.end ? span.start + delta : change.start
104
+ const end = span.end <= change.start
105
+ ? span.end
106
+ : span.end >= change.end ? span.end + delta : newEnd
107
+ return { start, end }
108
+ }
109
+
110
+ /** The smallest span containing both. */
111
+ function unionSpans(a: SourceSpan | null, b: SourceSpan): SourceSpan {
112
+ if (a === null) return b
113
+ return { start: Math.min(a.start, b.start), end: Math.max(a.end, b.end) }
114
+ }
115
+
87
116
  export class DocumentModel {
88
117
  // ── Core State ──
89
118
  private _source: string
@@ -123,6 +152,32 @@ export class DocumentModel {
123
152
  /** Why the last reparse fell back to a full rebuild, if it did. */
124
153
  lastReparseFallbackReason: FallbackReason | null = null
125
154
  lastReparseTimings: { findAffected: number; safeBoundary: number; parse: number; buildRed: number; mutate: number; other: number } | null = null
155
+ /**
156
+ * The span of the current source the last reparse actually re-parsed, in
157
+ * CURRENT coordinates — `null` after a full rebuild, where the answer is
158
+ * "all of it". See {@link ReparseResult.window}.
159
+ */
160
+ lastReparseWindow: SourceSpan | null = null
161
+
162
+ // ── Incremental analysis bookkeeping ──
163
+ //
164
+ // `analyzeWindow` re-validates the nodes an edit could have changed and
165
+ // keeps every other node's verdict. What makes that sound is a promise only
166
+ // the model can make: that this tree grew out of the analysed one through
167
+ // incremental reparses WITH red-subtree reuse, so every node outside the
168
+ // windows is the same object over the same text. These two fields are that
169
+ // promise, tracked edit by edit.
170
+
171
+ /**
172
+ * The union of every reparse window since the last analysis, in CURRENT
173
+ * coordinates — `null` when nothing is pending. A bounding span rather than
174
+ * a list: over-covering only means re-validating a few more nodes, and the
175
+ * arithmetic to keep several spans current across later edits costs more
176
+ * than the nodes do.
177
+ */
178
+ private _analysisWindow: SourceSpan | null = null
179
+ /** Set by anything a window pass cannot be built on: a rebuild, a reparse that fell back. */
180
+ private _analysisWindowBroken = true
126
181
 
127
182
  constructor(options: DocumentModelOptions = {}) {
128
183
  this._options = {
@@ -181,6 +236,12 @@ export class DocumentModel {
181
236
  return this._diagnostics
182
237
  }
183
238
 
239
+ /**
240
+ * What the last analysis cost and how it was obtained — its `scope` says
241
+ * whether it walked the document or only the edit's window, and `window`
242
+ * carries that span when it did. The diagnostics themselves are on
243
+ * {@link diagnostics}, and are the whole document's on either route.
244
+ */
184
245
  get lastAnalyze(): AnalyzeResult | null {
185
246
  return this._lastAnalyzeResult
186
247
  }
@@ -215,6 +276,12 @@ export class DocumentModel {
215
276
  // incrementally, so the range is cleared and the preview falls back to its
216
277
  // full reconcile (which is what it must do for undo/redo/load anyway).
217
278
  this._lastChangeRange = null
279
+ this.lastReparseWindow = null
280
+ // A rebuild replaces every node in the tree, so nothing a previous
281
+ // analysis knew about any of them survives. Set BEFORE `analyze()` below,
282
+ // which reads it.
283
+ this._analysisWindow = null
284
+ this._analysisWindowBroken = true
218
285
  this._attachChangeRange(this._redRoot)
219
286
  // Unchanged nodes keep the identity they had before the rebuild, so the
220
287
  // HTML of untouched subtrees stays byte-identical between renders and the
@@ -296,8 +363,22 @@ export class DocumentModel {
296
363
  }
297
364
 
298
365
  /**
299
- * Apply a source text change (e.g. from Monaco editor input).
300
- * Uses incremental parsing when possible.
366
+ * Apply one exact source change THE edit path, and the one an editor
367
+ * should use.
368
+ *
369
+ * An editor already knows what the user did: Monaco's
370
+ * `onDidChangeModelContent` hands over `rangeOffset`, `rangeLength` and
371
+ * `text`, which is exactly a {@link TextChange}. Handing that over instead
372
+ * of the new document text skips the only part of the pipeline that is
373
+ * unavoidably proportional to the whole document — the prefix/suffix diff
374
+ * in {@link applyTextUpdate} — and the difference is not marginal. Typing
375
+ * 120 characters into the middle of the 500 KB fixture, per keystroke:
376
+ *
377
+ * applyTextUpdate p50 7.35 ms p95 28.79 ms
378
+ * applyChange p50 1.85 ms p95 8.18 ms
379
+ *
380
+ * Everything downstream is already windowed, so what is left after the diff
381
+ * goes is work proportional to the edit.
301
382
  *
302
383
  * `origin` tags where the change came from — `'local'` (default) for the
303
384
  * user's own editing, anything else for programmatic or synced sources
@@ -357,6 +438,7 @@ export class DocumentModel {
357
438
  this.lastReparsePath = result.path
358
439
  this.lastReparseFallbackReason = result.reason ?? null
359
440
  this.lastReparseTimings = result.timings
441
+ this.lastReparseWindow = result.window
360
442
  this._greenRoot = result.green
361
443
  this._redRoot = result.red
362
444
  // Identity across the edit, by whichever mechanism applies.
@@ -377,6 +459,20 @@ export class DocumentModel {
377
459
  if (oldRoot && reuse.adopted === 0) {
378
460
  preserveNodeIds(oldRoot, this._redRoot)
379
461
  }
462
+ // Carry the analysis window across this edit, or give up on it. Both
463
+ // conditions matter: without a reparse window every node is new, and
464
+ // without adopted subtrees `preserveNodeIds` has just moved ids
465
+ // between different nodes, which is exactly what an id-keyed snapshot
466
+ // of the previous analysis cannot survive.
467
+ if (result.window === null || reuse.adopted === 0) {
468
+ this._analysisWindow = null
469
+ this._analysisWindowBroken = true
470
+ } else if (!this._analysisWindowBroken) {
471
+ this._analysisWindow = unionSpans(
472
+ shiftSpan(this._analysisWindow, change),
473
+ result.window,
474
+ )
475
+ }
380
476
  this._version++
381
477
  // The incremental path resolved: the range stays attached to the new
382
478
  // root so the preview can find the edited region.
@@ -384,6 +480,8 @@ export class DocumentModel {
384
480
  } catch {
385
481
  this.lastReparsePath = 'full_rebuild'
386
482
  this.lastReparseTimings = null
483
+ this.lastReparseWindow = null
484
+ // `rebuild` clears the analysis window itself.
387
485
  this.rebuild(this._source)
388
486
  return
389
487
  }
@@ -427,6 +525,10 @@ export class DocumentModel {
427
525
  version: this._version,
428
526
  source: this._source,
429
527
  diagnostics: this._diagnostics,
528
+ // How they were arrived at, not what they are: `diagnostics` is the
529
+ // whole document's on both routes. See `DocumentEvent.analysisScope`.
530
+ analysisScope: this._lastAnalyzeResult?.scope,
531
+ analysisWindow: this._lastAnalyzeResult?.window ?? null,
430
532
  timestamp: Date.now()
431
533
  }
432
534
  this.defineLazyNodeMatch(diagnosticsEvent, oldRoot, this._redRoot)
@@ -456,8 +558,14 @@ export class DocumentModel {
456
558
  }
457
559
 
458
560
  /**
459
- * Calculate a simple diff between the current source and the new source,
460
- * then apply the change.
561
+ * Recover the edit from the new document text, then apply it.
562
+ *
563
+ * The FALLBACK, for a caller that only has the text: a textarea's `value`,
564
+ * a CRDT that hands over a materialised document, a paste normaliser. It is
565
+ * a shared-prefix/shared-suffix diff, so it costs one pass over the
566
+ * unchanged part of the document before any of the incremental machinery
567
+ * gets to run — which is precisely the term {@link applyChange} does not
568
+ * pay. Anything that knows the edit should say so and use that instead.
461
569
  */
462
570
  applyTextUpdate(newSource: string, origin: string = 'local'): void {
463
571
  if (this._source === newSource) return
@@ -568,12 +676,35 @@ export class DocumentModel {
568
676
 
569
677
  /**
570
678
  * Run semantic analysis on the current tree.
679
+ *
680
+ * Incremental when it can be: if every edit since the last analysis was an
681
+ * incremental reparse that adopted subtrees, only the nodes inside those
682
+ * reparse windows — plus the ones whose document-wide facts the edits moved
683
+ * — are re-validated, and every other node keeps the verdict it already
684
+ * had. The DIAGNOSTICS ARE THE SAME EITHER WAY; the difference is how long
685
+ * it took to arrive at them, and {@link AnalyzeResult.scope} says which
686
+ * route was taken. The differential in `Chars500kAnalysis.test.ts` compares
687
+ * the two, edit by edit, on the real fixture.
688
+ *
689
+ * Falls back to the full pass whenever the window pass declines — no
690
+ * previous pass to build on, the validator set or the token source changed
691
+ * under it, or a validator whose verdicts are not a function of one node
692
+ * (see `Validator.scope`).
571
693
  */
572
694
  analyze(): AnalyzeResult {
573
695
  if (!this._redRoot) {
574
696
  throw new Error('Cannot analyze: no document loaded')
575
697
  }
576
- const result = this.semanticAnalyzer.analyze(this._redRoot, this._source)
698
+ const window = this._analysisWindowBroken ? null : this._analysisWindow
699
+ const result =
700
+ (window !== null
701
+ ? this.semanticAnalyzer.analyzeWindow(this._redRoot, this._source, window)
702
+ : null)
703
+ ?? this.semanticAnalyzer.analyze(this._redRoot, this._source)
704
+ // Whatever route it took, the tree is now fully analysed and the next
705
+ // edit starts a fresh window on top of this pass.
706
+ this._analysisWindow = null
707
+ this._analysisWindowBroken = false
577
708
  this._diagnostics = result.diagnostics
578
709
 
579
710
  // Keep the id index out of the retained result: holding it would pin every
@@ -584,6 +715,8 @@ export class DocumentModel {
584
715
  diagnostics: result.diagnostics,
585
716
  duration: result.duration,
586
717
  nodesAnalyzed: result.nodesAnalyzed,
718
+ scope: result.scope,
719
+ window: result.window,
587
720
  }
588
721
 
589
722
  // Attaching diagnostics to nodes used to happen here, and needed an id→node