@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 ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 wangyishuai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # @incremark/core
2
+
3
+ 增量式 Markdown 解析器核心库。
4
+
5
+ ## 特性
6
+
7
+ - 🚀 **增量解析** - 只解析新增内容,已完成的块不再重复处理
8
+ - 🔄 **流式友好** - 专为 AI 流式输出场景设计
9
+ - 🎯 **智能边界检测** - 准确识别 Markdown 块边界
10
+ - 📦 **框架无关** - 可与任何前端框架配合使用
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ pnpm add @incremark/core
16
+ ```
17
+
18
+ ## 快速开始
19
+
20
+ ```ts
21
+ import { createIncremarkParser } from '@incremark/core'
22
+
23
+ const parser = createIncremarkParser({ gfm: true })
24
+
25
+ // 模拟流式输入
26
+ parser.append('# Hello\n')
27
+ parser.append('\nWorld')
28
+ parser.finalize()
29
+
30
+ // 获取结果
31
+ console.log(parser.getCompletedBlocks())
32
+ console.log(parser.getAst())
33
+ ```
34
+
35
+ ## API
36
+
37
+ ### createIncremarkParser(options)
38
+
39
+ 创建解析器实例。
40
+
41
+ ```ts
42
+ interface ParserOptions {
43
+ gfm?: boolean // 启用 GFM
44
+ containers?: boolean // 启用 ::: 容器
45
+ extensions?: Extension[] // micromark 扩展
46
+ mdastExtensions?: Extension[] // mdast 扩展
47
+ }
48
+ ```
49
+
50
+ ### parser.append(chunk)
51
+
52
+ 追加内容,返回增量更新。
53
+
54
+ ### parser.finalize()
55
+
56
+ 完成解析。
57
+
58
+ ### parser.reset()
59
+
60
+ 重置状态。
61
+
62
+ ### parser.getBuffer()
63
+
64
+ 获取当前缓冲区内容。
65
+
66
+ ### parser.getCompletedBlocks()
67
+
68
+ 获取已完成的块。
69
+
70
+ ### parser.getPendingBlocks()
71
+
72
+ 获取待处理的块。
73
+
74
+ ### parser.getAst()
75
+
76
+ 获取完整 AST。
77
+
78
+ ## 类型定义
79
+
80
+ ```ts
81
+ interface ParsedBlock {
82
+ id: string
83
+ status: 'pending' | 'stable' | 'completed'
84
+ node: RootContent
85
+ startOffset: number
86
+ endOffset: number
87
+ rawText: string
88
+ }
89
+ ```
90
+
91
+ ## 与框架集成
92
+
93
+ - Vue: [@incremark/vue](../vue)
94
+ - React: [@incremark/react](../react)
95
+
96
+ ## License
97
+
98
+ MIT
99
+
@@ -0,0 +1,4 @@
1
+ export { r as createInitialContext, o as detectContainer, p as detectContainerEnd, g as detectFenceEnd, f as detectFenceStart, q as isBlockBoundary, l as isBlockquoteStart, i as isEmptyLine, h as isHeading, m as isHtmlBlock, k as isListItemStart, n as isTableDelimiter, j as isThematicBreak, u as updateContext } from '../index-i_qABRHQ.js';
2
+ import 'mdast';
3
+ import 'micromark-util-types';
4
+ import 'mdast-util-from-markdown';
@@ -0,0 +1,155 @@
1
+ // src/detector/index.ts
2
+ function detectFenceStart(line) {
3
+ const match = line.match(/^(\s*)((`{3,})|(~{3,}))/);
4
+ if (match) {
5
+ const fence = match[2];
6
+ const char = fence[0];
7
+ return { char, length: fence.length };
8
+ }
9
+ return null;
10
+ }
11
+ function detectFenceEnd(line, context) {
12
+ if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
13
+ return false;
14
+ }
15
+ const pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
16
+ return pattern.test(line);
17
+ }
18
+ function isEmptyLine(line) {
19
+ return /^\s*$/.test(line);
20
+ }
21
+ function isHeading(line) {
22
+ return /^#{1,6}\s/.test(line);
23
+ }
24
+ function isThematicBreak(line) {
25
+ return /^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim());
26
+ }
27
+ function isListItemStart(line) {
28
+ const unordered = line.match(/^(\s*)([-*+])\s/);
29
+ if (unordered) {
30
+ return { ordered: false, indent: unordered[1].length };
31
+ }
32
+ const ordered = line.match(/^(\s*)(\d{1,9})[.)]\s/);
33
+ if (ordered) {
34
+ return { ordered: true, indent: ordered[1].length };
35
+ }
36
+ return null;
37
+ }
38
+ function isBlockquoteStart(line) {
39
+ return /^\s{0,3}>/.test(line);
40
+ }
41
+ function isHtmlBlock(line) {
42
+ 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);
43
+ }
44
+ function isTableDelimiter(line) {
45
+ return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/.test(line.trim());
46
+ }
47
+ function detectContainer(line, config) {
48
+ const marker = config?.marker || ":";
49
+ const minLength = config?.minMarkerLength || 3;
50
+ const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
51
+ const pattern = new RegExp(
52
+ `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s+(\\w[\\w-]*))?(?:\\s+(.*))?\\s*$`
53
+ );
54
+ const match = line.match(pattern);
55
+ if (!match) {
56
+ return null;
57
+ }
58
+ const markerLength = match[2].length;
59
+ const name = match[3] || "";
60
+ const isEnd = !name && !match[4];
61
+ if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
62
+ if (!config.allowedNames.includes(name)) {
63
+ return null;
64
+ }
65
+ }
66
+ return { name, markerLength, isEnd };
67
+ }
68
+ function detectContainerEnd(line, context, config) {
69
+ if (!context.inContainer || !context.containerMarkerLength) {
70
+ return false;
71
+ }
72
+ const result = detectContainer(line, config);
73
+ if (!result) {
74
+ return false;
75
+ }
76
+ return result.isEnd && result.markerLength >= context.containerMarkerLength;
77
+ }
78
+ function isBlockBoundary(prevLine, currentLine, context) {
79
+ if (context.inFencedCode) {
80
+ return detectFenceEnd(currentLine, context);
81
+ }
82
+ if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {
83
+ return true;
84
+ }
85
+ if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
86
+ return true;
87
+ }
88
+ if (isThematicBreak(currentLine)) {
89
+ return true;
90
+ }
91
+ if (detectFenceStart(currentLine)) {
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ function createInitialContext() {
97
+ return {
98
+ inFencedCode: false,
99
+ listDepth: 0,
100
+ blockquoteDepth: 0,
101
+ inContainer: false,
102
+ containerDepth: 0
103
+ };
104
+ }
105
+ function updateContext(line, context, containerConfig) {
106
+ const newContext = { ...context };
107
+ const containerCfg = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
108
+ if (context.inFencedCode) {
109
+ if (detectFenceEnd(line, context)) {
110
+ newContext.inFencedCode = false;
111
+ newContext.fenceChar = void 0;
112
+ newContext.fenceLength = void 0;
113
+ }
114
+ return newContext;
115
+ }
116
+ const fence = detectFenceStart(line);
117
+ if (fence) {
118
+ newContext.inFencedCode = true;
119
+ newContext.fenceChar = fence.char;
120
+ newContext.fenceLength = fence.length;
121
+ return newContext;
122
+ }
123
+ if (containerCfg !== void 0) {
124
+ if (context.inContainer) {
125
+ if (detectContainerEnd(line, context, containerCfg)) {
126
+ newContext.containerDepth = context.containerDepth - 1;
127
+ if (newContext.containerDepth === 0) {
128
+ newContext.inContainer = false;
129
+ newContext.containerMarkerLength = void 0;
130
+ newContext.containerName = void 0;
131
+ }
132
+ return newContext;
133
+ }
134
+ const nested = detectContainer(line, containerCfg);
135
+ if (nested && !nested.isEnd) {
136
+ newContext.containerDepth = context.containerDepth + 1;
137
+ return newContext;
138
+ }
139
+ } else {
140
+ const container = detectContainer(line, containerCfg);
141
+ if (container && !container.isEnd) {
142
+ newContext.inContainer = true;
143
+ newContext.containerMarkerLength = container.markerLength;
144
+ newContext.containerName = container.name;
145
+ newContext.containerDepth = 1;
146
+ return newContext;
147
+ }
148
+ }
149
+ }
150
+ return newContext;
151
+ }
152
+
153
+ export { createInitialContext, detectContainer, detectContainerEnd, detectFenceEnd, detectFenceStart, isBlockBoundary, isBlockquoteStart, isEmptyLine, isHeading, isHtmlBlock, isListItemStart, isTableDelimiter, isThematicBreak, updateContext };
154
+ //# sourceMappingURL=index.js.map
155
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/detector/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"]}
@@ -0,0 +1,207 @@
1
+ import { RootContent, Root } from 'mdast';
2
+ import { Extension } from 'micromark-util-types';
3
+ import { Extension as Extension$1 } from 'mdast-util-from-markdown';
4
+
5
+ /**
6
+ * 解析块的状态
7
+ */
8
+ type BlockStatus = 'pending' | 'stable' | 'completed';
9
+ /**
10
+ * 解析出的块
11
+ */
12
+ interface ParsedBlock {
13
+ /** 块的唯一 ID */
14
+ id: string;
15
+ /** 块状态 */
16
+ status: BlockStatus;
17
+ /** AST 节点 */
18
+ node: RootContent;
19
+ /** 原始文本起始位置(相对于完整文档) */
20
+ startOffset: number;
21
+ /** 原始文本结束位置 */
22
+ endOffset: number;
23
+ /** 原始文本内容 */
24
+ rawText: string;
25
+ }
26
+ /**
27
+ * 增量更新事件
28
+ */
29
+ interface IncrementalUpdate {
30
+ /** 新完成的块 */
31
+ completed: ParsedBlock[];
32
+ /** 更新的块(内容变化) */
33
+ updated: ParsedBlock[];
34
+ /** 当前正在解析中的块(可能不完整) */
35
+ pending: ParsedBlock[];
36
+ /** 完整的 AST(包含所有已解析的内容) */
37
+ ast: Root;
38
+ }
39
+ /**
40
+ * 容器语法配置
41
+ */
42
+ interface ContainerConfig {
43
+ /** 容器标记字符,默认 ':' */
44
+ marker?: string;
45
+ /** 最小标记长度,默认 3 */
46
+ minMarkerLength?: number;
47
+ /** 允许的容器名称(如 ['warning', 'info', 'youtube']),undefined 表示允许所有 */
48
+ allowedNames?: string[];
49
+ }
50
+ /**
51
+ * 解析器状态变化事件
52
+ */
53
+ interface ParserState {
54
+ /** 已完成的块 */
55
+ completedBlocks: ParsedBlock[];
56
+ /** 待处理的块 */
57
+ pendingBlocks: ParsedBlock[];
58
+ /** 完整的 Markdown 内容 */
59
+ markdown: string;
60
+ /** 完整的 AST */
61
+ ast: Root;
62
+ }
63
+ /**
64
+ * 解析器配置
65
+ */
66
+ interface ParserOptions {
67
+ /** 启用 GFM 扩展(表格、任务列表等) */
68
+ gfm?: boolean;
69
+ /**
70
+ * 启用 ::: 容器语法支持(用于边界检测)
71
+ * - false: 禁用(默认)
72
+ * - true: 使用默认配置启用
73
+ * - ContainerConfig: 使用自定义配置启用
74
+ */
75
+ containers?: boolean | ContainerConfig;
76
+ /** 自定义块边界检测函数 */
77
+ blockBoundaryDetector?: (content: string, position: number) => boolean;
78
+ /** 自定义 micromark 扩展(如 directive) */
79
+ extensions?: Extension[];
80
+ /** 自定义 mdast 扩展(如 directiveFromMarkdown) */
81
+ mdastExtensions?: Extension$1[];
82
+ /** 状态变化回调 */
83
+ onChange?: (state: ParserState) => void;
84
+ }
85
+ /**
86
+ * 块上下文
87
+ */
88
+ interface BlockContext {
89
+ /** 当前是否在代码块中 */
90
+ inFencedCode: boolean;
91
+ /** 代码块的 fence 字符(` 或 ~) */
92
+ fenceChar?: string;
93
+ /** 代码块的 fence 长度 */
94
+ fenceLength?: number;
95
+ /** 当前列表嵌套深度 */
96
+ listDepth: number;
97
+ /** 当前引用嵌套深度 */
98
+ blockquoteDepth: number;
99
+ /** 当前是否在容器块中 */
100
+ inContainer: boolean;
101
+ /** 容器的标记长度 */
102
+ containerMarkerLength?: number;
103
+ /** 容器名称 */
104
+ containerName?: string;
105
+ /** 容器嵌套深度(支持嵌套容器) */
106
+ containerDepth: number;
107
+ }
108
+ /**
109
+ * 容器检测结果
110
+ */
111
+ interface ContainerMatch {
112
+ /** 容器名称 */
113
+ name: string;
114
+ /** 标记长度(冒号数量) */
115
+ markerLength: number;
116
+ /** 是否是结束标记 */
117
+ isEnd: boolean;
118
+ }
119
+ /**
120
+ * 块类型检测结果
121
+ */
122
+ interface BlockTypeInfo {
123
+ type: string;
124
+ /** 是否是容器节点(可以包含其他块) */
125
+ isContainer: boolean;
126
+ /** 是否需要显式关闭(如代码块) */
127
+ requiresClosing: boolean;
128
+ /** 关闭模式 */
129
+ closingPattern?: RegExp;
130
+ }
131
+
132
+ /**
133
+ * 块类型检测与边界判断
134
+ *
135
+ * Markdown 块级元素的识别规则
136
+ */
137
+
138
+ /**
139
+ * 检测行是否是代码块 fence 开始
140
+ */
141
+ declare function detectFenceStart(line: string): {
142
+ char: string;
143
+ length: number;
144
+ } | null;
145
+ /**
146
+ * 检测行是否是代码块 fence 结束
147
+ */
148
+ declare function detectFenceEnd(line: string, context: BlockContext): boolean;
149
+ /**
150
+ * 检测是否是空行或仅包含空白字符
151
+ */
152
+ declare function isEmptyLine(line: string): boolean;
153
+ /**
154
+ * 检测是否是标题行
155
+ */
156
+ declare function isHeading(line: string): boolean;
157
+ /**
158
+ * 检测是否是 thematic break(水平线)
159
+ */
160
+ declare function isThematicBreak(line: string): boolean;
161
+ /**
162
+ * 检测是否是列表项开始
163
+ */
164
+ declare function isListItemStart(line: string): {
165
+ ordered: boolean;
166
+ indent: number;
167
+ } | null;
168
+ /**
169
+ * 检测是否是引用块开始
170
+ */
171
+ declare function isBlockquoteStart(line: string): boolean;
172
+ /**
173
+ * 检测是否是 HTML 块
174
+ */
175
+ declare function isHtmlBlock(line: string): boolean;
176
+ /**
177
+ * 检测表格分隔行
178
+ */
179
+ declare function isTableDelimiter(line: string): boolean;
180
+ /**
181
+ * 检测容器开始或结束
182
+ *
183
+ * 支持格式:
184
+ * - ::: name 开始
185
+ * - ::: name attr 开始(带属性)
186
+ * - ::: 结束
187
+ * - :::::: name 开始(更长的标记,用于嵌套)
188
+ */
189
+ declare function detectContainer(line: string, config?: ContainerConfig): ContainerMatch | null;
190
+ /**
191
+ * 检测容器结束
192
+ */
193
+ declare function detectContainerEnd(line: string, context: BlockContext, config?: ContainerConfig): boolean;
194
+ /**
195
+ * 判断两行之间是否构成块边界
196
+ */
197
+ declare function isBlockBoundary(prevLine: string, currentLine: string, context: BlockContext): boolean;
198
+ /**
199
+ * 创建初始上下文
200
+ */
201
+ declare function createInitialContext(): BlockContext;
202
+ /**
203
+ * 更新上下文(处理一行后)
204
+ */
205
+ declare function updateContext(line: string, context: BlockContext, containerConfig?: ContainerConfig | boolean): BlockContext;
206
+
207
+ export { type BlockStatus as B, type ContainerConfig as C, type IncrementalUpdate as I, type ParserOptions as P, type ParsedBlock as a, type ParserState as b, type BlockContext as c, type ContainerMatch as d, type BlockTypeInfo as e, detectFenceStart as f, detectFenceEnd as g, isHeading as h, isEmptyLine as i, isThematicBreak as j, isListItemStart as k, isBlockquoteStart as l, isHtmlBlock as m, isTableDelimiter as n, detectContainer as o, detectContainerEnd as p, isBlockBoundary as q, createInitialContext as r, updateContext as u };
@@ -0,0 +1,89 @@
1
+ import { P as ParserOptions, I as IncrementalUpdate, a as ParsedBlock, b as ParserState } from './index-i_qABRHQ.js';
2
+ export { c as BlockContext, B as BlockStatus, e as BlockTypeInfo, C as ContainerConfig, d as ContainerMatch, r as createInitialContext, o as detectContainer, p as detectContainerEnd, g as detectFenceEnd, f as detectFenceStart, q as isBlockBoundary, l as isBlockquoteStart, i as isEmptyLine, h as isHeading, m as isHtmlBlock, k as isListItemStart, n as isTableDelimiter, j as isThematicBreak, u as updateContext } from './index-i_qABRHQ.js';
3
+ import { Root } from 'mdast';
4
+ export { Root, RootContent } from 'mdast';
5
+ export { calculateLineOffset, generateId, joinLines, resetIdCounter, splitLines } from './utils/index.js';
6
+ import 'micromark-util-types';
7
+ import 'mdast-util-from-markdown';
8
+
9
+ declare class IncremarkParser {
10
+ private buffer;
11
+ private lines;
12
+ /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
13
+ private lineOffsets;
14
+ private completedBlocks;
15
+ private pendingStartLine;
16
+ private blockIdCounter;
17
+ private context;
18
+ private options;
19
+ /** 缓存的容器配置,避免重复计算 */
20
+ private cachedContainerConfig;
21
+ /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
22
+ private lastPendingBlocks;
23
+ constructor(options?: ParserOptions);
24
+ private generateBlockId;
25
+ private computeContainerConfig;
26
+ private getContainerConfig;
27
+ private parse;
28
+ /**
29
+ * 增量更新 lines 和 lineOffsets
30
+ * 只处理新增的内容,避免全量 split
31
+ */
32
+ private updateLines;
33
+ /**
34
+ * O(1) 获取行偏移量
35
+ */
36
+ private getLineOffset;
37
+ /**
38
+ * 查找稳定边界
39
+ * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
40
+ */
41
+ private findStableBoundary;
42
+ private checkStability;
43
+ private nodesToBlocks;
44
+ /**
45
+ * 追加新的 chunk 并返回增量更新
46
+ */
47
+ append(chunk: string): IncrementalUpdate;
48
+ /**
49
+ * 触发状态变化回调
50
+ */
51
+ private emitChange;
52
+ /**
53
+ * 标记解析完成,处理剩余内容
54
+ * 也可用于强制中断时(如用户点击停止),将 pending 内容标记为 completed
55
+ */
56
+ finalize(): IncrementalUpdate;
57
+ /**
58
+ * 强制中断解析,将所有待处理内容标记为完成
59
+ * 语义上等同于 finalize(),但名称更清晰
60
+ */
61
+ abort(): IncrementalUpdate;
62
+ /**
63
+ * 获取当前完整的 AST
64
+ * 复用上次 append 的 pending 结果,避免重复解析
65
+ */
66
+ getAst(): Root;
67
+ /**
68
+ * 获取所有已完成的块
69
+ */
70
+ getCompletedBlocks(): ParsedBlock[];
71
+ /**
72
+ * 获取当前缓冲区内容
73
+ */
74
+ getBuffer(): string;
75
+ /**
76
+ * 设置状态变化回调(用于 DevTools 等)
77
+ */
78
+ setOnChange(callback: ((state: ParserState) => void) | undefined): void;
79
+ /**
80
+ * 重置解析器状态
81
+ */
82
+ reset(): void;
83
+ }
84
+ /**
85
+ * 创建 Incremark 解析器实例
86
+ */
87
+ declare function createIncremarkParser(options?: ParserOptions): IncremarkParser;
88
+
89
+ export { IncremarkParser, IncrementalUpdate, ParsedBlock, ParserOptions, ParserState, createIncremarkParser };