@incremark/core 0.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.
- package/LICENSE +22 -0
- package/README.md +99 -0
- package/dist/detector/index.d.ts +4 -0
- package/dist/detector/index.js +155 -0
- package/dist/detector/index.js.map +1 -0
- package/dist/index-i_qABRHQ.d.ts +207 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +515 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.d.ts +22 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +59 -0
- package/src/detector/index.test.ts +150 -0
- package/src/detector/index.ts +271 -0
- package/src/index.ts +51 -0
- package/src/parser/IncremarkParser.comprehensive.test.ts +418 -0
- package/src/parser/IncremarkParser.robustness.test.ts +428 -0
- package/src/parser/IncremarkParser.test.ts +110 -0
- package/src/parser/IncremarkParser.ts +476 -0
- package/src/parser/index.ts +2 -0
- package/src/types/index.ts +144 -0
- package/src/utils/index.ts +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
2
|
+
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
3
|
+
import { gfm } from 'micromark-extension-gfm';
|
|
4
|
+
|
|
5
|
+
// src/parser/IncremarkParser.ts
|
|
6
|
+
|
|
7
|
+
// src/detector/index.ts
|
|
8
|
+
function detectFenceStart(line) {
|
|
9
|
+
const match = line.match(/^(\s*)((`{3,})|(~{3,}))/);
|
|
10
|
+
if (match) {
|
|
11
|
+
const fence = match[2];
|
|
12
|
+
const char = fence[0];
|
|
13
|
+
return { char, length: fence.length };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function detectFenceEnd(line, context) {
|
|
18
|
+
if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
|
|
22
|
+
return pattern.test(line);
|
|
23
|
+
}
|
|
24
|
+
function isEmptyLine(line) {
|
|
25
|
+
return /^\s*$/.test(line);
|
|
26
|
+
}
|
|
27
|
+
function isHeading(line) {
|
|
28
|
+
return /^#{1,6}\s/.test(line);
|
|
29
|
+
}
|
|
30
|
+
function isThematicBreak(line) {
|
|
31
|
+
return /^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim());
|
|
32
|
+
}
|
|
33
|
+
function isListItemStart(line) {
|
|
34
|
+
const unordered = line.match(/^(\s*)([-*+])\s/);
|
|
35
|
+
if (unordered) {
|
|
36
|
+
return { ordered: false, indent: unordered[1].length };
|
|
37
|
+
}
|
|
38
|
+
const ordered = line.match(/^(\s*)(\d{1,9})[.)]\s/);
|
|
39
|
+
if (ordered) {
|
|
40
|
+
return { ordered: true, indent: ordered[1].length };
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function isBlockquoteStart(line) {
|
|
45
|
+
return /^\s{0,3}>/.test(line);
|
|
46
|
+
}
|
|
47
|
+
function isHtmlBlock(line) {
|
|
48
|
+
return /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i.test(line) || /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/.test(line);
|
|
49
|
+
}
|
|
50
|
+
function isTableDelimiter(line) {
|
|
51
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/.test(line.trim());
|
|
52
|
+
}
|
|
53
|
+
function detectContainer(line, config) {
|
|
54
|
+
const marker = config?.marker || ":";
|
|
55
|
+
const minLength = config?.minMarkerLength || 3;
|
|
56
|
+
const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
57
|
+
const pattern = new RegExp(
|
|
58
|
+
`^(\\s*)(${escapedMarker}{${minLength},})(?:\\s+(\\w[\\w-]*))?(?:\\s+(.*))?\\s*$`
|
|
59
|
+
);
|
|
60
|
+
const match = line.match(pattern);
|
|
61
|
+
if (!match) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const markerLength = match[2].length;
|
|
65
|
+
const name = match[3] || "";
|
|
66
|
+
const isEnd = !name && !match[4];
|
|
67
|
+
if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
|
|
68
|
+
if (!config.allowedNames.includes(name)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { name, markerLength, isEnd };
|
|
73
|
+
}
|
|
74
|
+
function detectContainerEnd(line, context, config) {
|
|
75
|
+
if (!context.inContainer || !context.containerMarkerLength) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const result = detectContainer(line, config);
|
|
79
|
+
if (!result) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return result.isEnd && result.markerLength >= context.containerMarkerLength;
|
|
83
|
+
}
|
|
84
|
+
function isBlockBoundary(prevLine, currentLine, context) {
|
|
85
|
+
if (context.inFencedCode) {
|
|
86
|
+
return detectFenceEnd(currentLine, context);
|
|
87
|
+
}
|
|
88
|
+
if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (isThematicBreak(currentLine)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (detectFenceStart(currentLine)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function createInitialContext() {
|
|
103
|
+
return {
|
|
104
|
+
inFencedCode: false,
|
|
105
|
+
listDepth: 0,
|
|
106
|
+
blockquoteDepth: 0,
|
|
107
|
+
inContainer: false,
|
|
108
|
+
containerDepth: 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function updateContext(line, context, containerConfig) {
|
|
112
|
+
const newContext = { ...context };
|
|
113
|
+
const containerCfg = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
|
|
114
|
+
if (context.inFencedCode) {
|
|
115
|
+
if (detectFenceEnd(line, context)) {
|
|
116
|
+
newContext.inFencedCode = false;
|
|
117
|
+
newContext.fenceChar = void 0;
|
|
118
|
+
newContext.fenceLength = void 0;
|
|
119
|
+
}
|
|
120
|
+
return newContext;
|
|
121
|
+
}
|
|
122
|
+
const fence = detectFenceStart(line);
|
|
123
|
+
if (fence) {
|
|
124
|
+
newContext.inFencedCode = true;
|
|
125
|
+
newContext.fenceChar = fence.char;
|
|
126
|
+
newContext.fenceLength = fence.length;
|
|
127
|
+
return newContext;
|
|
128
|
+
}
|
|
129
|
+
if (containerCfg !== void 0) {
|
|
130
|
+
if (context.inContainer) {
|
|
131
|
+
if (detectContainerEnd(line, context, containerCfg)) {
|
|
132
|
+
newContext.containerDepth = context.containerDepth - 1;
|
|
133
|
+
if (newContext.containerDepth === 0) {
|
|
134
|
+
newContext.inContainer = false;
|
|
135
|
+
newContext.containerMarkerLength = void 0;
|
|
136
|
+
newContext.containerName = void 0;
|
|
137
|
+
}
|
|
138
|
+
return newContext;
|
|
139
|
+
}
|
|
140
|
+
const nested = detectContainer(line, containerCfg);
|
|
141
|
+
if (nested && !nested.isEnd) {
|
|
142
|
+
newContext.containerDepth = context.containerDepth + 1;
|
|
143
|
+
return newContext;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const container = detectContainer(line, containerCfg);
|
|
147
|
+
if (container && !container.isEnd) {
|
|
148
|
+
newContext.inContainer = true;
|
|
149
|
+
newContext.containerMarkerLength = container.markerLength;
|
|
150
|
+
newContext.containerName = container.name;
|
|
151
|
+
newContext.containerDepth = 1;
|
|
152
|
+
return newContext;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return newContext;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/parser/IncremarkParser.ts
|
|
160
|
+
var IncremarkParser = class {
|
|
161
|
+
buffer = "";
|
|
162
|
+
lines = [];
|
|
163
|
+
/** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
|
|
164
|
+
lineOffsets = [0];
|
|
165
|
+
completedBlocks = [];
|
|
166
|
+
pendingStartLine = 0;
|
|
167
|
+
blockIdCounter = 0;
|
|
168
|
+
context;
|
|
169
|
+
options;
|
|
170
|
+
/** 缓存的容器配置,避免重复计算 */
|
|
171
|
+
cachedContainerConfig = null;
|
|
172
|
+
/** 上次 append 返回的 pending blocks,用于 getAst 复用 */
|
|
173
|
+
lastPendingBlocks = [];
|
|
174
|
+
constructor(options = {}) {
|
|
175
|
+
this.options = {
|
|
176
|
+
gfm: true,
|
|
177
|
+
...options
|
|
178
|
+
};
|
|
179
|
+
this.context = createInitialContext();
|
|
180
|
+
this.cachedContainerConfig = this.computeContainerConfig();
|
|
181
|
+
}
|
|
182
|
+
generateBlockId() {
|
|
183
|
+
return `block-${++this.blockIdCounter}`;
|
|
184
|
+
}
|
|
185
|
+
computeContainerConfig() {
|
|
186
|
+
const containers = this.options.containers;
|
|
187
|
+
if (!containers) return void 0;
|
|
188
|
+
return containers === true ? {} : containers;
|
|
189
|
+
}
|
|
190
|
+
getContainerConfig() {
|
|
191
|
+
return this.cachedContainerConfig ?? void 0;
|
|
192
|
+
}
|
|
193
|
+
parse(text) {
|
|
194
|
+
const extensions = [];
|
|
195
|
+
const mdastExtensions = [];
|
|
196
|
+
if (this.options.gfm) {
|
|
197
|
+
extensions.push(gfm());
|
|
198
|
+
mdastExtensions.push(...gfmFromMarkdown());
|
|
199
|
+
}
|
|
200
|
+
if (this.options.extensions) {
|
|
201
|
+
extensions.push(...this.options.extensions);
|
|
202
|
+
}
|
|
203
|
+
if (this.options.mdastExtensions) {
|
|
204
|
+
mdastExtensions.push(...this.options.mdastExtensions);
|
|
205
|
+
}
|
|
206
|
+
return fromMarkdown(text, { extensions, mdastExtensions });
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 增量更新 lines 和 lineOffsets
|
|
210
|
+
* 只处理新增的内容,避免全量 split
|
|
211
|
+
*/
|
|
212
|
+
updateLines() {
|
|
213
|
+
const prevLineCount = this.lines.length;
|
|
214
|
+
if (prevLineCount === 0) {
|
|
215
|
+
this.lines = this.buffer.split("\n");
|
|
216
|
+
this.lineOffsets = [0];
|
|
217
|
+
for (let i = 0; i < this.lines.length; i++) {
|
|
218
|
+
this.lineOffsets.push(this.lineOffsets[i] + this.lines[i].length + 1);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const lastLineStart = this.lineOffsets[prevLineCount - 1];
|
|
223
|
+
const textFromLastLine = this.buffer.slice(lastLineStart);
|
|
224
|
+
const newLines = textFromLastLine.split("\n");
|
|
225
|
+
this.lines.length = prevLineCount - 1;
|
|
226
|
+
this.lineOffsets.length = prevLineCount;
|
|
227
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
228
|
+
this.lines.push(newLines[i]);
|
|
229
|
+
const prevOffset = this.lineOffsets[this.lineOffsets.length - 1];
|
|
230
|
+
this.lineOffsets.push(prevOffset + newLines[i].length + 1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* O(1) 获取行偏移量
|
|
235
|
+
*/
|
|
236
|
+
getLineOffset(lineIndex) {
|
|
237
|
+
return this.lineOffsets[lineIndex] ?? 0;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 查找稳定边界
|
|
241
|
+
* 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
|
|
242
|
+
*/
|
|
243
|
+
findStableBoundary() {
|
|
244
|
+
let stableLine = -1;
|
|
245
|
+
let stableContext = this.context;
|
|
246
|
+
let tempContext = { ...this.context };
|
|
247
|
+
const containerConfig = this.getContainerConfig();
|
|
248
|
+
for (let i = this.pendingStartLine; i < this.lines.length; i++) {
|
|
249
|
+
const line = this.lines[i];
|
|
250
|
+
const wasInFencedCode = tempContext.inFencedCode;
|
|
251
|
+
const wasInContainer = tempContext.inContainer;
|
|
252
|
+
const wasContainerDepth = tempContext.containerDepth;
|
|
253
|
+
tempContext = updateContext(line, tempContext, containerConfig);
|
|
254
|
+
if (wasInFencedCode && !tempContext.inFencedCode) {
|
|
255
|
+
if (i < this.lines.length - 1) {
|
|
256
|
+
stableLine = i;
|
|
257
|
+
stableContext = { ...tempContext };
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (tempContext.inFencedCode) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
|
|
265
|
+
if (i < this.lines.length - 1) {
|
|
266
|
+
stableLine = i;
|
|
267
|
+
stableContext = { ...tempContext };
|
|
268
|
+
}
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (tempContext.inContainer) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const stablePoint = this.checkStability(i, containerConfig);
|
|
275
|
+
if (stablePoint >= 0) {
|
|
276
|
+
stableLine = stablePoint;
|
|
277
|
+
stableContext = { ...tempContext };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { line: stableLine, contextAtLine: stableContext };
|
|
281
|
+
}
|
|
282
|
+
checkStability(lineIndex, containerConfig) {
|
|
283
|
+
if (lineIndex === 0) {
|
|
284
|
+
return -1;
|
|
285
|
+
}
|
|
286
|
+
const line = this.lines[lineIndex];
|
|
287
|
+
const prevLine = this.lines[lineIndex - 1];
|
|
288
|
+
if (isHeading(prevLine) || isThematicBreak(prevLine)) {
|
|
289
|
+
return lineIndex - 1;
|
|
290
|
+
}
|
|
291
|
+
if (lineIndex >= this.lines.length - 1) {
|
|
292
|
+
return -1;
|
|
293
|
+
}
|
|
294
|
+
if (!isEmptyLine(prevLine)) {
|
|
295
|
+
if (isHeading(line)) {
|
|
296
|
+
return lineIndex - 1;
|
|
297
|
+
}
|
|
298
|
+
if (detectFenceStart(line)) {
|
|
299
|
+
return lineIndex - 1;
|
|
300
|
+
}
|
|
301
|
+
if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
|
|
302
|
+
return lineIndex - 1;
|
|
303
|
+
}
|
|
304
|
+
if (isListItemStart(line) && !isListItemStart(prevLine)) {
|
|
305
|
+
return lineIndex - 1;
|
|
306
|
+
}
|
|
307
|
+
if (containerConfig !== void 0) {
|
|
308
|
+
const container = detectContainer(line, containerConfig);
|
|
309
|
+
if (container && !container.isEnd) {
|
|
310
|
+
const prevContainer = detectContainer(prevLine, containerConfig);
|
|
311
|
+
if (!prevContainer || prevContainer.isEnd) {
|
|
312
|
+
return lineIndex - 1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (isEmptyLine(line) && !isEmptyLine(prevLine)) {
|
|
318
|
+
return lineIndex;
|
|
319
|
+
}
|
|
320
|
+
return -1;
|
|
321
|
+
}
|
|
322
|
+
nodesToBlocks(nodes, startOffset, rawText, status) {
|
|
323
|
+
const blocks = [];
|
|
324
|
+
let currentOffset = startOffset;
|
|
325
|
+
for (const node of nodes) {
|
|
326
|
+
const nodeStart = node.position?.start?.offset ?? currentOffset;
|
|
327
|
+
const nodeEnd = node.position?.end?.offset ?? currentOffset + 1;
|
|
328
|
+
const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset);
|
|
329
|
+
blocks.push({
|
|
330
|
+
id: this.generateBlockId(),
|
|
331
|
+
status,
|
|
332
|
+
node,
|
|
333
|
+
startOffset: nodeStart,
|
|
334
|
+
endOffset: nodeEnd,
|
|
335
|
+
rawText: nodeText
|
|
336
|
+
});
|
|
337
|
+
currentOffset = nodeEnd;
|
|
338
|
+
}
|
|
339
|
+
return blocks;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 追加新的 chunk 并返回增量更新
|
|
343
|
+
*/
|
|
344
|
+
append(chunk) {
|
|
345
|
+
this.buffer += chunk;
|
|
346
|
+
this.updateLines();
|
|
347
|
+
const { line: stableBoundary, contextAtLine } = this.findStableBoundary();
|
|
348
|
+
const update = {
|
|
349
|
+
completed: [],
|
|
350
|
+
updated: [],
|
|
351
|
+
pending: [],
|
|
352
|
+
ast: { type: "root", children: [] }
|
|
353
|
+
};
|
|
354
|
+
if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
|
|
355
|
+
const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join("\n");
|
|
356
|
+
const stableOffset = this.getLineOffset(this.pendingStartLine);
|
|
357
|
+
const ast = this.parse(stableText);
|
|
358
|
+
const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
|
|
359
|
+
this.completedBlocks.push(...newBlocks);
|
|
360
|
+
update.completed = newBlocks;
|
|
361
|
+
this.context = contextAtLine;
|
|
362
|
+
this.pendingStartLine = stableBoundary + 1;
|
|
363
|
+
}
|
|
364
|
+
if (this.pendingStartLine < this.lines.length) {
|
|
365
|
+
const pendingText = this.lines.slice(this.pendingStartLine).join("\n");
|
|
366
|
+
if (pendingText.trim()) {
|
|
367
|
+
const pendingOffset = this.getLineOffset(this.pendingStartLine);
|
|
368
|
+
const ast = this.parse(pendingText);
|
|
369
|
+
update.pending = this.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
this.lastPendingBlocks = update.pending;
|
|
373
|
+
update.ast = {
|
|
374
|
+
type: "root",
|
|
375
|
+
children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]
|
|
376
|
+
};
|
|
377
|
+
this.emitChange(update.pending);
|
|
378
|
+
return update;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 触发状态变化回调
|
|
382
|
+
*/
|
|
383
|
+
emitChange(pendingBlocks = []) {
|
|
384
|
+
if (this.options.onChange) {
|
|
385
|
+
this.options.onChange({
|
|
386
|
+
completedBlocks: this.completedBlocks,
|
|
387
|
+
pendingBlocks,
|
|
388
|
+
markdown: this.buffer,
|
|
389
|
+
ast: {
|
|
390
|
+
type: "root",
|
|
391
|
+
children: [
|
|
392
|
+
...this.completedBlocks.map((b) => b.node),
|
|
393
|
+
...pendingBlocks.map((b) => b.node)
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 标记解析完成,处理剩余内容
|
|
401
|
+
* 也可用于强制中断时(如用户点击停止),将 pending 内容标记为 completed
|
|
402
|
+
*/
|
|
403
|
+
finalize() {
|
|
404
|
+
const update = {
|
|
405
|
+
completed: [],
|
|
406
|
+
updated: [],
|
|
407
|
+
pending: [],
|
|
408
|
+
ast: { type: "root", children: [] }
|
|
409
|
+
};
|
|
410
|
+
if (this.pendingStartLine < this.lines.length) {
|
|
411
|
+
const remainingText = this.lines.slice(this.pendingStartLine).join("\n");
|
|
412
|
+
if (remainingText.trim()) {
|
|
413
|
+
const remainingOffset = this.getLineOffset(this.pendingStartLine);
|
|
414
|
+
const ast = this.parse(remainingText);
|
|
415
|
+
const finalBlocks = this.nodesToBlocks(
|
|
416
|
+
ast.children,
|
|
417
|
+
remainingOffset,
|
|
418
|
+
remainingText,
|
|
419
|
+
"completed"
|
|
420
|
+
);
|
|
421
|
+
this.completedBlocks.push(...finalBlocks);
|
|
422
|
+
update.completed = finalBlocks;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
this.lastPendingBlocks = [];
|
|
426
|
+
this.pendingStartLine = this.lines.length;
|
|
427
|
+
update.ast = {
|
|
428
|
+
type: "root",
|
|
429
|
+
children: this.completedBlocks.map((b) => b.node)
|
|
430
|
+
};
|
|
431
|
+
this.emitChange([]);
|
|
432
|
+
return update;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* 强制中断解析,将所有待处理内容标记为完成
|
|
436
|
+
* 语义上等同于 finalize(),但名称更清晰
|
|
437
|
+
*/
|
|
438
|
+
abort() {
|
|
439
|
+
return this.finalize();
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 获取当前完整的 AST
|
|
443
|
+
* 复用上次 append 的 pending 结果,避免重复解析
|
|
444
|
+
*/
|
|
445
|
+
getAst() {
|
|
446
|
+
return {
|
|
447
|
+
type: "root",
|
|
448
|
+
children: [
|
|
449
|
+
...this.completedBlocks.map((b) => b.node),
|
|
450
|
+
...this.lastPendingBlocks.map((b) => b.node)
|
|
451
|
+
]
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* 获取所有已完成的块
|
|
456
|
+
*/
|
|
457
|
+
getCompletedBlocks() {
|
|
458
|
+
return [...this.completedBlocks];
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 获取当前缓冲区内容
|
|
462
|
+
*/
|
|
463
|
+
getBuffer() {
|
|
464
|
+
return this.buffer;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* 设置状态变化回调(用于 DevTools 等)
|
|
468
|
+
*/
|
|
469
|
+
setOnChange(callback) {
|
|
470
|
+
this.options.onChange = callback;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 重置解析器状态
|
|
474
|
+
*/
|
|
475
|
+
reset() {
|
|
476
|
+
this.buffer = "";
|
|
477
|
+
this.lines = [];
|
|
478
|
+
this.lineOffsets = [0];
|
|
479
|
+
this.completedBlocks = [];
|
|
480
|
+
this.pendingStartLine = 0;
|
|
481
|
+
this.blockIdCounter = 0;
|
|
482
|
+
this.context = createInitialContext();
|
|
483
|
+
this.lastPendingBlocks = [];
|
|
484
|
+
this.emitChange([]);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
function createIncremarkParser(options) {
|
|
488
|
+
return new IncremarkParser(options);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/utils/index.ts
|
|
492
|
+
var idCounter = 0;
|
|
493
|
+
function generateId(prefix = "block") {
|
|
494
|
+
return `${prefix}-${++idCounter}`;
|
|
495
|
+
}
|
|
496
|
+
function resetIdCounter() {
|
|
497
|
+
idCounter = 0;
|
|
498
|
+
}
|
|
499
|
+
function calculateLineOffset(lines, lineIndex) {
|
|
500
|
+
let offset = 0;
|
|
501
|
+
for (let i = 0; i < lineIndex && i < lines.length; i++) {
|
|
502
|
+
offset += lines[i].length + 1;
|
|
503
|
+
}
|
|
504
|
+
return offset;
|
|
505
|
+
}
|
|
506
|
+
function splitLines(text) {
|
|
507
|
+
return text.split("\n");
|
|
508
|
+
}
|
|
509
|
+
function joinLines(lines, start, end) {
|
|
510
|
+
return lines.slice(start, end + 1).join("\n");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export { IncremarkParser, calculateLineOffset, createIncremarkParser, createInitialContext, detectContainer, detectContainerEnd, detectFenceEnd, detectFenceStart, generateId, isBlockBoundary, isBlockquoteStart, isEmptyLine, isHeading, isHtmlBlock, isListItemStart, isTableDelimiter, isThematicBreak, joinLines, resetIdCounter, splitLines, updateContext };
|
|
514
|
+
//# sourceMappingURL=index.js.map
|
|
515
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/detector/index.ts","../src/parser/IncremarkParser.ts","../src/utils/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * 块类型检测与边界判断\n *\n * Markdown 块级元素的识别规则\n */\n\nimport type { BlockContext, ContainerConfig, ContainerMatch } from '../types'\n\n// ============ 代码块检测 ============\n\n/**\n * 检测行是否是代码块 fence 开始\n */\nexport function detectFenceStart(line: string): { char: string; length: number } | null {\n const match = line.match(/^(\\s*)((`{3,})|(~{3,}))/)\n if (match) {\n const fence = match[2]\n const char = fence[0]\n return { char, length: fence.length }\n }\n return null\n}\n\n/**\n * 检测行是否是代码块 fence 结束\n */\nexport function detectFenceEnd(line: string, context: BlockContext): boolean {\n if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {\n return false\n }\n\n const pattern = new RegExp(`^\\\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\\\s*$`)\n return pattern.test(line)\n}\n\n// ============ 行类型检测 ============\n\n/**\n * 检测是否是空行或仅包含空白字符\n */\nexport function isEmptyLine(line: string): boolean {\n return /^\\s*$/.test(line)\n}\n\n/**\n * 检测是否是标题行\n */\nexport function isHeading(line: string): boolean {\n return /^#{1,6}\\s/.test(line)\n}\n\n/**\n * 检测是否是 thematic break(水平线)\n */\nexport function isThematicBreak(line: string): boolean {\n return /^(\\*{3,}|-{3,}|_{3,})\\s*$/.test(line.trim())\n}\n\n/**\n * 检测是否是列表项开始\n */\nexport function isListItemStart(line: string): { ordered: boolean; indent: number } | null {\n // 无序列表: - * +\n const unordered = line.match(/^(\\s*)([-*+])\\s/)\n if (unordered) {\n return { ordered: false, indent: unordered[1].length }\n }\n\n // 有序列表: 1. 2) 等\n const ordered = line.match(/^(\\s*)(\\d{1,9})[.)]\\s/)\n if (ordered) {\n return { ordered: true, indent: ordered[1].length }\n }\n\n return null\n}\n\n/**\n * 检测是否是引用块开始\n */\nexport function isBlockquoteStart(line: string): boolean {\n return /^\\s{0,3}>/.test(line)\n}\n\n/**\n * 检测是否是 HTML 块\n */\nexport function isHtmlBlock(line: string): boolean {\n return (\n /^\\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\\?|!\\[CDATA\\[)/i.test(line) ||\n /^\\s{0,3}<\\/?[a-zA-Z][a-zA-Z0-9-]*(\\s|>|$)/.test(line)\n )\n}\n\n/**\n * 检测表格分隔行\n */\nexport function isTableDelimiter(line: string): boolean {\n return /^\\|?\\s*:?-{3,}:?\\s*(\\|\\s*:?-{3,}:?\\s*)*\\|?$/.test(line.trim())\n}\n\n// ============ 容器检测 ============\n\n/**\n * 检测容器开始或结束\n *\n * 支持格式:\n * - ::: name 开始\n * - ::: name attr 开始(带属性)\n * - ::: 结束\n * - :::::: name 开始(更长的标记,用于嵌套)\n */\nexport function detectContainer(line: string, config?: ContainerConfig): ContainerMatch | null {\n const marker = config?.marker || ':'\n const minLength = config?.minMarkerLength || 3\n\n const escapedMarker = marker.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const pattern = new RegExp(\n `^(\\\\s*)(${escapedMarker}{${minLength},})(?:\\\\s+(\\\\w[\\\\w-]*))?(?:\\\\s+(.*))?\\\\s*$`\n )\n\n const match = line.match(pattern)\n if (!match) {\n return null\n }\n\n const markerLength = match[2].length\n const name = match[3] || ''\n const isEnd = !name && !match[4]\n\n if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {\n if (!config.allowedNames.includes(name)) {\n return null\n }\n }\n\n return { name, markerLength, isEnd }\n}\n\n/**\n * 检测容器结束\n */\nexport function detectContainerEnd(\n line: string,\n context: BlockContext,\n config?: ContainerConfig\n): boolean {\n if (!context.inContainer || !context.containerMarkerLength) {\n return false\n }\n\n const result = detectContainer(line, config)\n if (!result) {\n return false\n }\n\n return result.isEnd && result.markerLength >= context.containerMarkerLength\n}\n\n// ============ 边界检测 ============\n\n/**\n * 判断两行之间是否构成块边界\n */\nexport function isBlockBoundary(\n prevLine: string,\n currentLine: string,\n context: BlockContext\n): boolean {\n if (context.inFencedCode) {\n return detectFenceEnd(currentLine, context)\n }\n\n if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {\n return true\n }\n\n if (isHeading(currentLine) && !isEmptyLine(prevLine)) {\n return true\n }\n\n if (isThematicBreak(currentLine)) {\n return true\n }\n\n if (detectFenceStart(currentLine)) {\n return true\n }\n\n return false\n}\n\n// ============ 上下文管理 ============\n\n/**\n * 创建初始上下文\n */\nexport function createInitialContext(): BlockContext {\n return {\n inFencedCode: false,\n listDepth: 0,\n blockquoteDepth: 0,\n inContainer: false,\n containerDepth: 0\n }\n}\n\n/**\n * 更新上下文(处理一行后)\n */\nexport function updateContext(\n line: string,\n context: BlockContext,\n containerConfig?: ContainerConfig | boolean\n): BlockContext {\n const newContext = { ...context }\n\n const containerCfg =\n containerConfig === true ? {} : containerConfig === false ? undefined : containerConfig\n\n // 代码块优先级最高\n if (context.inFencedCode) {\n if (detectFenceEnd(line, context)) {\n newContext.inFencedCode = false\n newContext.fenceChar = undefined\n newContext.fenceLength = undefined\n }\n return newContext\n }\n\n const fence = detectFenceStart(line)\n if (fence) {\n newContext.inFencedCode = true\n newContext.fenceChar = fence.char\n newContext.fenceLength = fence.length\n return newContext\n }\n\n // 容器处理\n if (containerCfg !== undefined) {\n if (context.inContainer) {\n if (detectContainerEnd(line, context, containerCfg)) {\n newContext.containerDepth = context.containerDepth - 1\n if (newContext.containerDepth === 0) {\n newContext.inContainer = false\n newContext.containerMarkerLength = undefined\n newContext.containerName = undefined\n }\n return newContext\n }\n\n const nested = detectContainer(line, containerCfg)\n if (nested && !nested.isEnd) {\n newContext.containerDepth = context.containerDepth + 1\n return newContext\n }\n } else {\n const container = detectContainer(line, containerCfg)\n if (container && !container.isEnd) {\n newContext.inContainer = true\n newContext.containerMarkerLength = container.markerLength\n newContext.containerName = container.name\n newContext.containerDepth = 1\n return newContext\n }\n }\n }\n\n return newContext\n}\n\n","/**\n * 增量 Markdown 解析器\n *\n * 设计思路:\n * 1. 维护一个文本缓冲区,接收流式输入\n * 2. 识别\"稳定边界\"(如空行、标题等),将已完成的块标记为 completed\n * 3. 对于正在接收的块,每次重新解析,但只解析该块的内容\n * 4. 复杂嵌套节点(如列表、引用)作为整体处理,直到确认完成\n */\n\nimport { fromMarkdown } from 'mdast-util-from-markdown'\nimport { gfmFromMarkdown } from 'mdast-util-gfm'\nimport { gfm } from 'micromark-extension-gfm'\nimport type { Extension as MicromarkExtension } from 'micromark-util-types'\nimport type { Extension as MdastExtension } from 'mdast-util-from-markdown'\n\nimport type {\n Root,\n RootContent,\n ParsedBlock,\n IncrementalUpdate,\n ParserOptions,\n BlockStatus,\n BlockContext,\n ContainerConfig\n} from '../types'\n\nimport {\n createInitialContext,\n updateContext,\n isEmptyLine,\n detectFenceStart,\n isHeading,\n isThematicBreak,\n isBlockquoteStart,\n isListItemStart,\n detectContainer\n} from '../detector'\n\n// ============ 解析器类 ============\n\nexport class IncremarkParser {\n private buffer = ''\n private lines: string[] = []\n /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */\n private lineOffsets: number[] = [0]\n private completedBlocks: ParsedBlock[] = []\n private pendingStartLine = 0\n private blockIdCounter = 0\n private context: BlockContext\n private options: ParserOptions\n /** 缓存的容器配置,避免重复计算 */\n private cachedContainerConfig: ContainerConfig | undefined | null = null\n /** 上次 append 返回的 pending blocks,用于 getAst 复用 */\n private lastPendingBlocks: ParsedBlock[] = []\n\n constructor(options: ParserOptions = {}) {\n this.options = {\n gfm: true,\n ...options\n }\n this.context = createInitialContext()\n // 初始化容器配置缓存\n this.cachedContainerConfig = this.computeContainerConfig()\n }\n\n private generateBlockId(): string {\n return `block-${++this.blockIdCounter}`\n }\n\n private computeContainerConfig(): ContainerConfig | undefined {\n const containers = this.options.containers\n if (!containers) return undefined\n return containers === true ? {} : containers\n }\n\n private getContainerConfig(): ContainerConfig | undefined {\n return this.cachedContainerConfig ?? undefined\n }\n\n private parse(text: string): Root {\n const extensions: MicromarkExtension[] = []\n const mdastExtensions: MdastExtension[] = []\n\n if (this.options.gfm) {\n extensions.push(gfm())\n mdastExtensions.push(...gfmFromMarkdown())\n }\n\n // 如果用户传入了自定义扩展,添加它们\n if (this.options.extensions) {\n extensions.push(...this.options.extensions)\n }\n if (this.options.mdastExtensions) {\n mdastExtensions.push(...this.options.mdastExtensions)\n }\n\n return fromMarkdown(text, { extensions, mdastExtensions })\n }\n\n /**\n * 增量更新 lines 和 lineOffsets\n * 只处理新增的内容,避免全量 split\n */\n private updateLines(): void {\n const prevLineCount = this.lines.length\n\n if (prevLineCount === 0) {\n // 首次输入,直接 split\n this.lines = this.buffer.split('\\n')\n this.lineOffsets = [0]\n for (let i = 0; i < this.lines.length; i++) {\n this.lineOffsets.push(this.lineOffsets[i] + this.lines[i].length + 1)\n }\n return\n }\n\n // 找到最后一个不完整的行(可能被新 chunk 续上)\n const lastLineStart = this.lineOffsets[prevLineCount - 1]\n const textFromLastLine = this.buffer.slice(lastLineStart)\n\n // 重新 split 最后一行及之后的内容\n const newLines = textFromLastLine.split('\\n')\n\n // 替换最后一行并追加新行\n this.lines.length = prevLineCount - 1\n this.lineOffsets.length = prevLineCount\n\n for (let i = 0; i < newLines.length; i++) {\n this.lines.push(newLines[i])\n const prevOffset = this.lineOffsets[this.lineOffsets.length - 1]\n this.lineOffsets.push(prevOffset + newLines[i].length + 1)\n }\n }\n\n /**\n * O(1) 获取行偏移量\n */\n private getLineOffset(lineIndex: number): number {\n return this.lineOffsets[lineIndex] ?? 0\n }\n\n /**\n * 查找稳定边界\n * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)\n */\n private findStableBoundary(): { line: number; contextAtLine: BlockContext } {\n let stableLine = -1\n let stableContext: BlockContext = this.context\n let tempContext = { ...this.context }\n const containerConfig = this.getContainerConfig()\n\n for (let i = this.pendingStartLine; i < this.lines.length; i++) {\n const line = this.lines[i]\n const wasInFencedCode = tempContext.inFencedCode\n const wasInContainer = tempContext.inContainer\n const wasContainerDepth = tempContext.containerDepth\n\n tempContext = updateContext(line, tempContext, containerConfig)\n\n if (wasInFencedCode && !tempContext.inFencedCode) {\n if (i < this.lines.length - 1) {\n stableLine = i\n stableContext = { ...tempContext }\n }\n continue\n }\n\n if (tempContext.inFencedCode) {\n continue\n }\n\n if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {\n if (i < this.lines.length - 1) {\n stableLine = i\n stableContext = { ...tempContext }\n }\n continue\n }\n\n if (tempContext.inContainer) {\n continue\n }\n\n const stablePoint = this.checkStability(i, containerConfig)\n if (stablePoint >= 0) {\n stableLine = stablePoint\n stableContext = { ...tempContext }\n }\n }\n\n return { line: stableLine, contextAtLine: stableContext }\n }\n\n private checkStability(\n lineIndex: number,\n containerConfig: ContainerConfig | undefined\n ): number {\n // 第一行永远不稳定\n if (lineIndex === 0) {\n return -1\n }\n\n const line = this.lines[lineIndex]\n const prevLine = this.lines[lineIndex - 1]\n\n // 前一行是独立块(标题、分割线),该块已完成\n if (isHeading(prevLine) || isThematicBreak(prevLine)) {\n return lineIndex - 1\n }\n\n // 最后一行不稳定(可能还有更多内容)\n if (lineIndex >= this.lines.length - 1) {\n return -1\n }\n\n // 前一行非空时,如果当前行是新块开始,则前一块已完成\n if (!isEmptyLine(prevLine)) {\n // 新标题开始\n if (isHeading(line)) {\n return lineIndex - 1\n }\n\n // 新代码块开始\n if (detectFenceStart(line)) {\n return lineIndex - 1\n }\n\n // 新引用块开始(排除连续引用)\n if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {\n return lineIndex - 1\n }\n\n // 新列表开始(排除连续列表项)\n if (isListItemStart(line) && !isListItemStart(prevLine)) {\n return lineIndex - 1\n }\n\n // 新容器开始\n if (containerConfig !== undefined) {\n const container = detectContainer(line, containerConfig)\n if (container && !container.isEnd) {\n const prevContainer = detectContainer(prevLine, containerConfig)\n if (!prevContainer || prevContainer.isEnd) {\n return lineIndex - 1\n }\n }\n }\n }\n\n // 空行标志段落结束\n if (isEmptyLine(line) && !isEmptyLine(prevLine)) {\n return lineIndex\n }\n\n return -1\n }\n\n private nodesToBlocks(\n nodes: RootContent[],\n startOffset: number,\n rawText: string,\n status: BlockStatus\n ): ParsedBlock[] {\n const blocks: ParsedBlock[] = []\n let currentOffset = startOffset\n\n for (const node of nodes) {\n const nodeStart = node.position?.start?.offset ?? currentOffset\n const nodeEnd = node.position?.end?.offset ?? currentOffset + 1\n const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset)\n\n blocks.push({\n id: this.generateBlockId(),\n status,\n node,\n startOffset: nodeStart,\n endOffset: nodeEnd,\n rawText: nodeText\n })\n\n currentOffset = nodeEnd\n }\n\n return blocks\n }\n\n /**\n * 追加新的 chunk 并返回增量更新\n */\n append(chunk: string): IncrementalUpdate {\n this.buffer += chunk\n this.updateLines()\n\n const { line: stableBoundary, contextAtLine } = this.findStableBoundary()\n\n const update: IncrementalUpdate = {\n completed: [],\n updated: [],\n pending: [],\n ast: { type: 'root', children: [] }\n }\n\n if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {\n const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join('\\n')\n const stableOffset = this.getLineOffset(this.pendingStartLine)\n\n const ast = this.parse(stableText)\n const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, 'completed')\n\n this.completedBlocks.push(...newBlocks)\n update.completed = newBlocks\n\n // 直接使用 findStableBoundary 计算好的上下文,避免重复遍历\n this.context = contextAtLine\n this.pendingStartLine = stableBoundary + 1\n }\n\n if (this.pendingStartLine < this.lines.length) {\n const pendingText = this.lines.slice(this.pendingStartLine).join('\\n')\n\n if (pendingText.trim()) {\n const pendingOffset = this.getLineOffset(this.pendingStartLine)\n const ast = this.parse(pendingText)\n\n update.pending = this.nodesToBlocks(ast.children, pendingOffset, pendingText, 'pending')\n }\n }\n\n // 缓存 pending blocks 供 getAst 使用\n this.lastPendingBlocks = update.pending\n\n update.ast = {\n type: 'root',\n children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]\n }\n\n // 触发状态变化回调\n this.emitChange(update.pending)\n\n return update\n }\n\n /**\n * 触发状态变化回调\n */\n private emitChange(pendingBlocks: ParsedBlock[] = []): void {\n if (this.options.onChange) {\n this.options.onChange({\n completedBlocks: this.completedBlocks,\n pendingBlocks,\n markdown: this.buffer,\n ast: {\n type: 'root',\n children: [\n ...this.completedBlocks.map((b) => b.node),\n ...pendingBlocks.map((b) => b.node)\n ]\n }\n })\n }\n }\n\n /**\n * 标记解析完成,处理剩余内容\n * 也可用于强制中断时(如用户点击停止),将 pending 内容标记为 completed\n */\n finalize(): IncrementalUpdate {\n const update: IncrementalUpdate = {\n completed: [],\n updated: [],\n pending: [],\n ast: { type: 'root', children: [] }\n }\n\n if (this.pendingStartLine < this.lines.length) {\n const remainingText = this.lines.slice(this.pendingStartLine).join('\\n')\n\n if (remainingText.trim()) {\n const remainingOffset = this.getLineOffset(this.pendingStartLine)\n const ast = this.parse(remainingText)\n\n const finalBlocks = this.nodesToBlocks(\n ast.children,\n remainingOffset,\n remainingText,\n 'completed'\n )\n\n this.completedBlocks.push(...finalBlocks)\n update.completed = finalBlocks\n }\n }\n\n // 清空 pending 缓存\n this.lastPendingBlocks = []\n this.pendingStartLine = this.lines.length\n\n update.ast = {\n type: 'root',\n children: this.completedBlocks.map((b) => b.node)\n }\n\n // 触发状态变化回调\n this.emitChange([])\n\n return update\n }\n\n /**\n * 强制中断解析,将所有待处理内容标记为完成\n * 语义上等同于 finalize(),但名称更清晰\n */\n abort(): IncrementalUpdate {\n return this.finalize()\n }\n\n /**\n * 获取当前完整的 AST\n * 复用上次 append 的 pending 结果,避免重复解析\n */\n getAst(): Root {\n return {\n type: 'root',\n children: [\n ...this.completedBlocks.map((b) => b.node),\n ...this.lastPendingBlocks.map((b) => b.node)\n ]\n }\n }\n\n /**\n * 获取所有已完成的块\n */\n getCompletedBlocks(): ParsedBlock[] {\n return [...this.completedBlocks]\n }\n\n /**\n * 获取当前缓冲区内容\n */\n getBuffer(): string {\n return this.buffer\n }\n\n /**\n * 设置状态变化回调(用于 DevTools 等)\n */\n setOnChange(callback: ((state: import('../types').ParserState) => void) | undefined): void {\n this.options.onChange = callback\n }\n\n /**\n * 重置解析器状态\n */\n reset(): void {\n this.buffer = ''\n this.lines = []\n this.lineOffsets = [0]\n this.completedBlocks = []\n this.pendingStartLine = 0\n this.blockIdCounter = 0\n this.context = createInitialContext()\n this.lastPendingBlocks = []\n\n // 触发状态变化回调\n this.emitChange([])\n }\n}\n\n/**\n * 创建 Incremark 解析器实例\n */\nexport function createIncremarkParser(options?: ParserOptions): IncremarkParser {\n return new IncremarkParser(options)\n}\n","/**\n * 工具函数\n */\n\n/**\n * 生成唯一 ID\n */\nlet idCounter = 0\nexport function generateId(prefix = 'block'): string {\n return `${prefix}-${++idCounter}`\n}\n\n/**\n * 重置 ID 计数器(用于测试)\n */\nexport function resetIdCounter(): void {\n idCounter = 0\n}\n\n/**\n * 计算行的偏移量\n */\nexport function calculateLineOffset(lines: string[], lineIndex: number): number {\n let offset = 0\n for (let i = 0; i < lineIndex && i < lines.length; i++) {\n offset += lines[i].length + 1 // +1 for newline\n }\n return offset\n}\n\n/**\n * 将文本按行分割\n */\nexport function splitLines(text: string): string[] {\n return text.split('\\n')\n}\n\n/**\n * 合并行为文本\n */\nexport function joinLines(lines: string[], start: number, end: number): string {\n return lines.slice(start, end + 1).join('\\n')\n}\n\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具函数
|
|
3
|
+
*/
|
|
4
|
+
declare function generateId(prefix?: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* 重置 ID 计数器(用于测试)
|
|
7
|
+
*/
|
|
8
|
+
declare function resetIdCounter(): void;
|
|
9
|
+
/**
|
|
10
|
+
* 计算行的偏移量
|
|
11
|
+
*/
|
|
12
|
+
declare function calculateLineOffset(lines: string[], lineIndex: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* 将文本按行分割
|
|
15
|
+
*/
|
|
16
|
+
declare function splitLines(text: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* 合并行为文本
|
|
19
|
+
*/
|
|
20
|
+
declare function joinLines(lines: string[], start: number, end: number): string;
|
|
21
|
+
|
|
22
|
+
export { calculateLineOffset, generateId, joinLines, resetIdCounter, splitLines };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/utils/index.ts
|
|
2
|
+
var idCounter = 0;
|
|
3
|
+
function generateId(prefix = "block") {
|
|
4
|
+
return `${prefix}-${++idCounter}`;
|
|
5
|
+
}
|
|
6
|
+
function resetIdCounter() {
|
|
7
|
+
idCounter = 0;
|
|
8
|
+
}
|
|
9
|
+
function calculateLineOffset(lines, lineIndex) {
|
|
10
|
+
let offset = 0;
|
|
11
|
+
for (let i = 0; i < lineIndex && i < lines.length; i++) {
|
|
12
|
+
offset += lines[i].length + 1;
|
|
13
|
+
}
|
|
14
|
+
return offset;
|
|
15
|
+
}
|
|
16
|
+
function splitLines(text) {
|
|
17
|
+
return text.split("\n");
|
|
18
|
+
}
|
|
19
|
+
function joinLines(lines, start, end) {
|
|
20
|
+
return lines.slice(start, end + 1).join("\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { calculateLineOffset, generateId, joinLines, resetIdCounter, splitLines };
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"names":[],"mappings":";AAOA,IAAI,SAAA,GAAY,CAAA;AACT,SAAS,UAAA,CAAW,SAAS,OAAA,EAAiB;AACnD,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,EAAE,SAAS,CAAA,CAAA;AACjC;AAKO,SAAS,cAAA,GAAuB;AACrC,EAAA,SAAA,GAAY,CAAA;AACd;AAKO,SAAS,mBAAA,CAAoB,OAAiB,SAAA,EAA2B;AAC9E,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,aAAa,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACtD,IAAA,MAAA,IAAU,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA,GAAS,CAAA;AAAA,EAC9B;AACA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,WAAW,IAAA,EAAwB;AACjD,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB;AAKO,SAAS,SAAA,CAAU,KAAA,EAAiB,KAAA,EAAe,GAAA,EAAqB;AAC7E,EAAA,OAAO,MAAM,KAAA,CAAM,KAAA,EAAO,MAAM,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAC9C","file":"index.js","sourcesContent":["/**\n * 工具函数\n */\n\n/**\n * 生成唯一 ID\n */\nlet idCounter = 0\nexport function generateId(prefix = 'block'): string {\n return `${prefix}-${++idCounter}`\n}\n\n/**\n * 重置 ID 计数器(用于测试)\n */\nexport function resetIdCounter(): void {\n idCounter = 0\n}\n\n/**\n * 计算行的偏移量\n */\nexport function calculateLineOffset(lines: string[], lineIndex: number): number {\n let offset = 0\n for (let i = 0; i < lineIndex && i < lines.length; i++) {\n offset += lines[i].length + 1 // +1 for newline\n }\n return offset\n}\n\n/**\n * 将文本按行分割\n */\nexport function splitLines(text: string): string[] {\n return text.split('\\n')\n}\n\n/**\n * 合并行为文本\n */\nexport function joinLines(lines: string[], start: number, end: number): string {\n return lines.slice(start, end + 1).join('\\n')\n}\n\n"]}
|