@linloir/limr 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.en.md +151 -0
  2. package/README.md +80 -127
  3. package/benchmarks/results/layout.json +2593 -0
  4. package/benchmarks/results/markstream-code.json +1823 -0
  5. package/benchmarks/results/markstream-reading-repeat.json +1152 -0
  6. package/benchmarks/results/markstream.json +12486 -0
  7. package/benchmarks/results/streaming-browser.json +77 -0
  8. package/benchmarks/results/streaming.json +357 -0
  9. package/dist/base.css +93 -10
  10. package/dist/{chunk-7JF5HHVF.js → chunk-BG5QUQFR.js} +340 -72
  11. package/dist/chunk-BG5QUQFR.js.map +1 -0
  12. package/dist/chunk-BVHHOVMT.js +179 -0
  13. package/dist/chunk-BVHHOVMT.js.map +1 -0
  14. package/dist/chunk-FJZCQY2S.js +155 -0
  15. package/dist/chunk-FJZCQY2S.js.map +1 -0
  16. package/dist/chunk-FSI5OTBW.js +1 -0
  17. package/dist/{chunk-ETHLPBKQ.js → chunk-GU2RPESF.js} +238 -131
  18. package/dist/chunk-GU2RPESF.js.map +1 -0
  19. package/dist/{chunk-65V5OZ4G.js → chunk-SUAAUGAX.js} +1350 -511
  20. package/dist/chunk-SUAAUGAX.js.map +1 -0
  21. package/dist/core/index.d.ts +6 -3
  22. package/dist/core/index.js +15 -5
  23. package/dist/extensions/index.d.ts +30 -5
  24. package/dist/extensions/index.js +15 -3
  25. package/dist/index.d.ts +4 -4
  26. package/dist/index.js +31 -8
  27. package/dist/{engine-43u8B4yt.d.ts → knuth-plass-C8mgFm7W.d.ts} +67 -1
  28. package/dist/knuth-plass-layout-CHQCWKB5.js +449 -0
  29. package/dist/knuth-plass-layout-CHQCWKB5.js.map +1 -0
  30. package/dist/react/index.d.ts +7 -5
  31. package/dist/react/index.js +7 -3
  32. package/dist/styles.css +93 -10
  33. package/dist/{translations-BliabrPd.d.ts → translations-BXzSwLBJ.d.ts} +49 -4
  34. package/docs/README.md +13 -0
  35. package/docs/api.md +5 -158
  36. package/docs/assets/rendered.svg +9 -0
  37. package/docs/assets/source.svg +9 -0
  38. package/docs/en/README.md +28 -0
  39. package/docs/en/ai-skills.md +57 -0
  40. package/docs/en/annotations.md +86 -0
  41. package/docs/en/api.md +115 -0
  42. package/docs/en/architecture.md +41 -0
  43. package/docs/en/extensions.md +208 -0
  44. package/docs/en/getting-started.md +69 -0
  45. package/docs/en/integration.md +154 -0
  46. package/docs/en/modules.md +84 -0
  47. package/docs/en/performance.md +93 -0
  48. package/docs/en/plugin-packages.md +68 -0
  49. package/docs/en/styles.md +99 -0
  50. package/docs/en/syntax.md +455 -0
  51. package/docs/extensions.md +5 -306
  52. package/docs/implementation.md +5 -76
  53. package/docs/performance.md +5 -81
  54. package/docs/screenshots/blocks.png +0 -0
  55. package/docs/screenshots/dark.png +0 -0
  56. package/docs/screenshots/desktop.png +0 -0
  57. package/docs/screenshots/plugin-mobile.png +0 -0
  58. package/docs/styles.md +5 -100
  59. package/docs/visual-upgrade.md +5 -145
  60. package/docs/zh-CN/README.md +28 -0
  61. package/docs/zh-CN/ai-skills.md +57 -0
  62. package/docs/zh-CN/annotations.md +86 -0
  63. package/docs/zh-CN/api.md +115 -0
  64. package/docs/zh-CN/architecture.md +41 -0
  65. package/docs/zh-CN/extensions.md +210 -0
  66. package/docs/zh-CN/getting-started.md +69 -0
  67. package/docs/zh-CN/integration.md +154 -0
  68. package/docs/zh-CN/modules.md +84 -0
  69. package/docs/zh-CN/performance.md +93 -0
  70. package/docs/zh-CN/plugin-packages.md +68 -0
  71. package/docs/zh-CN/styles.md +99 -0
  72. package/docs/zh-CN/syntax.md +457 -0
  73. package/examples/notes.tsx +18 -11
  74. package/examples/packages/paper-theme/index.css +25 -0
  75. package/examples/packages/paper-theme/package.json +7 -0
  76. package/examples/packages/reading-tools/index.d.ts +2 -0
  77. package/examples/packages/reading-tools/index.js +25 -0
  78. package/examples/packages/reading-tools/package.json +16 -0
  79. package/package.json +17 -5
  80. package/skills/limr-plugin-development/SKILL.md +22 -0
  81. package/skills/limr-plugin-development/agents/openai.yaml +4 -0
  82. package/skills/limr-plugin-development/assets/plugin-template/README.md +30 -0
  83. package/skills/limr-plugin-development/assets/plugin-template/package.json +36 -0
  84. package/skills/limr-plugin-development/assets/plugin-template/src/index.tsx +50 -0
  85. package/skills/limr-plugin-development/assets/plugin-template/src/styles.css +9 -0
  86. package/skills/limr-plugin-development/assets/plugin-template/tsconfig.json +20 -0
  87. package/skills/limr-plugin-development/assets/plugin-template/tsup.config.ts +8 -0
  88. package/skills/limr-plugin-development/references/contract.md +51 -0
  89. package/skills/limr-use/SKILL.md +23 -0
  90. package/skills/limr-use/agents/openai.yaml +4 -0
  91. package/skills/limr-use/references/integration.md +49 -0
  92. package/skills/limr-use/references/syntax.md +20 -0
  93. package/dist/chunk-65V5OZ4G.js.map +0 -1
  94. package/dist/chunk-7JF5HHVF.js.map +0 -1
  95. package/dist/chunk-ETHLPBKQ.js.map +0 -1
  96. package/dist/chunk-V2OG4Y3F.js +0 -1
  97. /package/dist/{chunk-V2OG4Y3F.js.map → chunk-FSI5OTBW.js.map} +0 -0
@@ -1,3 +1,13 @@
1
+ import {
2
+ intersects,
3
+ lineStarts,
4
+ rangeOf,
5
+ slugify,
6
+ sourcePoint,
7
+ textContent,
8
+ walk
9
+ } from "./chunk-FJZCQY2S.js";
10
+
1
11
  // src/core/parser.ts
2
12
  import { fromMarkdown } from "mdast-util-from-markdown";
3
13
  import { mdxJsx } from "micromark-extension-mdx-jsx";
@@ -5,49 +15,240 @@ import { mdxJsxFromMarkdown } from "mdast-util-mdx-jsx";
5
15
  import { directive } from "micromark-extension-directive";
6
16
  import { directiveFromMarkdown } from "mdast-util-directive";
7
17
 
8
- // src/core/tree.ts
9
- function walk(node, visit, parent) {
10
- visit(node, parent);
11
- for (const child of node.children ?? []) walk(child, visit, node);
12
- }
13
- function textContent(node) {
14
- if (node.type === "image") return String(node.alt ?? "");
15
- return node.value ?? node.children?.map(textContent).join("") ?? "";
16
- }
17
- function rangeOf(node) {
18
- return { start: node.position?.start.offset ?? 0, end: node.position?.end.offset ?? 0 };
19
- }
20
- function intersects(a, b) {
21
- return a.start < b.end && b.start < a.end;
22
- }
23
- function lineStarts(source) {
24
- const result = [0];
25
- for (let i = 0; i < source.length; i++) {
26
- if (source[i] === "\r") {
27
- if (source[i + 1] === "\n") i++;
28
- result.push(i + 1);
29
- } else if (source[i] === "\n") result.push(i + 1);
18
+ // src/core/streaming.ts
19
+ import { decodeString } from "micromark-util-decode-string";
20
+ function insertions(source, edits) {
21
+ edits.sort((a, b) => a.at - b.at || (a.order ?? 0) - (b.order ?? 0));
22
+ const parts = [], points = [];
23
+ let at = 0, delta = 0;
24
+ for (const edit of edits) {
25
+ parts.push(source.slice(at, edit.at), edit.insert);
26
+ points.push({ from: edit.at + delta, to: edit.at + delta + edit.insert.length, at: edit.at, delta: delta + edit.insert.length });
27
+ at = edit.at;
28
+ delta += edit.insert.length;
30
29
  }
31
- return result;
30
+ parts.push(source.slice(at));
31
+ return {
32
+ value: parts.join(""),
33
+ original(offset) {
34
+ let lo = 0, hi = points.length;
35
+ while (lo < hi) {
36
+ const mid = lo + hi >>> 1;
37
+ if (points[mid].from <= offset) lo = mid + 1;
38
+ else hi = mid;
39
+ }
40
+ const point = points[lo - 1];
41
+ return Math.min(source.length, point ? offset < point.to ? point.at : offset - point.delta : offset);
42
+ }
43
+ };
32
44
  }
33
- function sourcePoint(offset, lines) {
34
- let lo = 0, hi = lines.length;
35
- while (lo + 1 < hi) {
36
- const mid = lo + hi >>> 1;
37
- if (lines[mid] <= offset) lo = mid;
38
- else hi = mid;
45
+ function prepareSource(source, names, tags, streaming, literals = /* @__PURE__ */ new Set()) {
46
+ if (!streaming && (!names.size || !source.includes("::")) || !/[<:`~$]/.test(source)) return { value: source, original: (at) => at, incomplete: /* @__PURE__ */ new Set() };
47
+ const edits = [], protectedRegions = [];
48
+ const incomplete = /* @__PURE__ */ new Set();
49
+ const containers = [], stack = [];
50
+ let fence;
51
+ let hidden;
52
+ let literal;
53
+ let maxDepth = 0;
54
+ const lines = /[^\r\n]*(?:\r\n|\r|\n|$)/g;
55
+ for (const match of source.matchAll(lines)) {
56
+ if (!match[0]) continue;
57
+ const line = match[0].replace(/[\r\n]+$/, ""), at = match.index;
58
+ const prefix = /^(?: {0,3}>[ \t]?)*(?: {0,3}(?:[-+*]|\d+[.)])[ \t]+)? {0,3}/.exec(line)[0];
59
+ const body = line.slice(prefix.length), start = at + prefix.length;
60
+ if (literal && !/^:{2,}[ \t]*$/.test(body)) continue;
61
+ if (literal) {
62
+ protectedRegions.push({ start: literal.content, end: at });
63
+ literal = void 0;
64
+ }
65
+ if (fence) {
66
+ if (new RegExp(`^${fence.marker}{${fence.size},}[ \\t]*$`).test(body)) {
67
+ protectedRegions.push({ start: fence.start, end: at + match[0].length });
68
+ fence = void 0;
69
+ }
70
+ continue;
71
+ }
72
+ const openingFence = /^(`{3,}|~{3,}|\${2,})(.*)$/.exec(body);
73
+ if (openingFence && !(openingFence[1][0] === "`" && openingFence[2].includes("`"))) {
74
+ fence = { start, marker: openingFence[1][0] === "$" ? "\\$" : openingFence[1][0], size: openingFence[1].length };
75
+ if (streaming && !/[\r\n]$/.test(match[0])) hidden = { start };
76
+ continue;
77
+ }
78
+ if (/^[ \t]/.test(body)) {
79
+ protectedRegions.push({ start: at, end: at + match[0].length });
80
+ continue;
81
+ }
82
+ const open = /^(:{2,})([a-zA-Z][\w-]*)(.*)$/.exec(body);
83
+ if (open && names.has(open[2])) {
84
+ const rest = open[3].trim();
85
+ const validHeader = /^(?:\[(?:\\.|[^\]])*\])?(?:\{(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^{}"'])*\})?$/.test(rest);
86
+ if (!validHeader) {
87
+ if (streaming && at + match[0].length === source.length) hidden = { start, name: open[2] };
88
+ continue;
89
+ }
90
+ const item = { start, marker: start, size: open[1].length, depth: stack.length };
91
+ containers.push(item);
92
+ stack.push(item);
93
+ maxDepth = Math.max(maxDepth, item.depth);
94
+ if (literals.has(open[2]) && /[\r\n]$/.test(match[0])) {
95
+ item.content = at + match[0].length;
96
+ item.prefix = prefix.replace(/[-+*]|\d+[.)]/g, (value) => " ".repeat(value.length));
97
+ literal = item;
98
+ }
99
+ continue;
100
+ }
101
+ const closing = /^(:{2,})[ \t]*$/.exec(body);
102
+ if (closing && stack.length) {
103
+ if (closing[1].length > 2) {
104
+ let ancestor = -1;
105
+ for (let index = stack.length - 1; index >= 0; index--) if (stack[index].size > 2 && stack[index].size <= closing[1].length) {
106
+ ancestor = index;
107
+ break;
108
+ }
109
+ if (ancestor >= 0) for (const leaf of stack.splice(ancestor + 1)) leaf.leaf = true;
110
+ }
111
+ const item = stack[stack.length - 1];
112
+ if (closing[1].length >= item.size) {
113
+ item.close = start;
114
+ item.end = at + line.length;
115
+ stack.pop();
116
+ }
117
+ } else if (streaming && /^::[\w-]*$/.test(body) && at + match[0].length === source.length) hidden = { start };
39
118
  }
40
- return { offset, line: lo + 1, column: offset - lines[lo] + 1 };
41
- }
42
- function slugify(text) {
43
- return text.toLowerCase().trim().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "") || "section";
119
+ if (fence) {
120
+ protectedRegions.push({ start: fence.start, end: source.length });
121
+ if (streaming) incomplete.add(fence.start);
122
+ }
123
+ if (literal) protectedRegions.push({ start: literal.content, end: source.length });
124
+ const active = containers.filter((item) => !item.leaf && (item.close !== void 0 || streaming));
125
+ const hasDouble = active.some((item) => item.size === 2);
126
+ const maxSize = Math.max(3, ...active.map((value) => value.size));
127
+ if (hasDouble) for (const item of active) {
128
+ const width = maxSize + maxDepth - item.depth;
129
+ if (width > item.size) edits.push({ at: item.marker, insert: ":".repeat(width - item.size) });
130
+ if (item.close !== void 0) {
131
+ const size = /^:+/.exec(source.slice(item.close))[0].length;
132
+ if (width > size) edits.push({ at: item.close, insert: ":".repeat(width - size) });
133
+ }
134
+ }
135
+ for (const item of active) if (item.content !== void 0) {
136
+ const payload = source.slice(item.content, item.close ?? source.length);
137
+ const fence2 = "~".repeat(Math.max(3, ...[...payload.matchAll(/~+/g)].map((match) => match[0].length + 1)));
138
+ edits.push({ at: item.content, insert: `${item.prefix}${fence2}
139
+ `, order: -2 });
140
+ if (item.close !== void 0) edits.push({ at: item.close, insert: `${fence2}
141
+ ${item.prefix}`, order: -1 });
142
+ }
143
+ if (streaming) for (const item of stack) incomplete.add(item.start);
144
+ const openTags = [];
145
+ if (streaming && tags.size && source.includes("<")) {
146
+ let region = 0, codeTicks = 0, codeStart = 0;
147
+ for (let i = 0; i < (hidden?.start ?? source.length); i++) {
148
+ while (protectedRegions[region] && protectedRegions[region].end <= i) region++;
149
+ if (protectedRegions[region]?.start <= i) {
150
+ i = protectedRegions[region].end - 1;
151
+ continue;
152
+ }
153
+ if (source[i] === "\\") {
154
+ i++;
155
+ continue;
156
+ }
157
+ if (!codeTicks && names.has("math") && source[i] === "$") {
158
+ const size = /^\$+/.exec(source.slice(i))[0].length;
159
+ const close = source.indexOf("$".repeat(size), i + size);
160
+ if (close < 0) {
161
+ hidden = { start: i };
162
+ break;
163
+ }
164
+ i = close + size - 1;
165
+ continue;
166
+ }
167
+ if (source[i] === "`") {
168
+ const size = /^`+/.exec(source.slice(i))[0].length;
169
+ if (!codeTicks) {
170
+ codeTicks = size;
171
+ codeStart = i;
172
+ } else if (codeTicks === size) codeTicks = 0;
173
+ i += size - 1;
174
+ continue;
175
+ }
176
+ if (codeTicks || source[i] !== "<") continue;
177
+ const head = /^<(\/?)([a-zA-Z][\w-]*)/.exec(source.slice(i));
178
+ if (!head) {
179
+ if (/^<\/?$/.test(source.slice(i))) hidden = { start: i };
180
+ continue;
181
+ }
182
+ const name = head[2];
183
+ if (!tags.has(name)) {
184
+ if ([...tags].some((tag) => tag.startsWith(name)) && i + head[0].length === source.length) hidden = { start: i };
185
+ continue;
186
+ }
187
+ let end = i + head[0].length, quote = "";
188
+ for (; end < source.length; end++) {
189
+ const char = source[end];
190
+ if (quote) {
191
+ if (char === quote) quote = "";
192
+ } else if (char === '"' || char === "'") quote = char;
193
+ else if (char === ">") break;
194
+ }
195
+ if (end === source.length) {
196
+ hidden = { start: i, name: head[1] ? void 0 : name };
197
+ break;
198
+ }
199
+ if (head[1]) {
200
+ if (openTags.at(-1)?.name === name) openTags.pop();
201
+ } else if (source[end - 1] !== "/") openTags.push({ name, start: i, block: /^(?: {0,3}> ?)*\s*$/.test(source.slice(source.lastIndexOf("\n", i - 1) + 1, i)) && /^[ \t]*(?:\r?\n|$)/.test(source.slice(end + 1)) });
202
+ i = end;
203
+ }
204
+ if (codeTicks) hidden = { start: Math.min(codeStart, hidden?.start ?? source.length) };
205
+ }
206
+ for (const tag of openTags) incomplete.add(tag.start);
207
+ const cutoff = hidden?.start ?? source.length;
208
+ const mapped = insertions(source.slice(0, cutoff), edits.filter((edit) => edit.at < cutoff));
209
+ const suffix = openTags.reverse().map((tag) => `${tag.block ? "\n\n" : ""}</${tag.name}>`).join("");
210
+ return {
211
+ ...mapped,
212
+ value: mapped.value + suffix,
213
+ incomplete,
214
+ hidden,
215
+ recoveryOffset: incomplete.size || hidden ? Math.min(...incomplete, hidden?.start ?? Infinity) : void 0
216
+ };
44
217
  }
45
- function safeUrl(value, image = false) {
46
- if (typeof value !== "string") return void 0;
47
- const normalized = value.trim().replace(/[\u0000-\u0020\u007f]/g, "");
48
- if (/^(?:https?:|mailto:|tel:)/i.test(normalized) || !/^[a-z][a-z\d+.-]*:/i.test(normalized)) return value;
49
- if (image && /^data:image\/(?:png|gif|jpe?g|webp|avif);base64,/i.test(normalized)) return value;
50
- return void 0;
218
+ function concealIncomplete(root, source, lines) {
219
+ const tail = root.children?.at(-1);
220
+ if (tail?.type === "paragraph" && tail.position) {
221
+ const raw = source.slice(tail.position.start.offset);
222
+ if (/^ {0,3}\|[^\r\n]*(?:(?:\r\n|\r|\n)[|:\- \t]*)?$/.test(raw)) {
223
+ tail.children = [];
224
+ return;
225
+ }
226
+ }
227
+ let cutoff = source.length;
228
+ walk(root.children?.at(-1) ?? root, (node) => {
229
+ if (node.type !== "text" || !node.position) return;
230
+ const { start, end } = node.position;
231
+ const raw = source.slice(start.offset);
232
+ for (const candidate of raw.slice(0, end.offset - start.offset).matchAll(/[!\[`*_~\$&^]/g)) {
233
+ const at = candidate.index;
234
+ let escapes = 0;
235
+ for (let before = at - 1; before >= 0 && raw[before] === "\\"; before--) escapes++;
236
+ if (escapes % 2) continue;
237
+ if (raw[at] === "_" && /[\p{L}\p{N}]/u.test(raw[at - 1] ?? "") && /[\p{L}\p{N}]/u.test(raw[at + 1] ?? "")) continue;
238
+ const tail2 = raw.slice(at).trimEnd();
239
+ if (/^(?:\^\[[^\]]*$|!?(?:\[[^\]]*$|\[[^\]]*\]\([^)]*$)|`+[^`]*$|\*{1,3}(?:\S[^*]*)?$|_{1,3}(?:\S[^_]*)?$|~{1,2}(?:\S[^~]*)?$|\$[^$]*$|&(?:#x?[\da-f]*|[a-z]*)$)/i.test(tail2)) {
240
+ cutoff = Math.min(cutoff, start.offset + at);
241
+ break;
242
+ }
243
+ }
244
+ });
245
+ if (cutoff === source.length) return;
246
+ walk(root, (node) => {
247
+ if (node.children) node.children = node.children.filter((child) => (child.position?.start.offset ?? 0) < cutoff);
248
+ if (node.type !== "text" || !node.position || node.position.end.offset <= cutoff) return;
249
+ node.value = decodeString(source.slice(node.position.start.offset, cutoff));
250
+ node.position = { start: node.position.start, end: sourcePoint(cutoff, lines) };
251
+ });
51
252
  }
52
253
 
53
254
  // src/core/parser.ts
@@ -55,6 +256,7 @@ function validateExtensions(extensions) {
55
256
  const names = /* @__PURE__ */ new Set();
56
257
  const tags = /* @__PURE__ */ new Set();
57
258
  const directives = /* @__PURE__ */ new Set();
259
+ const owners = /* @__PURE__ */ new Map();
58
260
  for (const extension of extensions) {
59
261
  if (names.has(extension.name)) throw new Error(`Duplicate LiMR extension: ${extension.name}. Use replaceExtension() to replace it explicitly.`);
60
262
  names.add(extension.name);
@@ -64,46 +266,72 @@ function validateExtensions(extensions) {
64
266
  known.add(name);
65
267
  }
66
268
  }
269
+ for (const name of [...extension.tags ?? [], ...extension.directives ?? []]) {
270
+ if (owners.has(name) && owners.get(name) !== extension) throw new Error(`Duplicate LiMR component syntax: ${name}`);
271
+ owners.set(name, extension);
272
+ }
67
273
  }
68
274
  }
69
275
  function createParser(extensions) {
70
276
  validateExtensions(extensions);
71
277
  const tagDefinitions = new Map(extensions.flatMap((ext) => (ext.tags ?? []).map((name) => [name, ext])));
72
- const directiveDefinitions = new Map(extensions.flatMap((ext) => (ext.directives ?? []).map((name) => [name, ext])));
278
+ const directiveDefinitions = new Map(extensions.flatMap((ext) => [.../* @__PURE__ */ new Set([...ext.tags ?? [], ...ext.directives ?? []])].map((name) => [name, ext])));
279
+ const names = new Set(directiveDefinitions.keys()), tags = new Set(tagDefinitions.keys());
280
+ const literals = new Set([...directiveDefinitions].filter(([, definition]) => definition.literal).map(([name]) => name));
73
281
  const syntax = extensions.flatMap((ext) => [...ext.micromark ?? []]);
74
282
  const mdast = extensions.flatMap((ext) => [...ext.mdast ?? []]);
283
+ const plainSyntax = [...syntax], plainMdast = [...mdast];
75
284
  if (directiveDefinitions.size) {
76
285
  syntax.push(directive());
77
286
  mdast.push(directiveFromMarkdown());
78
287
  }
79
288
  const jsxSyntax = tagDefinitions.size ? [...syntax, { disable: { null: ["htmlFlow", "htmlText"] } }, mdxJsx()] : syntax;
80
289
  const jsxMdast = tagDefinitions.size ? [...mdast, mdxJsxFromMarkdown()] : mdast;
290
+ const plainJsxSyntax = [...plainSyntax, { disable: { null: ["htmlFlow", "htmlText"] } }, mdxJsx()];
291
+ const plainJsxMdast = [...plainMdast, mdxJsxFromMarkdown()];
292
+ let literalCache;
81
293
  return (fragment, offset, context, lines, environment = "") => {
82
294
  const prefix = environment ? `${environment}
83
295
 
84
296
  limr-definition-boundary
85
297
 
86
298
  ` : "";
87
- const input = prefix + fragment;
299
+ const cached = literalCache;
300
+ const canAppendLiteral = context.streaming && cached && !prefix && cached.offset === offset && fragment.startsWith(cached.fragment) && !new RegExp(`(?:^|[\\r\\n]) {0,3}${cached.fence[0]}{${cached.fence.length},}[ \\t]*(?=[\\r\\n]|$)`).test(fragment.slice(Math.max(cached.content, cached.fragment.lastIndexOf("\n") + 1, cached.fragment.lastIndexOf("\r") + 1)));
301
+ const prepared = canAppendLiteral ? { value: fragment, original: (at) => at, incomplete: /* @__PURE__ */ new Set([0]), recoveryOffset: 0, hidden: void 0 } : prepareSource(fragment, names, tags, context.streaming, literals);
302
+ const input = prefix + prepared.value;
303
+ const hasDirectives = directiveDefinitions.size > 0 && /:[a-zA-Z]/.test(input);
304
+ const hasJsx = tagDefinitions.size > 0 && input.includes("<");
305
+ const activeSyntax = hasDirectives ? syntax : plainSyntax, activeMdast = hasDirectives ? mdast : plainMdast;
306
+ const mappedOffset = (at) => offset + prepared.original(Math.max(0, at - prefix.length));
307
+ const mappedLines = lines.length ? lines : lineStarts(context.source);
88
308
  const diagnostics = [];
89
309
  let attributeLines;
90
310
  const attributePoint = (at) => sourcePoint(at, lines.length ? lines : attributeLines ??= lineStarts(context.source));
91
- let recoveryOffset;
311
+ let recoveryOffset = prepared.recoveryOffset === void 0 ? void 0 : offset + prepared.recoveryOffset;
92
312
  let root;
93
313
  try {
94
- root = fromMarkdown(input, { extensions: jsxSyntax, mdastExtensions: jsxMdast });
314
+ root = canAppendLiteral ? { type: "root", children: [{
315
+ ...cached.node,
316
+ value: fragment.slice(cached.content).replace(/(?:\r\n|\r|\n)$/, ""),
317
+ position: { start: { offset: 0, line: 1, column: 1 }, end: { offset: fragment.length, line: 0, column: 0 } }
318
+ }] } : fromMarkdown(input, { extensions: hasJsx ? hasDirectives ? jsxSyntax : plainJsxSyntax : activeSyntax, mdastExtensions: hasJsx ? hasDirectives ? jsxMdast : plainJsxMdast : activeMdast });
95
319
  } catch (error) {
96
- root = fromMarkdown(input, { extensions: syntax, mdastExtensions: mdast });
320
+ root = fromMarkdown(input, { extensions: activeSyntax, mdastExtensions: activeMdast });
97
321
  recoveryOffset = offset;
98
322
  diagnostics.push({ message: error instanceof Error ? error.message : String(error), offset });
99
323
  }
100
324
  root.children = root.children?.filter((node) => (node.position?.start.offset ?? 0) >= prefix.length);
101
- if (offset || prefix.length || tagDefinitions.size || directiveDefinitions.size) walk(root, (node) => {
102
- if (node.position && (offset || prefix.length)) {
103
- const start = Math.max(0, node.position.start.offset - prefix.length + offset);
104
- const end = Math.max(start, node.position.end.offset - prefix.length + offset);
105
- node.position = { start: sourcePoint(start, lines), end: sourcePoint(end, lines) };
325
+ const only = root.children?.length === 1 ? root.children[0] : void 0;
326
+ const fence = !prefix && only?.type === "code" && prepared.incomplete.has(0) && /^(`{3,}|~{3,})[^\r\n]*(?:\r\n|\r|\n)/.exec(fragment);
327
+ literalCache = fence ? { fragment, offset, node: { ...only }, fence: fence[1], content: fence[0].length } : void 0;
328
+ if (offset || prefix.length || tagDefinitions.size || directiveDefinitions.size || context.streaming) walk(root, (node) => {
329
+ if (node.position && (offset || prefix.length || prepared.value !== fragment || canAppendLiteral)) {
330
+ const start = mappedOffset(node.position.start.offset);
331
+ const end = Math.max(start, mappedOffset(node.position.end.offset));
332
+ node.position = { start: sourcePoint(start, mappedLines), end: sourcePoint(end, mappedLines) };
106
333
  }
334
+ if (node.position && prepared.incomplete.has(node.position.start.offset - offset)) node.incomplete = true;
107
335
  if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
108
336
  const definition = tagDefinitions.get(node.name ?? "");
109
337
  const rawAttributes = node.attributes;
@@ -113,8 +341,8 @@ limr-definition-boundary
113
341
  if (attr.type === "mdxJsxAttribute" && (typeof attr.value === "string" || attr.value === null)) {
114
342
  attributes[attr.name] = attr.value ?? true;
115
343
  if (definition && typeof attr.value === "string" && attr.position) {
116
- const start = attr.position.start.offset - prefix.length + offset;
117
- const end = attr.position.end.offset - prefix.length + offset;
344
+ const start = mappedOffset(attr.position.start.offset);
345
+ const end = mappedOffset(attr.position.end.offset);
118
346
  const raw = context.source.slice(start, end), opening = raw.search(/["']/);
119
347
  if (opening >= 0) attributeNodes[attr.name] = { type: "text", value: attr.value, verbatimEscapes: true, position: { start: attributePoint(start + opening + 1), end: attributePoint(end - 1) } };
120
348
  }
@@ -156,7 +384,20 @@ limr-definition-boundary
156
384
  }
157
385
  }
158
386
  });
159
- if (offset || prefix.length) root.position = { start: sourcePoint(offset, lines), end: sourcePoint(offset + fragment.length, lines) };
387
+ if (prepared.hidden) {
388
+ const start = offset + prepared.hidden.start;
389
+ recoveryOffset = Math.min(recoveryOffset ?? start, root.children?.at(-1)?.position?.start.offset ?? start);
390
+ root.children?.push({
391
+ type: prepared.hidden.name ? "component" : "streamPending",
392
+ name: prepared.hidden.name,
393
+ incomplete: true,
394
+ position: { start: sourcePoint(start, mappedLines), end: sourcePoint(offset + fragment.length, mappedLines) },
395
+ children: [],
396
+ attributes: {}
397
+ });
398
+ }
399
+ if (context.streaming) concealIncomplete(root, context.source, mappedLines);
400
+ if (offset || prefix.length) root.position = { start: sourcePoint(offset, mappedLines), end: sourcePoint(offset + fragment.length, mappedLines) };
160
401
  for (const extension of extensions) {
161
402
  try {
162
403
  extension.transform?.(root, context);
@@ -179,6 +420,19 @@ var DocumentEngine = class {
179
420
  listeners = /* @__PURE__ */ new Set();
180
421
  definitionSignature = "";
181
422
  recoveryOffset;
423
+ lines = [0];
424
+ facts = /* @__PURE__ */ new WeakMap();
425
+ relevant(node) {
426
+ let result = this.facts.get(node);
427
+ if (!result) {
428
+ const nodes = [];
429
+ walk(node, (child) => {
430
+ if (child.annotation || ["heading", "definition", "footnoteDefinition", "footnoteReference", "annotationDefinition", "annotationReference"].includes(child.type)) nodes.push(child);
431
+ });
432
+ this.facts.set(node, result = nodes);
433
+ }
434
+ return result;
435
+ }
182
436
  constructor(source = "", options = {}) {
183
437
  this.id = options.id ?? `limr-${++sequence}`;
184
438
  this.extensions = options.extensions ?? [];
@@ -210,29 +464,34 @@ var DocumentEngine = class {
210
464
  const globalTransform = this.extensions.some((ext) => ext.transform && ext.scope !== "local");
211
465
  const append = !force && previous?.streaming && source.startsWith(previous.source) && !globalTransform;
212
466
  const finalizing = !!previous?.streaming && !streaming;
213
- let start = append && streaming && previous.blocks.length ? previous.blocks[previous.blocks.length - 1].range.start : 0;
467
+ let start = append && previous.blocks.length ? previous.blocks[previous.blocks.length - 1].range.start : 0;
214
468
  const recovering = this.recoveryOffset !== void 0;
215
469
  if (append && recovering) start = Math.min(start, this.recoveryOffset);
216
470
  let reason = force || globalTransform ? "extension" : !previous ? "initial" : finalizing ? "finalize" : append ? "append" : "replace";
217
- const lines = start ? lineStarts(source) : [];
471
+ if (append && previous) {
472
+ const from = Math.max(0, previous.source.length - 1);
473
+ while (this.lines.length > 1 && this.lines.at(-1) > from) this.lines.pop();
474
+ for (const at of lineStarts(source.slice(from)).slice(1)) this.lines.push(from + at);
475
+ } else this.lines = lineStarts(source);
476
+ const lines = this.lines;
218
477
  const context = { source, streaming, documentId: this.id };
219
478
  let retained = start ? previous.blocks.filter((block) => block.range.end <= start) : [];
220
479
  const environment = [];
221
- for (const block of retained) walk(block.node, (node) => {
480
+ for (const block of retained) for (const node of this.relevant(block.node)) {
222
481
  if (node.type === "definition" || node.type === "footnoteDefinition") {
223
482
  const range = rangeOf(node);
224
483
  environment.push(source.slice(range.start, range.end));
225
484
  }
226
- });
485
+ }
227
486
  let parsed = this.parse(source.slice(start), start, context, lines, environment.join("\n\n"));
228
487
  const getSignature = (nodes) => {
229
488
  const entries = [];
230
- for (const node of nodes) walk(node, (child) => {
489
+ for (const node of nodes) for (const child of this.relevant(node)) {
231
490
  if (child.type === "definition" || child.type === "footnoteDefinition") {
232
491
  const range = rangeOf(child);
233
492
  entries.push(source.slice(range.start, range.end));
234
493
  }
235
- });
494
+ }
236
495
  return entries.join("\0");
237
496
  };
238
497
  let signature = getSignature([...retained.map((block) => block.node), ...parsed.nodes]);
@@ -248,7 +507,7 @@ var DocumentEngine = class {
248
507
  }
249
508
  this.definitionSignature = signature;
250
509
  this.recoveryOffset = parsed.recoveryOffset;
251
- const oldAt = new Map(previous?.blocks.map((block) => [block.range.start, block]));
510
+ const oldAt = new Map(previous?.blocks.slice(retained.length).map((block) => [block.range.start, block]));
252
511
  let reused = retained.length;
253
512
  const blocks = [...retained];
254
513
  for (let index = 0; index < parsed.nodes.length; index++) {
@@ -269,7 +528,7 @@ var DocumentEngine = class {
269
528
  const annotations = /* @__PURE__ */ new Map();
270
529
  const occurrences = [];
271
530
  const slugCounts = /* @__PURE__ */ new Map();
272
- for (const block of blocks) walk(block.node, (node) => {
531
+ for (const block of blocks) for (const node of this.relevant(block.node)) {
273
532
  if (node.type === "heading") {
274
533
  const text = textContent(node);
275
534
  const base = slugify(text);
@@ -280,15 +539,15 @@ var DocumentEngine = class {
280
539
  if (node.type === "definition" && !definitions.has(String(node.identifier))) definitions.set(String(node.identifier), node);
281
540
  if (node.type === "footnoteDefinition" && !footnotes.has(String(node.identifier))) footnotes.set(String(node.identifier), node);
282
541
  if (node.type === "footnoteReference" && !footnoteOrder.includes(String(node.identifier))) footnoteOrder.push(String(node.identifier));
283
- if (node.type === "annotationDefinition") {
542
+ if (node.annotation) {
284
543
  const annotation = node.annotation;
285
- annotations.set(annotation.id, annotation);
544
+ if (node.type !== "footnoteDefinition" || !annotations.has(annotation.id)) annotations.set(annotation.id, annotation);
286
545
  }
287
546
  if (node.type === "annotationReference") {
288
547
  const range = rangeOf(node);
289
- occurrences.push({ id: `${this.id}-ref-${range.start}`, range, annotationIds: node.annotationIds, embedded: true });
548
+ occurrences.push({ id: `${this.id}-ref-${range.start}`, range, annotationIds: node.annotationIds, embedded: true, ...node.referenceRange ? { referenceRange: node.referenceRange } : {} });
290
549
  }
291
- });
550
+ }
292
551
  this.snapshot = {
293
552
  id: this.id,
294
553
  revision: (previous?.revision ?? -1) + 1,
@@ -320,7 +579,7 @@ function replaceExtension(extensions, replacement) {
320
579
  }
321
580
 
322
581
  // src/core/selection.ts
323
- import { decodeString } from "micromark-util-decode-string";
582
+ import { decodeString as decodeString2 } from "micromark-util-decode-string";
324
583
  function mapText(source, value, range, options = {}) {
325
584
  const raw = source.slice(range.start, range.end);
326
585
  const starts = [], ends = [];
@@ -333,7 +592,7 @@ function mapText(source, value, range, options = {}) {
333
592
  } else if (raw[at] === "&") {
334
593
  const entity = /^&(?:#x[\da-f]+|#\d+|[a-z][\da-z]+);/i.exec(raw.slice(at));
335
594
  if (entity) {
336
- const result = decodeString(entity[0]);
595
+ const result = decodeString2(entity[0]);
337
596
  if (result !== entity[0]) {
338
597
  length = entity[0].length;
339
598
  decoded = result;
@@ -434,6 +693,19 @@ function selectionFromRange(document, raw, options = {}) {
434
693
  // src/core/annotations.ts
435
694
  var defaultHighlight = { color: "#F2C744", style: "background" };
436
695
  var sameRange = (a, b) => a.start === b.start && a.end === b.end;
696
+ function annotationReferenceRange(document, occurrence) {
697
+ if (occurrence.referenceRange) return occurrence.referenceRange;
698
+ if (!occurrence.embedded) return occurrence.range;
699
+ let reference = occurrence.range;
700
+ const visit = (node) => {
701
+ const range = rangeOf(node);
702
+ if (!intersects(range, occurrence.range)) return;
703
+ if (["paragraph", "heading", "listItem", "tableCell"].includes(node.type)) reference = range;
704
+ node.children?.forEach(visit);
705
+ };
706
+ for (const block of document.blocks) if (intersects(block.range, occurrence.range)) visit(block.node);
707
+ return reference;
708
+ }
437
709
  function resolveAnnotationAnchors(annotation, document) {
438
710
  return (annotation.anchors ?? (annotation.range ? [{ range: annotation.range }] : [])).flatMap((anchor) => {
439
711
  if (anchor.documentId && anchor.documentId !== document.id) return [];
@@ -448,11 +720,6 @@ function resolveAnnotationAnchors(annotation, document) {
448
720
  }
449
721
 
450
722
  export {
451
- walk,
452
- textContent,
453
- rangeOf,
454
- intersects,
455
- safeUrl,
456
723
  DocumentEngine,
457
724
  createDocument,
458
725
  replaceExtension,
@@ -460,6 +727,7 @@ export {
460
727
  selectionFromRange,
461
728
  defaultHighlight,
462
729
  sameRange,
730
+ annotationReferenceRange,
463
731
  resolveAnnotationAnchors
464
732
  };
465
- //# sourceMappingURL=chunk-7JF5HHVF.js.map
733
+ //# sourceMappingURL=chunk-BG5QUQFR.js.map