@effected/markdown 0.2.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 (65) hide show
  1. package/Frontmatter.js +272 -0
  2. package/FrontmatterResolver.js +273 -0
  3. package/JsonFrontmatter.js +46 -0
  4. package/LICENSE +21 -0
  5. package/Markdown.js +251 -0
  6. package/MarkdownDiagnostic.js +85 -0
  7. package/MarkdownDocument.js +248 -0
  8. package/MarkdownEdit.js +52 -0
  9. package/MarkdownFormat.js +380 -0
  10. package/MarkdownNode.js +641 -0
  11. package/MarkdownVisitor.js +88 -0
  12. package/Mdast.js +384 -0
  13. package/README.md +255 -0
  14. package/TomlFrontmatter.js +44 -0
  15. package/YamlFrontmatter.js +45 -0
  16. package/index.d.ts +2190 -0
  17. package/index.js +15 -0
  18. package/internal/blockParser.js +419 -0
  19. package/internal/blockRegistry.js +90 -0
  20. package/internal/blockTypes.js +27 -0
  21. package/internal/blocks/atxHeading.js +54 -0
  22. package/internal/blocks/blockquote.js +40 -0
  23. package/internal/blocks/code.js +90 -0
  24. package/internal/blocks/document.js +17 -0
  25. package/internal/blocks/fencedCode.js +26 -0
  26. package/internal/blocks/footnoteDefinition.js +84 -0
  27. package/internal/blocks/frontmatter.js +56 -0
  28. package/internal/blocks/htmlBlock.js +72 -0
  29. package/internal/blocks/indentedCode.js +16 -0
  30. package/internal/blocks/linkReferenceDefinition.js +72 -0
  31. package/internal/blocks/list.js +159 -0
  32. package/internal/blocks/paragraph.js +27 -0
  33. package/internal/blocks/setextHeading.js +24 -0
  34. package/internal/blocks/table.js +378 -0
  35. package/internal/blocks/taskListItem.js +36 -0
  36. package/internal/blocks/thematicBreak.js +35 -0
  37. package/internal/carriers.js +42 -0
  38. package/internal/entities.js +26 -0
  39. package/internal/entityMap.js +7 -0
  40. package/internal/htmlTags.js +18 -0
  41. package/internal/inlineNode.js +54 -0
  42. package/internal/inlineParser.js +403 -0
  43. package/internal/inlineRegistry.js +68 -0
  44. package/internal/inlines/autolink.js +36 -0
  45. package/internal/inlines/autolinkLiteral.js +374 -0
  46. package/internal/inlines/codeSpan.js +32 -0
  47. package/internal/inlines/emphasis.js +79 -0
  48. package/internal/inlines/entity.js +21 -0
  49. package/internal/inlines/escape.js +34 -0
  50. package/internal/inlines/footnoteReference.js +63 -0
  51. package/internal/inlines/lineBreak.js +25 -0
  52. package/internal/inlines/link.js +212 -0
  53. package/internal/inlines/rawHtml.js +27 -0
  54. package/internal/inlines/strikethrough.js +81 -0
  55. package/internal/inlines/text.js +22 -0
  56. package/internal/lineIndex.js +76 -0
  57. package/internal/patterns.js +26 -0
  58. package/internal/preprocess.js +58 -0
  59. package/internal/rawInline.js +57 -0
  60. package/internal/references.js +160 -0
  61. package/internal/segments.js +36 -0
  62. package/internal/stringify.js +519 -0
  63. package/internal/unescape.js +18 -0
  64. package/package.json +61 -0
  65. package/tsdoc-metadata.json +11 -0
@@ -0,0 +1,212 @@
1
+ import { ReferenceScanner, normalizeReference } from "../references.js";
2
+ import { appendChild, childrenOf, insertAfter, makeInlineNode, unlink } from "../inlineNode.js";
3
+
4
+ //#region src/internal/inlines/link.ts
5
+ const C_BANG = 33;
6
+ const C_CARET = 94;
7
+ const C_OPEN_BRACKET = 91;
8
+ const C_CLOSE_BRACKET = 93;
9
+ const C_OPEN_PAREN = 40;
10
+ const C_CLOSE_PAREN = 41;
11
+ const reWhitespaceChar = /^[ \t\n\v\f\r]/;
12
+ /**
13
+ * Run one of `references.ts`'s grammar functions over the subject at the
14
+ * scanner's cursor, keeping the two cursors in step.
15
+ *
16
+ * The destination, title and label grammars are identical wherever they
17
+ * appear, so a link reuses exactly what a definition parsed with.
18
+ */
19
+ const withReferenceScanner = (scanner, run) => {
20
+ const reference = new ReferenceScanner(scanner.subject);
21
+ reference.pos = scanner.pos;
22
+ const result = run(reference);
23
+ scanner.pos = reference.pos;
24
+ return result;
25
+ };
26
+ /**
27
+ * The plain-text flattening of a node list — an image's `alt`.
28
+ *
29
+ * ITERATIVE ON PURPOSE. This runs while the brackets close, before
30
+ * materialization's depth guard has seen anything, and the content it walks
31
+ * can nest as deeply as the input has balanced delimiters: the recursive
32
+ * spelling died with a `RangeError` — a defect, not a typed error — on
33
+ * `![` plus ten thousand nested emphasis markers. A flattening has no reason
34
+ * to recurse, so it does not, and needs no cap.
35
+ */
36
+ const plainTextOf = (nodes) => {
37
+ let text = "";
38
+ const pending = [...nodes].reverse();
39
+ while (pending.length > 0) {
40
+ const node = pending.pop();
41
+ if (node === void 0) break;
42
+ if (node.type === "text" || node.type === "inlineCode") text += node.value;
43
+ else if (node.type === "image" || node.type === "imageReference") text += node.value;
44
+ else {
45
+ const children = childrenOf(node);
46
+ for (let index = children.length - 1; index >= 0; index -= 1) {
47
+ const child = children[index];
48
+ if (child !== void 0) pending.push(child);
49
+ }
50
+ }
51
+ }
52
+ return text;
53
+ };
54
+ /** `[` — a potential link opener. */
55
+ const linkOpenConstruct = {
56
+ name: "linkOpen",
57
+ triggers: [C_OPEN_BRACKET],
58
+ parse: (scanner) => {
59
+ const startpos = scanner.pos;
60
+ scanner.pos += 1;
61
+ const node = scanner.appendText("[", startpos, scanner.pos);
62
+ scanner.addBracket(node, startpos, false);
63
+ return true;
64
+ }
65
+ };
66
+ /**
67
+ * Build the `!` construct — an image opener when a `[` follows, otherwise
68
+ * literal.
69
+ *
70
+ * `caretOpensImage` is the second GFM footnote seam, and the surprising one.
71
+ * cmark-gfm's bang handler reads
72
+ *
73
+ * ```c
74
+ * if (peek_char(subj) == '[' && peek_char_n(subj, 1) != '^') {
75
+ * ```
76
+ *
77
+ * so under GFM `![^` NEVER opens an image: the `!` stays literal text and the
78
+ * `[` becomes an ordinary bracket, which is what lets `text![^1]` render as a
79
+ * literal `!` followed by a footnote reference (`extensions.txt` example 23
80
+ * pins exactly that). It also means the footnote branch in the close-bracket
81
+ * handler can never see an image opener, which is why that branch does not
82
+ * test for one. CommonMark passes `true` and is untouched.
83
+ */
84
+ const makeImageOpenConstruct = (caretOpensImage) => ({
85
+ name: "imageOpen",
86
+ triggers: [C_BANG],
87
+ parse: (scanner) => {
88
+ const startpos = scanner.pos;
89
+ scanner.pos += 1;
90
+ if (scanner.peek() === C_OPEN_BRACKET && (caretOpensImage || scanner.subject.charCodeAt(scanner.pos + 1) !== C_CARET)) {
91
+ scanner.pos += 1;
92
+ const node = scanner.appendText("![", startpos, scanner.pos);
93
+ scanner.addBracket(node, startpos + 1, true);
94
+ return true;
95
+ }
96
+ scanner.appendText("!", startpos, scanner.pos);
97
+ return true;
98
+ }
99
+ });
100
+ /** `!` — an image opener when a `[` follows, otherwise literal. */
101
+ const imageOpenConstruct = makeImageOpenConstruct(true);
102
+ /**
103
+ * Build the `]` construct — closes a link or image, or stays literal.
104
+ *
105
+ * `onNoMatch` is the dialect seam described on {@link LinkCloseFallback}. With
106
+ * none, this is CommonMark's close-bracket handler exactly.
107
+ */
108
+ const makeLinkCloseConstruct = (onNoMatch) => ({
109
+ name: "linkClose",
110
+ triggers: [C_CLOSE_BRACKET],
111
+ parse: (scanner) => {
112
+ const bracketPos = scanner.pos;
113
+ scanner.pos += 1;
114
+ const startpos = scanner.pos;
115
+ const opener = scanner.brackets;
116
+ if (opener === void 0) {
117
+ scanner.appendText("]", bracketPos, scanner.pos);
118
+ return true;
119
+ }
120
+ if (!opener.active) {
121
+ scanner.appendText("]", bracketPos, scanner.pos);
122
+ scanner.removeBracket();
123
+ return true;
124
+ }
125
+ const isImage = opener.image;
126
+ const savepos = scanner.pos;
127
+ let url;
128
+ let title;
129
+ let matched = false;
130
+ if (scanner.peek() === C_OPEN_PAREN) {
131
+ scanner.pos += 1;
132
+ const inline = withReferenceScanner(scanner, (reference) => {
133
+ reference.spnl();
134
+ const destination = reference.parseLinkDestination();
135
+ if (destination === void 0) return;
136
+ reference.spnl();
137
+ const parsedTitle = reWhitespaceChar.test(reference.subject.charAt(reference.pos - 1)) ? reference.parseLinkTitle() : void 0;
138
+ reference.spnl();
139
+ if (reference.peek() !== C_CLOSE_PAREN) return;
140
+ reference.pos += 1;
141
+ return {
142
+ destination,
143
+ parsedTitle
144
+ };
145
+ });
146
+ if (inline === void 0) scanner.pos = savepos;
147
+ else {
148
+ url = inline.destination;
149
+ title = inline.parsedTitle;
150
+ matched = true;
151
+ }
152
+ }
153
+ let identifier;
154
+ let rawLabel;
155
+ let referenceType = "shortcut";
156
+ if (!matched) {
157
+ const beforeLabel = scanner.pos;
158
+ const labelLength = withReferenceScanner(scanner, (reference) => reference.parseLinkLabel());
159
+ if (labelLength > 2) {
160
+ rawLabel = scanner.subject.slice(beforeLabel, beforeLabel + labelLength);
161
+ referenceType = "full";
162
+ } else if (opener.bracketAfter !== true) {
163
+ rawLabel = scanner.subject.slice(opener.index, startpos);
164
+ referenceType = labelLength === 2 ? "collapsed" : "shortcut";
165
+ }
166
+ if (labelLength === 0) scanner.pos = savepos;
167
+ if (rawLabel !== void 0) {
168
+ const key = normalizeReference(rawLabel);
169
+ if (scanner.refmap.has(key)) {
170
+ identifier = key.toLowerCase();
171
+ matched = true;
172
+ }
173
+ }
174
+ }
175
+ if (!matched) {
176
+ if (onNoMatch?.(scanner, opener, bracketPos, startpos) === true) return true;
177
+ scanner.removeBracket();
178
+ scanner.pos = startpos;
179
+ scanner.appendText("]", bracketPos, scanner.pos);
180
+ return true;
181
+ }
182
+ const node = makeInlineNode(identifier === void 0 ? isImage ? "image" : "link" : isImage ? "imageReference" : "linkReference", opener.node.start, scanner.pos);
183
+ let child = opener.node.next;
184
+ while (child !== void 0) {
185
+ const following = child.next;
186
+ appendChild(node, child);
187
+ child = following;
188
+ }
189
+ insertAfter(opener.node, node);
190
+ if (identifier === void 0) {
191
+ if (url !== void 0) node.data.url = url;
192
+ if (title !== void 0) node.data.title = title;
193
+ } else {
194
+ node.data.identifier = identifier;
195
+ node.data.label = rawLabel === void 0 ? identifier : rawLabel.slice(1, -1);
196
+ node.data.referenceType = referenceType;
197
+ }
198
+ scanner.processEmphasis(opener.previousDelimiter);
199
+ scanner.removeBracket();
200
+ unlink(opener.node);
201
+ if (isImage) {
202
+ node.value = plainTextOf(childrenOf(node));
203
+ for (const orphan of childrenOf(node)) unlink(orphan);
204
+ } else scanner.deactivateLinkOpeners();
205
+ return true;
206
+ }
207
+ });
208
+ /** `]` — closes a link or image, or stays literal. The CommonMark spelling. */
209
+ const linkCloseConstruct = makeLinkCloseConstruct();
210
+
211
+ //#endregion
212
+ export { imageOpenConstruct, linkCloseConstruct, linkOpenConstruct, makeImageOpenConstruct, makeLinkCloseConstruct };
@@ -0,0 +1,27 @@
1
+ import { reHtmlTag } from "../htmlTags.js";
2
+ import { makeInlineNode } from "../inlineNode.js";
3
+
4
+ //#region src/internal/inlines/rawHtml.ts
5
+ const C_LESSTHAN = 60;
6
+ /** The raw-HTML forms that scan forward for a fixed closing sequence. */
7
+ const UNTERMINATED_FORMS = [
8
+ ["<!--", "-->"],
9
+ ["<?", "?>"],
10
+ ["<![CDATA[", "]]>"]
11
+ ];
12
+ /** A raw HTML tag, comment, processing instruction, declaration or CDATA. */
13
+ const rawHtmlConstruct = {
14
+ name: "rawHtml",
15
+ triggers: [C_LESSTHAN],
16
+ parse: (scanner) => {
17
+ const from = scanner.pos;
18
+ for (const [opener, closer] of UNTERMINATED_FORMS) if (scanner.subject.startsWith(opener, from) && !scanner.hasAhead(closer)) return false;
19
+ const matched = scanner.match(reHtmlTag);
20
+ if (matched === void 0) return false;
21
+ scanner.append(makeInlineNode("html", from, scanner.pos, matched));
22
+ return true;
23
+ }
24
+ };
25
+
26
+ //#endregion
27
+ export { rawHtmlConstruct };
@@ -0,0 +1,81 @@
1
+ import { appendChild, insertAfter, makeInlineNode, unlink } from "../inlineNode.js";
2
+ import { scanDelims } from "./emphasis.js";
3
+
4
+ //#region src/internal/inlines/strikethrough.ts
5
+ /** The `~` character code — the construct's only trigger. */
6
+ const C_TILDE = 126;
7
+ /**
8
+ * Consume a `~` run as literal text and, when it is a viable one- or
9
+ * two-tilde run, push it onto the shared delimiter stack.
10
+ *
11
+ * The run becomes a text node either way: a `~` that never pairs has to
12
+ * survive as the character it is.
13
+ */
14
+ const handleTilde = (scanner) => {
15
+ const res = scanDelims(scanner, 126);
16
+ if (res === void 0) return false;
17
+ const startpos = scanner.pos;
18
+ scanner.pos += res.numdelims;
19
+ const node = scanner.appendText(scanner.subject.slice(startpos, scanner.pos), startpos, scanner.pos);
20
+ if ((res.canOpen || res.canClose) && (res.numdelims === 1 || res.numdelims === 2)) {
21
+ const delimiter = {
22
+ cc: 126,
23
+ numdelims: res.numdelims,
24
+ origdelims: res.numdelims,
25
+ node,
26
+ previous: scanner.delimiters,
27
+ next: void 0,
28
+ canOpen: res.canOpen,
29
+ canClose: res.canClose
30
+ };
31
+ if (delimiter.previous !== void 0) delimiter.previous.next = delimiter;
32
+ scanner.delimiters = delimiter;
33
+ }
34
+ return true;
35
+ };
36
+ /**
37
+ * Pair `opener` with `closer` into a `delete` node — upstream's `insert`.
38
+ *
39
+ * Returns the delimiter the caller's loop continues from, which upstream
40
+ * captures as `closer->next` BEFORE it starts unlinking, so both the matched
41
+ * and the mismatched path resume at the same place.
42
+ */
43
+ const insertStrikethrough = (scanner, opener, closer) => {
44
+ const resume = closer.next;
45
+ const openerNode = opener.node;
46
+ const closerNode = closer.node;
47
+ if (openerNode.value.length === closerNode.value.length) {
48
+ const delims = openerNode.value.length;
49
+ openerNode.value = "";
50
+ openerNode.end -= delims;
51
+ closerNode.value = "";
52
+ closerNode.start += delims;
53
+ const strikethrough = makeInlineNode("delete", openerNode.end, closerNode.start);
54
+ let between = openerNode.next;
55
+ while (between !== void 0 && between !== closerNode) {
56
+ const following = between.next;
57
+ appendChild(strikethrough, between);
58
+ between = following;
59
+ }
60
+ insertAfter(openerNode, strikethrough);
61
+ unlink(openerNode);
62
+ unlink(closerNode);
63
+ }
64
+ let delimiter = closer;
65
+ while (delimiter !== void 0 && delimiter !== opener) {
66
+ const previous = delimiter.previous;
67
+ scanner.removeDelimiter(delimiter);
68
+ delimiter = previous;
69
+ }
70
+ scanner.removeDelimiter(opener);
71
+ return resume;
72
+ };
73
+ /** GFM strikethrough: `~foo~` and `~~foo~~`. */
74
+ const strikethroughConstruct = {
75
+ name: "strikethrough",
76
+ triggers: [126],
77
+ parse: (scanner) => handleTilde(scanner)
78
+ };
79
+
80
+ //#endregion
81
+ export { insertStrikethrough, strikethroughConstruct };
@@ -0,0 +1,22 @@
1
+ //#region src/internal/inlines/text.ts
2
+ const reMain = /^[^\n`[\]\\!<&*_'"]+/;
3
+ const reMainGfm = /^[^\n`[\]\\!<&*_'"~w:]+/;
4
+ /** Consume a run of ordinary characters, stopping before any construct's. */
5
+ const runConstruct = (name, pattern) => ({
6
+ name,
7
+ triggers: [],
8
+ parse: (scanner) => {
9
+ const from = scanner.pos;
10
+ const matched = scanner.match(pattern);
11
+ if (matched === void 0) return false;
12
+ scanner.appendText(matched, from, scanner.pos);
13
+ return true;
14
+ }
15
+ });
16
+ /** Ordinary text: the fallback construct, with no trigger of its own. */
17
+ const textConstruct = runConstruct("text", reMain);
18
+ /** Ordinary text under `gfm`, which has three more characters to yield to. */
19
+ const gfmTextConstruct = runConstruct("text", reMainGfm);
20
+
21
+ //#endregion
22
+ export { gfmTextConstruct, textConstruct };
@@ -0,0 +1,76 @@
1
+ //#region src/internal/lineIndex.ts
2
+ /**
3
+ * A precomputed index of line-start offsets over a fixed source text,
4
+ * answering `positionAt(offset)` in `O(log n)` via binary search rather than
5
+ * rescanning the text per query.
6
+ *
7
+ * @remarks
8
+ * Only `\n` begins a new line. A `\r` immediately before a `\n` (CRLF) is
9
+ * left as the trailing character of the prior line — the pair still reads as
10
+ * a single boundary because the following `\n` is what advances the line
11
+ * counter, not the `\r`. A bare `\r` (old Mac line endings) is not
12
+ * recognized as a break; CommonMark's preprocessing pass normalizes those
13
+ * before this index is ever built.
14
+ */
15
+ var LineIndex = class LineIndex {
16
+ text;
17
+ lineStarts;
18
+ constructor(text, lineStarts) {
19
+ this.text = text;
20
+ this.lineStarts = lineStarts;
21
+ }
22
+ /**
23
+ * Build a {@link LineIndex} over `text` with one forward scan.
24
+ *
25
+ * @remarks
26
+ * Recognizes only `\n`, which is why the block pass hands over its own
27
+ * line table instead — see {@link LineIndex.fromLineStarts}.
28
+ */
29
+ static make(text) {
30
+ const lineStarts = [0];
31
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) lineStarts.push(i + 1);
32
+ return new LineIndex(text, lineStarts);
33
+ }
34
+ /**
35
+ * Build a {@link LineIndex} over `text` from a line table someone else
36
+ * already computed.
37
+ *
38
+ * @remarks
39
+ * The parser splits lines on `\r\n`, `\n` AND a bare `\r`; this index's
40
+ * own scan recognizes only `\n`. For any document without a bare `\r` the
41
+ * two agree, but for one with them they disagree about what a line is, and
42
+ * every reported line number would be wrong. Handing the parser's own
43
+ * table over removes the possibility rather than documenting it: there is
44
+ * one definition of a line, and it belongs to the preprocessor.
45
+ *
46
+ * `starts` must be ascending and begin at 0; a table that is neither is a
47
+ * wiring bug and dies as a defect.
48
+ */
49
+ static fromLineStarts(text, starts) {
50
+ if (starts.length === 0 || starts[0] !== 0) throw new TypeError("line index: a line table must be non-empty and start at offset 0");
51
+ return new LineIndex(text, starts);
52
+ }
53
+ /**
54
+ * The 1-based `{ line, column }` of `offset` within this index's text.
55
+ * Out-of-range offsets clamp to the nearest valid position (`0` or
56
+ * `text.length`) rather than throwing.
57
+ */
58
+ positionAt(offset) {
59
+ const clamped = Math.min(Math.max(offset, 0), this.text.length);
60
+ let low = 0;
61
+ let high = this.lineStarts.length - 1;
62
+ while (low < high) {
63
+ const mid = low + high + 1 >> 1;
64
+ if ((this.lineStarts[mid] ?? 0) <= clamped) low = mid;
65
+ else high = mid - 1;
66
+ }
67
+ const lineStart = this.lineStarts[low] ?? 0;
68
+ return {
69
+ line: low + 1,
70
+ column: clamped - lineStart + 1
71
+ };
72
+ }
73
+ };
74
+
75
+ //#endregion
76
+ export { LineIndex };
@@ -0,0 +1,26 @@
1
+ //#region src/internal/patterns.ts
2
+ const stickyCache = /* @__PURE__ */ new WeakMap();
3
+ const globalCache = /* @__PURE__ */ new WeakMap();
4
+ const clone = (pattern, flag, dropCaret) => {
5
+ const source = dropCaret && pattern.source.startsWith("^") ? pattern.source.slice(1) : pattern.source;
6
+ return new RegExp(source, `${pattern.flags.replace(/[gy]/g, "")}${flag}`);
7
+ };
8
+ /** The sticky twin of `pattern`, anchored at `lastIndex`. */
9
+ const stickyOf = (pattern) => {
10
+ const cached = stickyCache.get(pattern);
11
+ if (cached !== void 0) return cached;
12
+ const made = clone(pattern, "y", true);
13
+ stickyCache.set(pattern, made);
14
+ return made;
15
+ };
16
+ /** The global twin of `pattern`, searching forward from `lastIndex`. */
17
+ const globalOf = (pattern) => {
18
+ const cached = globalCache.get(pattern);
19
+ if (cached !== void 0) return cached;
20
+ const made = clone(pattern, "g", false);
21
+ globalCache.set(pattern, made);
22
+ return made;
23
+ };
24
+
25
+ //#endregion
26
+ export { globalOf, stickyOf };
@@ -0,0 +1,58 @@
1
+ //#region src/internal/preprocess.ts
2
+ /** The tab stop width CommonMark fixes for indentation arithmetic. */
3
+ const TAB_STOP = 4;
4
+ /**
5
+ * Split `text` into lines, preserving each line's absolute start offset.
6
+ *
7
+ * `\r\n`, `\n` and `\r` all terminate a line. U+0000 is replaced with U+FFFD
8
+ * per the spec's security note; the replacement is length-preserving so
9
+ * offsets stay aligned with the original source.
10
+ *
11
+ * A single trailing `\n` does not produce a final empty line (upstream's
12
+ * rule — the document "foo\n" is one line, not two). A trailing bare `\r`
13
+ * does, which is also upstream's behavior: the extra blank line is inert.
14
+ */
15
+ const preprocessLines = (text) => {
16
+ const lines = [];
17
+ let start = 0;
18
+ let index = 0;
19
+ while (index < text.length) {
20
+ const code = text.charCodeAt(index);
21
+ if (code === 10) {
22
+ lines.push({
23
+ text: replaceNul(text.slice(start, index)),
24
+ start
25
+ });
26
+ index += 1;
27
+ start = index;
28
+ } else if (code === 13) {
29
+ lines.push({
30
+ text: replaceNul(text.slice(start, index)),
31
+ start
32
+ });
33
+ index += text.charCodeAt(index + 1) === 10 ? 2 : 1;
34
+ start = index;
35
+ } else index += 1;
36
+ }
37
+ if (start < text.length || !text.endsWith("\n")) lines.push({
38
+ text: replaceNul(text.slice(start)),
39
+ start
40
+ });
41
+ return lines;
42
+ };
43
+ /** The spec's security substitution: U+0000 becomes U+FFFD, 1:1 by length. */
44
+ const replaceNul = (line) => line.includes("\0") ? line.replaceAll("\0", "�") : line;
45
+ /** True for the two characters CommonMark treats as horizontal whitespace. */
46
+ const isSpaceOrTab = (code) => code === 32 || code === 9;
47
+ /** The char code at `position`, or `-1` past the end of `line`. */
48
+ const peekCode = (line, position) => position >= 0 && position < line.length ? line.charCodeAt(position) : -1;
49
+ /**
50
+ * Columns from `column` to the next tab stop — the width a `\t` expands to.
51
+ *
52
+ * `column` is the tab-expanded column the block pass tracks, not a character
53
+ * index: a tab at column 2 is two columns wide, at column 4 it is four.
54
+ */
55
+ const columnsToNextTabStop = (column) => 4 - column % 4;
56
+
57
+ //#endregion
58
+ export { columnsToNextTabStop, isSpaceOrTab, peekCode, preprocessLines, replaceNul };
@@ -0,0 +1,57 @@
1
+ import { parseInlines } from "./inlineParser.js";
2
+
3
+ //#region src/internal/rawInline.ts
4
+ const reWhitespace = /\s/;
5
+ const reCmarkSpace = /[ \t\n\r]/;
6
+ /**
7
+ * Trim `text` the way commonmark.js does before inline parsing, carrying the
8
+ * segment table along so the surviving characters keep their source offsets.
9
+ */
10
+ const trimWithSegments = (text, segments, whitespace) => {
11
+ let start = 0;
12
+ let end = text.length;
13
+ while (start < end && whitespace.test(text.charAt(start))) start += 1;
14
+ while (end > start && whitespace.test(text.charAt(end - 1))) end -= 1;
15
+ if (start === 0 && end === text.length) return {
16
+ text,
17
+ segments
18
+ };
19
+ const trimmed = [];
20
+ for (const segment of segments) {
21
+ const from = Math.max(segment.textOffset, start);
22
+ const to = Math.min(segment.textOffset + segment.length, end);
23
+ if (to > from) trimmed.push({
24
+ textOffset: from - start,
25
+ sourceOffset: segment.sourceOffset + (from - segment.textOffset),
26
+ length: to - from
27
+ });
28
+ }
29
+ return {
30
+ text: text.slice(start, end),
31
+ segments: trimmed
32
+ };
33
+ };
34
+ /**
35
+ * Prepare a leaf block's accumulated content and run the inline pass over it:
36
+ * trim it, keep its source provenance, and parse it into phrasing content.
37
+ */
38
+ const prepareInline = (block, position, refmap, dialect = "commonmark", footnoteLabels = /* @__PURE__ */ new Set()) => {
39
+ const { text, segments } = trimWithSegments(block.stringContent, block.segments, block.type === "tableCell" ? reCmarkSpace : reWhitespace);
40
+ const first = segments[0];
41
+ const last = segments[segments.length - 1];
42
+ const startOffset = first === void 0 ? block.startOffset : first.sourceOffset;
43
+ return {
44
+ text,
45
+ startOffset,
46
+ endOffset: last === void 0 ? startOffset : last.sourceOffset + last.length,
47
+ segments,
48
+ children: text.length === 0 ? [] : parseInlines({
49
+ text,
50
+ startOffset,
51
+ segments
52
+ }, refmap, position, dialect, footnoteLabels)
53
+ };
54
+ };
55
+
56
+ //#endregion
57
+ export { prepareInline };