@simon_he/vue-tui 1.0.2 → 1.1.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/README.md +44 -26
- package/dist/TMermaidText-CXm_L969.js +885 -0
- package/dist/{TSelect-DR0FomSu.js → TSelect-CiyMvwl-.js} +60 -81
- package/dist/{TTree-kzhlB5P0.js → TTree-DKGiuIcM.js} +140 -103
- package/dist/agent/mermaid.cjs +2 -0
- package/dist/agent/mermaid.d.cts +2 -0
- package/dist/agent/mermaid.d.ts +2 -0
- package/dist/agent/mermaid.js +1 -0
- package/dist/agent.cjs +31184 -24907
- package/dist/agent.d.cts +11 -1
- package/dist/agent.d.ts +11 -1
- package/dist/agent.js +1700 -13
- package/dist/block-source-5CQp4tF8.js +3230 -0
- package/dist/cli/stdin-buffer.d.ts +1 -1
- package/dist/cli.cjs +5037 -6767
- package/dist/cli.d.cts +4 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +2733 -148
- package/dist/core/buffer/eaw-ranges-unicode-17.d.ts +14 -0
- package/dist/core/perf/instrumentation-noop.d.ts +48 -0
- package/dist/core/perf/instrumentation.d.ts +136 -0
- package/dist/core/terminal/create-terminal.d.ts +1 -0
- package/dist/core.cjs +843 -1469
- package/dist/core.js +6 -6
- package/dist/{create-terminal-DBFNYr8f.js → create-terminal-DXuZ2fci.js} +39 -13
- package/dist/debug-logger-ChY_PXVk.js +74 -0
- package/dist/{dom-renderer-BniJSQyX.js → dom-renderer-DNzmg2As.js} +2 -2
- package/dist/experimental.cjs +4115 -5928
- package/dist/experimental.d.cts +2 -0
- package/dist/experimental.d.ts +2 -0
- package/dist/experimental.js +1490 -9
- package/dist/frame-perf-BBhE-iuS.js +111 -0
- package/dist/{sgr-BmzxEjAO.js → hyperlink-B35-O-cW.js} +100 -1
- package/dist/index.cjs +5017 -10339
- package/dist/index.js +16 -14
- package/dist/{linkify-D_gzn9Xq.js → linkify-CuuquMQT.js} +32 -3
- package/dist/markdown.cjs +21295 -12691
- package/dist/markdown.d.cts +1 -1
- package/dist/markdown.d.ts +1 -1
- package/dist/markdown.js +211 -51
- package/dist/mermaid-CWto52Wk.js +123 -0
- package/dist/mermaid.cjs +1707 -0
- package/dist/mermaid.d.cts +3 -0
- package/dist/mermaid.d.ts +3 -0
- package/dist/mermaid.js +6 -0
- package/dist/observability/frame-perf-store.d.ts +2 -1
- package/dist/observability/frame-perf.d.ts +33 -0
- package/dist/observability.cjs +106 -107
- package/dist/observability.d.cts +2 -2
- package/dist/observability.d.ts +2 -2
- package/dist/observability.js +3 -3
- package/dist/{path-suggest-BNqcuE3-.js → path-suggest-fPzRsgPv.js} +1 -1
- package/dist/{wheel-scroll-vBRaQ7p-.js → rect-Ng-3eKzF.js} +253 -117
- package/dist/renderer/cli/stdout-metrics.d.ts +10 -0
- package/dist/renderer/cli/stdout-renderer.d.ts +3 -0
- package/dist/renderer/terminal-graphic-render-queue.d.ts +50 -0
- package/dist/renderer/terminal-graphics-trace.d.ts +53 -0
- package/dist/renderer/terminal-graphics.d.ts +146 -0
- package/dist/renderer-dom.cjs +364 -903
- package/dist/renderer-dom.js +4 -4
- package/dist/runtime/index.d.ts +2 -0
- package/dist/{runtime-rfcZCyJq.js → runtime-D77C03RM.js} +28 -15
- package/dist/runtime.cjs +427 -889
- package/dist/runtime.js +2 -2
- package/dist/{terminal-selection-DBinO6p5.js → terminal-selection-CQYr--26.js} +4 -1
- package/dist/{tlog-theme-CvLFNMwk.js → tlog-theme-CopRt9SC.js} +37 -6
- package/dist/{trace-CjzYeIxN.js → trace-CYHDKM1m.js} +5 -0
- package/dist/vue/agent/create-png-terminal-graphic-renderer.d.ts +19 -0
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +481 -0
- package/dist/vue/components/TCharts.d.ts +1121 -0
- package/dist/vue/components/TFeedback.d.ts +1 -1
- package/dist/vue/components/TFlex.d.ts +389 -0
- package/dist/vue/components/TForm.d.ts +4 -4
- package/dist/vue/components/TLogSearchBar.d.ts +3 -3
- package/dist/vue/components/TLogSearchPager.d.ts +2 -2
- package/dist/vue/components/TMarkdownText.d.ts +162 -4
- package/dist/vue/components/TMermaidText.d.ts +900 -0
- package/dist/vue/components/TVirtualList.d.ts +9 -0
- package/dist/vue/components/TVirtualMarkdown.d.ts +170 -9
- package/dist/vue/components/TVirtualRows.d.ts +9 -0
- package/dist/vue/components/TerminalProvider.d.ts +2 -0
- package/dist/vue/context.d.ts +24 -0
- package/dist/vue/index.d.ts +5 -0
- package/dist/vue/layout/flex.d.ts +67 -0
- package/dist/vue/markdown/ast.d.ts +5 -2
- package/dist/vue/markdown/document.d.ts +5 -0
- package/dist/vue/markdown/image-actions.d.ts +14 -0
- package/dist/vue/markdown/image.d.ts +11 -0
- package/dist/vue/markdown/link-actions.d.ts +14 -0
- package/dist/vue/markdown/math-actions.d.ts +14 -0
- package/dist/vue/markdown/math.d.ts +6 -0
- package/dist/vue/markdown/render.d.ts +28 -0
- package/dist/vue/markdown/types.d.ts +78 -0
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +1436 -0
- package/dist/vue/render/render-manager.d.ts +7 -0
- package/dist/vue.cjs +7207 -12437
- package/dist/vue.js +706 -39
- package/dist/wheel-scroll-CQ22Hcyj.js +106 -0
- package/dist/{hyperlink-Bn02UXJb.js → width-GqllnV8C.js} +166 -111
- package/package.json +85 -14
- package/dist/block-source-CKUOnkgT.js +0 -1663
- package/dist/frame-perf-C6Xfg85O.js +0 -24
- /package/dist/{env-Cl8qmNFN.js → env-DNCzvX8b.js} +0 -0
- /package/dist/{path-mnSgEpCm.js → path-BI9W15Zt.js} +0 -0
|
@@ -1,1663 +0,0 @@
|
|
|
1
|
-
import { sanitizeDomHref } from "./hyperlink-Bn02UXJb.js";
|
|
2
|
-
import { EventZIndexContextKey, applyWheelScroll, createWheelScrollState, currentTextWidthProvider, forEachTextCellSegment, intersectRect, normalizeCellRect, repeatChar, resetWheelScrollState, sanitizeInlineText, sanitizeTextBlock, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./wheel-scroll-vBRaQ7p-.js";
|
|
3
|
-
import { terminalSelectionRowSpans, terminalSelectionVisibleRowSpans } from "./terminal-selection-DBinO6p5.js";
|
|
4
|
-
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, ref, shallowRef, watch, watchEffect } from "vue";
|
|
5
|
-
import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
|
|
6
|
-
|
|
7
|
-
//#region src/vue/markdown/parser.ts
|
|
8
|
-
const markdownInstanceCache = new Map();
|
|
9
|
-
const MAX_MARKDOWN_INSTANCE_CACHE = 32;
|
|
10
|
-
function sanitizeMarkdownLink(url) {
|
|
11
|
-
return sanitizeDomHref(url, { allowRelative: true });
|
|
12
|
-
}
|
|
13
|
-
function isSafeMarkdownLink(url) {
|
|
14
|
-
return sanitizeMarkdownLink(url) != null;
|
|
15
|
-
}
|
|
16
|
-
function normalizeCustomHtmlTags(config) {
|
|
17
|
-
return Array.from(new Set(config?.customHtmlTags?.filter(Boolean) ?? [])).sort();
|
|
18
|
-
}
|
|
19
|
-
function markdownInstanceCacheKey(customHtmlTags) {
|
|
20
|
-
return JSON.stringify({ customHtmlTags });
|
|
21
|
-
}
|
|
22
|
-
function getCachedMarkdownInstance(customHtmlTags) {
|
|
23
|
-
const cacheKey = markdownInstanceCacheKey(customHtmlTags);
|
|
24
|
-
const cached = markdownInstanceCache.get(cacheKey);
|
|
25
|
-
if (cached) {
|
|
26
|
-
markdownInstanceCache.delete(cacheKey);
|
|
27
|
-
markdownInstanceCache.set(cacheKey, cached);
|
|
28
|
-
return cached;
|
|
29
|
-
}
|
|
30
|
-
const created = getMarkdown(`vue-tui-markdown:${cacheKey}`, { customHtmlTags });
|
|
31
|
-
markdownInstanceCache.set(cacheKey, created);
|
|
32
|
-
while (markdownInstanceCache.size > MAX_MARKDOWN_INSTANCE_CACHE) {
|
|
33
|
-
const oldest = markdownInstanceCache.keys().next().value;
|
|
34
|
-
if (oldest == null) break;
|
|
35
|
-
markdownInstanceCache.delete(oldest);
|
|
36
|
-
}
|
|
37
|
-
return created;
|
|
38
|
-
}
|
|
39
|
-
function createTuiMarkdownParser(config) {
|
|
40
|
-
const customHtmlTags = normalizeCustomHtmlTags(config);
|
|
41
|
-
const md = getCachedMarkdownInstance(customHtmlTags);
|
|
42
|
-
function parse(content, final) {
|
|
43
|
-
return parseMarkdownToStructure(content, md, {
|
|
44
|
-
final,
|
|
45
|
-
customHtmlTags,
|
|
46
|
-
requireClosingStrong: final || !config?.streaming,
|
|
47
|
-
validateLink: isSafeMarkdownLink
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return { parse };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
//#endregion
|
|
54
|
-
//#region src/vue/markdown/ast.ts
|
|
55
|
-
const EMPTY_PREFIX = Object.freeze([]);
|
|
56
|
-
const HARD_BREAK_SEGMENT = Object.freeze({
|
|
57
|
-
text: "",
|
|
58
|
-
hardBreak: true
|
|
59
|
-
});
|
|
60
|
-
function mergeStyle$1(base, overlay) {
|
|
61
|
-
if (!base) return overlay;
|
|
62
|
-
if (!overlay) return base;
|
|
63
|
-
return {
|
|
64
|
-
...base,
|
|
65
|
-
...overlay
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function trimBlockArray(blocks) {
|
|
69
|
-
let start = 0;
|
|
70
|
-
let end = blocks.length;
|
|
71
|
-
while (start < end && blocks[start]?.type === "blank") start++;
|
|
72
|
-
while (end > start && blocks[end - 1]?.type === "blank") end--;
|
|
73
|
-
return blocks.slice(start, end);
|
|
74
|
-
}
|
|
75
|
-
function nodeChildren(node) {
|
|
76
|
-
return "children" in node && Array.isArray(node.children) ? node.children : [];
|
|
77
|
-
}
|
|
78
|
-
function stringProp(node, key) {
|
|
79
|
-
const value = node[key];
|
|
80
|
-
return typeof value === "string" ? value : "";
|
|
81
|
-
}
|
|
82
|
-
function booleanProp(node, key) {
|
|
83
|
-
return node[key] === true;
|
|
84
|
-
}
|
|
85
|
-
function numberProp(node, key) {
|
|
86
|
-
const value = node[key];
|
|
87
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
88
|
-
}
|
|
89
|
-
function nodeItems(node) {
|
|
90
|
-
const value = node.items;
|
|
91
|
-
return Array.isArray(value) ? value : [];
|
|
92
|
-
}
|
|
93
|
-
function pushTextSegments(out, text, style) {
|
|
94
|
-
if (!text) return;
|
|
95
|
-
const normalized = sanitizeTextBlock(text);
|
|
96
|
-
if (!normalized) return;
|
|
97
|
-
const parts = normalized.split("\n");
|
|
98
|
-
for (let i = 0; i < parts.length; i++) {
|
|
99
|
-
const part = sanitizeInlineText(parts[i] ?? "");
|
|
100
|
-
if (part) out.push(style ? {
|
|
101
|
-
text: part,
|
|
102
|
-
style
|
|
103
|
-
} : { text: part });
|
|
104
|
-
if (i < parts.length - 1) out.push(HARD_BREAK_SEGMENT);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function sanitizeCodeBlockText(text, tabSize = 4) {
|
|
108
|
-
const normalized = String(text ?? "").replace(/\r\n?/g, "\n").replace(/\t/g, spaces(tabSize)).replace(/\u00a0/g, " ");
|
|
109
|
-
return sanitizeTextBlock(normalized);
|
|
110
|
-
}
|
|
111
|
-
function inlineNodeSegments(nodes, theme, inheritedStyle) {
|
|
112
|
-
const out = [];
|
|
113
|
-
for (const node of nodes) switch (node.type) {
|
|
114
|
-
case "text":
|
|
115
|
-
pushTextSegments(out, stringProp(node, "content"), inheritedStyle);
|
|
116
|
-
break;
|
|
117
|
-
case "inline":
|
|
118
|
-
case "paragraph":
|
|
119
|
-
case "heading":
|
|
120
|
-
case "list_item":
|
|
121
|
-
case "blockquote":
|
|
122
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
123
|
-
break;
|
|
124
|
-
case "strong":
|
|
125
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong)));
|
|
126
|
-
break;
|
|
127
|
-
case "emphasis":
|
|
128
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis)));
|
|
129
|
-
break;
|
|
130
|
-
case "strikethrough":
|
|
131
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough)));
|
|
132
|
-
break;
|
|
133
|
-
case "highlight":
|
|
134
|
-
case "insert":
|
|
135
|
-
case "subscript":
|
|
136
|
-
case "superscript":
|
|
137
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
138
|
-
break;
|
|
139
|
-
case "inline_code":
|
|
140
|
-
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
141
|
-
break;
|
|
142
|
-
case "hardbreak":
|
|
143
|
-
out.push(HARD_BREAK_SEGMENT);
|
|
144
|
-
break;
|
|
145
|
-
case "link": {
|
|
146
|
-
const href = stringProp(node, "href");
|
|
147
|
-
const safeHref = href ? sanitizeMarkdownLink(href) : null;
|
|
148
|
-
const linkStyle = safeHref ? mergeStyle$1(inheritedStyle, {
|
|
149
|
-
...theme.link,
|
|
150
|
-
href: safeHref
|
|
151
|
-
}) : inheritedStyle;
|
|
152
|
-
const children = nodeChildren(node);
|
|
153
|
-
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle));
|
|
154
|
-
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
case "image":
|
|
158
|
-
pushTextSegments(out, stringProp(node, "alt") || stringProp(node, "src"), mergeStyle$1(inheritedStyle, theme.link));
|
|
159
|
-
break;
|
|
160
|
-
case "checkbox":
|
|
161
|
-
case "checkbox_input":
|
|
162
|
-
pushTextSegments(out, booleanProp(node, "checked") ? "[x]" : "[ ]", inheritedStyle);
|
|
163
|
-
break;
|
|
164
|
-
case "emoji":
|
|
165
|
-
pushTextSegments(out, stringProp(node, "markup") || stringProp(node, "name"), inheritedStyle);
|
|
166
|
-
break;
|
|
167
|
-
case "math_inline":
|
|
168
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
169
|
-
break;
|
|
170
|
-
case "reference":
|
|
171
|
-
case "footnote_reference":
|
|
172
|
-
case "footnote_anchor":
|
|
173
|
-
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
174
|
-
break;
|
|
175
|
-
case "html_inline":
|
|
176
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
177
|
-
break;
|
|
178
|
-
case "html_block":
|
|
179
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
180
|
-
break;
|
|
181
|
-
default:
|
|
182
|
-
if (nodeChildren(node).length) {
|
|
183
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
return out;
|
|
190
|
-
}
|
|
191
|
-
function inlineBlock(key, segments, context) {
|
|
192
|
-
return {
|
|
193
|
-
type: "inline",
|
|
194
|
-
key,
|
|
195
|
-
segments,
|
|
196
|
-
prefixSegments: context.prefixSegments,
|
|
197
|
-
continuationPrefixSegments: context.continuationPrefixSegments
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
function blockFromParagraph(node, key, context, theme) {
|
|
201
|
-
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme), context);
|
|
202
|
-
}
|
|
203
|
-
function blockFromHeading(node, key, context, theme) {
|
|
204
|
-
const level = Math.min(6, Math.max(1, Math.floor(numberProp(node, "level") ?? 1)));
|
|
205
|
-
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1]), context);
|
|
206
|
-
}
|
|
207
|
-
function blockFromCodeBlock(node, key, context, theme) {
|
|
208
|
-
return {
|
|
209
|
-
type: "code_block",
|
|
210
|
-
key,
|
|
211
|
-
lines: sanitizeCodeBlockText(stringProp(node, "code")).split("\n"),
|
|
212
|
-
style: theme.codeBlock,
|
|
213
|
-
prefixSegments: context.prefixSegments,
|
|
214
|
-
continuationPrefixSegments: context.continuationPrefixSegments
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function blockFromHtmlBlock(node, key, context, theme) {
|
|
218
|
-
return inlineBlock(key, inlineNodeSegments([{
|
|
219
|
-
type: "html_block",
|
|
220
|
-
raw: stringProp(node, "raw"),
|
|
221
|
-
content: stringProp(node, "content")
|
|
222
|
-
}], theme), context);
|
|
223
|
-
}
|
|
224
|
-
function tableRows$1(node, key) {
|
|
225
|
-
const value = node[key];
|
|
226
|
-
if (key === "header") return value && typeof value === "object" ? [value] : [];
|
|
227
|
-
return Array.isArray(value) ? value : [];
|
|
228
|
-
}
|
|
229
|
-
function tableCells(row) {
|
|
230
|
-
const value = row.cells;
|
|
231
|
-
return Array.isArray(value) ? value : [];
|
|
232
|
-
}
|
|
233
|
-
function tableCellAlign(cell) {
|
|
234
|
-
const align = stringProp(cell, "align");
|
|
235
|
-
return align === "center" || align === "right" || align === "left" ? align : void 0;
|
|
236
|
-
}
|
|
237
|
-
function blockFromTable(node, key, context, theme) {
|
|
238
|
-
const headerRow = tableRows$1(node, "header")[0];
|
|
239
|
-
const header = tableCells(headerRow ?? { type: "table_row" }).map((cell) => ({
|
|
240
|
-
segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong),
|
|
241
|
-
align: tableCellAlign(cell)
|
|
242
|
-
}));
|
|
243
|
-
const rows = tableRows$1(node, "rows").map((row) => tableCells(row).map((cell) => ({
|
|
244
|
-
segments: inlineNodeSegments(nodeChildren(cell), theme),
|
|
245
|
-
align: tableCellAlign(cell)
|
|
246
|
-
})));
|
|
247
|
-
return {
|
|
248
|
-
type: "table",
|
|
249
|
-
key,
|
|
250
|
-
header,
|
|
251
|
-
rows,
|
|
252
|
-
borderStyle: theme.thematicBreak,
|
|
253
|
-
prefixSegments: context.prefixSegments
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
function childSequenceToBlocks(nodes, context, theme, keyPrefix) {
|
|
257
|
-
const out = [];
|
|
258
|
-
let appended = false;
|
|
259
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
260
|
-
const childBlocks = trimBlockArray(nodeToBlocks(nodes[i], context, theme, `${keyPrefix}-${i}`));
|
|
261
|
-
if (!childBlocks.length) continue;
|
|
262
|
-
if (appended && out[out.length - 1]?.type !== "blank" && childBlocks[0]?.type !== "blank") out.push({
|
|
263
|
-
type: "blank",
|
|
264
|
-
key: `${keyPrefix}-${i}-gap`
|
|
265
|
-
});
|
|
266
|
-
out.push(...childBlocks);
|
|
267
|
-
appended = true;
|
|
268
|
-
}
|
|
269
|
-
return out;
|
|
270
|
-
}
|
|
271
|
-
function listItemBlocks(item, index, ordered, start, context, theme, keyPrefix) {
|
|
272
|
-
const markerText = ordered ? `${start + index}. ` : "- ";
|
|
273
|
-
const markerCells = textCellWidth(markerText);
|
|
274
|
-
const markerSegment = {
|
|
275
|
-
text: markerText,
|
|
276
|
-
style: theme.listMarker
|
|
277
|
-
};
|
|
278
|
-
const indentSegment = { text: spaces(markerCells) };
|
|
279
|
-
const firstContext = {
|
|
280
|
-
prefixSegments: [...context.prefixSegments, markerSegment],
|
|
281
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
|
|
282
|
-
};
|
|
283
|
-
const nestedContext = {
|
|
284
|
-
prefixSegments: [...context.continuationPrefixSegments, indentSegment],
|
|
285
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
|
|
286
|
-
};
|
|
287
|
-
const children = nodeChildren(item);
|
|
288
|
-
if (!children.length) return [inlineBlock(`${keyPrefix}-empty`, [], firstContext)];
|
|
289
|
-
const out = [];
|
|
290
|
-
let usedLead = false;
|
|
291
|
-
for (let i = 0; i < children.length; i++) {
|
|
292
|
-
const child = children[i];
|
|
293
|
-
const useLeadContext = !usedLead && (child.type === "paragraph" || child.type === "heading" || child.type === "code_block");
|
|
294
|
-
if (!usedLead && !useLeadContext) {
|
|
295
|
-
out.push(inlineBlock(`${keyPrefix}-marker`, [], firstContext));
|
|
296
|
-
usedLead = true;
|
|
297
|
-
}
|
|
298
|
-
const childBlocks = trimBlockArray(nodeToBlocks(child, useLeadContext && !usedLead ? firstContext : nestedContext, theme, `${keyPrefix}-${i}`));
|
|
299
|
-
if (!childBlocks.length) continue;
|
|
300
|
-
const startsNestedList = child.type === "list" && out.length > 0 && out[out.length - 1]?.type === "inline" && childBlocks[0]?.type === "inline";
|
|
301
|
-
if (out.length && out[out.length - 1]?.type !== "blank" && childBlocks[0]?.type !== "blank" && !startsNestedList) out.push({
|
|
302
|
-
type: "blank",
|
|
303
|
-
key: `${keyPrefix}-${i}-gap`
|
|
304
|
-
});
|
|
305
|
-
out.push(...childBlocks);
|
|
306
|
-
usedLead = true;
|
|
307
|
-
}
|
|
308
|
-
if (!out.length) out.push(inlineBlock(`${keyPrefix}-fallback`, [], firstContext));
|
|
309
|
-
return out;
|
|
310
|
-
}
|
|
311
|
-
function listBlocks(node, context, theme, keyPrefix) {
|
|
312
|
-
const out = [];
|
|
313
|
-
const items = nodeItems(node);
|
|
314
|
-
for (let i = 0; i < items.length; i++) out.push(...listItemBlocks(items[i], i, booleanProp(node, "ordered"), numberProp(node, "start") ?? 1, context, theme, `${keyPrefix}-${i}`));
|
|
315
|
-
return out;
|
|
316
|
-
}
|
|
317
|
-
function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
318
|
-
switch (node.type) {
|
|
319
|
-
case "paragraph":
|
|
320
|
-
if ("children" in node && Array.isArray(node.children)) return [blockFromParagraph(node, keyPrefix, context, theme)];
|
|
321
|
-
break;
|
|
322
|
-
case "heading":
|
|
323
|
-
if ("children" in node && Array.isArray(node.children) && "level" in node) return [blockFromHeading(node, keyPrefix, context, theme)];
|
|
324
|
-
break;
|
|
325
|
-
case "code_block":
|
|
326
|
-
if ("code" in node) return [blockFromCodeBlock(node, keyPrefix, context, theme)];
|
|
327
|
-
break;
|
|
328
|
-
case "thematic_break": return [{
|
|
329
|
-
type: "thematic_break",
|
|
330
|
-
key: keyPrefix,
|
|
331
|
-
char: "─",
|
|
332
|
-
style: theme.thematicBreak,
|
|
333
|
-
prefixSegments: context.prefixSegments
|
|
334
|
-
}];
|
|
335
|
-
case "blockquote": {
|
|
336
|
-
const quoteSegment = {
|
|
337
|
-
text: "│ ",
|
|
338
|
-
style: theme.blockquote
|
|
339
|
-
};
|
|
340
|
-
const quoteContext = {
|
|
341
|
-
prefixSegments: [...context.prefixSegments, quoteSegment],
|
|
342
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment]
|
|
343
|
-
};
|
|
344
|
-
return childSequenceToBlocks(nodeChildren(node), quoteContext, theme, keyPrefix);
|
|
345
|
-
}
|
|
346
|
-
case "list":
|
|
347
|
-
if ("items" in node && Array.isArray(node.items)) return listBlocks(node, context, theme, keyPrefix);
|
|
348
|
-
break;
|
|
349
|
-
case "table": return [blockFromTable(node, keyPrefix, context, theme)];
|
|
350
|
-
case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme), context)];
|
|
351
|
-
case "html_block":
|
|
352
|
-
if ("content" in node) return [blockFromHtmlBlock(node, keyPrefix, context, theme)];
|
|
353
|
-
break;
|
|
354
|
-
case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
|
|
355
|
-
}
|
|
356
|
-
if (nodeChildren(node).length) return childSequenceToBlocks(nodeChildren(node), context, theme, keyPrefix);
|
|
357
|
-
return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
|
|
358
|
-
}
|
|
359
|
-
function markdownAstToBlocks(nodes, theme) {
|
|
360
|
-
const blocks = trimBlockArray(childSequenceToBlocks(nodes, {
|
|
361
|
-
prefixSegments: EMPTY_PREFIX,
|
|
362
|
-
continuationPrefixSegments: EMPTY_PREFIX
|
|
363
|
-
}, theme, "md"));
|
|
364
|
-
return blocks.length ? blocks : [{
|
|
365
|
-
type: "blank",
|
|
366
|
-
key: "md-empty"
|
|
367
|
-
}];
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/vue/markdown/layout.ts
|
|
372
|
-
function toVisualSegment(segment) {
|
|
373
|
-
if (!segment.text) return null;
|
|
374
|
-
const cells = textCellWidth(segment.text);
|
|
375
|
-
if (cells <= 0) return null;
|
|
376
|
-
return {
|
|
377
|
-
text: segment.text,
|
|
378
|
-
style: segment.style,
|
|
379
|
-
cells
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
function segmentsPlainText(segments) {
|
|
383
|
-
return segments.map((segment) => segment.text).join("");
|
|
384
|
-
}
|
|
385
|
-
function segmentsCellWidth(segments) {
|
|
386
|
-
let cells = 0;
|
|
387
|
-
for (const segment of segments) {
|
|
388
|
-
if (segment.hardBreak || !segment.text) continue;
|
|
389
|
-
cells += textCellWidth(segment.text);
|
|
390
|
-
}
|
|
391
|
-
return cells;
|
|
392
|
-
}
|
|
393
|
-
function normalizeInlineSegments(segments) {
|
|
394
|
-
const lines = [[]];
|
|
395
|
-
for (const segment of segments) {
|
|
396
|
-
if (segment.hardBreak) {
|
|
397
|
-
lines.push([]);
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
lines[lines.length - 1].push(segment);
|
|
401
|
-
}
|
|
402
|
-
return lines;
|
|
403
|
-
}
|
|
404
|
-
function clipInlineSegmentsToWidth(segments, width) {
|
|
405
|
-
let remaining = Math.max(0, Math.floor(width));
|
|
406
|
-
if (remaining <= 0) return [];
|
|
407
|
-
const out = [];
|
|
408
|
-
for (const segment of segments) {
|
|
409
|
-
if (segment.hardBreak || !segment.text || remaining <= 0) continue;
|
|
410
|
-
const piece = sliceByCellsRange(segment.text, 0, remaining);
|
|
411
|
-
const cells = textCellWidth(piece);
|
|
412
|
-
if (!piece || cells <= 0) continue;
|
|
413
|
-
out.push(segment.style ? {
|
|
414
|
-
text: piece,
|
|
415
|
-
style: segment.style
|
|
416
|
-
} : { text: piece });
|
|
417
|
-
remaining -= cells;
|
|
418
|
-
}
|
|
419
|
-
return out;
|
|
420
|
-
}
|
|
421
|
-
function clipVisualSegmentsToWidth(segments, width) {
|
|
422
|
-
let remaining = Math.max(0, Math.floor(width));
|
|
423
|
-
if (remaining <= 0) return [];
|
|
424
|
-
const out = [];
|
|
425
|
-
for (const segment of segments) {
|
|
426
|
-
if (!segment.text || remaining <= 0) continue;
|
|
427
|
-
if (segment.cells <= remaining) {
|
|
428
|
-
out.push(segment);
|
|
429
|
-
remaining -= segment.cells;
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
const text = sliceByCellsRange(segment.text, 0, remaining);
|
|
433
|
-
const cells = textCellWidth(text);
|
|
434
|
-
if (!text || cells <= 0) continue;
|
|
435
|
-
out.push({
|
|
436
|
-
text,
|
|
437
|
-
style: segment.style,
|
|
438
|
-
cells
|
|
439
|
-
});
|
|
440
|
-
remaining -= cells;
|
|
441
|
-
}
|
|
442
|
-
return out;
|
|
443
|
-
}
|
|
444
|
-
function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, width) {
|
|
445
|
-
const rows = [];
|
|
446
|
-
const prefixCells = segmentsCellWidth(prefixSegments);
|
|
447
|
-
const continuationPrefixCells = segmentsCellWidth(continuationPrefixSegments);
|
|
448
|
-
const firstPrefix = prefixCells >= width ? clipInlineSegmentsToWidth(prefixSegments, width) : prefixSegments;
|
|
449
|
-
const firstPrefixCells = segmentsCellWidth(firstPrefix);
|
|
450
|
-
const continuationPrefix = continuationPrefixCells >= width ? clipInlineSegmentsToWidth(continuationPrefixSegments, Math.max(0, width - 1)) : continuationPrefixSegments;
|
|
451
|
-
const trimmedContinuationPrefixCells = segmentsCellWidth(continuationPrefix);
|
|
452
|
-
const maxFirst = Math.max(0, width - firstPrefixCells);
|
|
453
|
-
const maxNext = Math.max(0, width - trimmedContinuationPrefixCells);
|
|
454
|
-
const openRow = (useContinuation) => {
|
|
455
|
-
const prefix = useContinuation ? continuationPrefix : firstPrefix;
|
|
456
|
-
const renderedPrefix = prefix.map(toVisualSegment).filter(Boolean);
|
|
457
|
-
return {
|
|
458
|
-
useContinuation,
|
|
459
|
-
segments: [...renderedPrefix],
|
|
460
|
-
remaining: useContinuation ? maxNext : maxFirst
|
|
461
|
-
};
|
|
462
|
-
};
|
|
463
|
-
const openDegradedRow = () => ({
|
|
464
|
-
useContinuation: true,
|
|
465
|
-
segments: [],
|
|
466
|
-
remaining: width
|
|
467
|
-
});
|
|
468
|
-
let row = openRow(false);
|
|
469
|
-
if (!source.length) {
|
|
470
|
-
rows.push(row.segments);
|
|
471
|
-
return rows;
|
|
472
|
-
}
|
|
473
|
-
const pushRow = () => {
|
|
474
|
-
rows.push(row.segments);
|
|
475
|
-
row = openRow(true);
|
|
476
|
-
};
|
|
477
|
-
const prefixLengthForRow = () => row.useContinuation ? continuationPrefix.length : firstPrefix.length;
|
|
478
|
-
const rowHasBody = () => row.segments.length > prefixLengthForRow();
|
|
479
|
-
for (const segment of source) {
|
|
480
|
-
let aborted = false;
|
|
481
|
-
forEachTextCellSegment(segment.text, (piece) => {
|
|
482
|
-
while (true) {
|
|
483
|
-
if (row.remaining <= 0) {
|
|
484
|
-
pushRow();
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
if (piece.cells > row.remaining) {
|
|
488
|
-
if (rowHasBody()) {
|
|
489
|
-
pushRow();
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
if (!row.useContinuation) {
|
|
493
|
-
rows.push(row.segments);
|
|
494
|
-
row = openDegradedRow();
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
if (piece.cells <= width) {
|
|
498
|
-
row = openDegradedRow();
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
aborted = true;
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
row.segments.push({
|
|
505
|
-
text: piece.text,
|
|
506
|
-
style: segment.style,
|
|
507
|
-
cells: piece.cells
|
|
508
|
-
});
|
|
509
|
-
row.remaining -= piece.cells;
|
|
510
|
-
return void 0;
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
if (aborted) continue;
|
|
514
|
-
}
|
|
515
|
-
rows.push(row.segments);
|
|
516
|
-
return rows;
|
|
517
|
-
}
|
|
518
|
-
function inlineBlockRows(block, width) {
|
|
519
|
-
const rows = [];
|
|
520
|
-
const lines = normalizeInlineSegments(block.segments);
|
|
521
|
-
const prefix = block.prefixSegments ?? [];
|
|
522
|
-
const continuationPrefix = block.continuationPrefixSegments ?? prefix;
|
|
523
|
-
let rowInBlock = 0;
|
|
524
|
-
for (const line of lines) {
|
|
525
|
-
const wrapped = wrapLineSegments(line, prefix, continuationPrefix, width);
|
|
526
|
-
for (const visualSegments of wrapped) {
|
|
527
|
-
rows.push({
|
|
528
|
-
key: `${block.key}:${rowInBlock}`,
|
|
529
|
-
blockKey: block.key,
|
|
530
|
-
rowInBlock,
|
|
531
|
-
plainText: segmentsPlainText(visualSegments),
|
|
532
|
-
segments: visualSegments
|
|
533
|
-
});
|
|
534
|
-
rowInBlock++;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return rows.length ? rows : [{
|
|
538
|
-
key: `${block.key}:0`,
|
|
539
|
-
blockKey: block.key,
|
|
540
|
-
rowInBlock: 0,
|
|
541
|
-
plainText: "",
|
|
542
|
-
segments: prefix.map(toVisualSegment).filter(Boolean)
|
|
543
|
-
}];
|
|
544
|
-
}
|
|
545
|
-
function codeBlockRows(block, width) {
|
|
546
|
-
const rows = [];
|
|
547
|
-
const prefix = block.prefixSegments ?? [];
|
|
548
|
-
const continuationPrefix = block.continuationPrefixSegments ?? prefix;
|
|
549
|
-
const lineSegments = block.lines.length ? block.lines : [""];
|
|
550
|
-
let rowInBlock = 0;
|
|
551
|
-
for (const line of lineSegments) {
|
|
552
|
-
const sourceSegments = [{
|
|
553
|
-
text: line,
|
|
554
|
-
style: block.style
|
|
555
|
-
}];
|
|
556
|
-
const wrapped = wrapLineSegments(sourceSegments, prefix, continuationPrefix, width);
|
|
557
|
-
for (const visualSegments of wrapped) {
|
|
558
|
-
rows.push({
|
|
559
|
-
key: `${block.key}:${rowInBlock}`,
|
|
560
|
-
blockKey: block.key,
|
|
561
|
-
rowInBlock,
|
|
562
|
-
plainText: segmentsPlainText(visualSegments),
|
|
563
|
-
segments: visualSegments
|
|
564
|
-
});
|
|
565
|
-
rowInBlock++;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return rows;
|
|
569
|
-
}
|
|
570
|
-
function thematicBreakRows(block, width) {
|
|
571
|
-
const prefix = (block.prefixSegments ?? []).map(toVisualSegment).filter(Boolean);
|
|
572
|
-
const prefixCells = prefix.reduce((sum, segment) => sum + segment.cells, 0);
|
|
573
|
-
const fill = Math.max(0, width - prefixCells);
|
|
574
|
-
const bar = fill > 0 ? [{
|
|
575
|
-
text: repeatChar(block.char ?? "─", fill),
|
|
576
|
-
style: block.style,
|
|
577
|
-
cells: fill
|
|
578
|
-
}] : [];
|
|
579
|
-
return [{
|
|
580
|
-
key: `${block.key}:0`,
|
|
581
|
-
blockKey: block.key,
|
|
582
|
-
rowInBlock: 0,
|
|
583
|
-
plainText: segmentsPlainText([...prefix, ...bar]),
|
|
584
|
-
segments: [...prefix, ...bar]
|
|
585
|
-
}];
|
|
586
|
-
}
|
|
587
|
-
function tableColumnCount(block) {
|
|
588
|
-
let columns = block.header.length;
|
|
589
|
-
for (const row of block.rows) columns = Math.max(columns, row.length);
|
|
590
|
-
return columns;
|
|
591
|
-
}
|
|
592
|
-
function tableColumnWidths(block, columns, width) {
|
|
593
|
-
const widths = Array.from({ length: columns }, () => 0);
|
|
594
|
-
const measureRow = (row) => {
|
|
595
|
-
for (let index = 0; index < columns; index++) widths[index] = Math.max(widths[index] ?? 0, segmentsCellWidth(row[index]?.segments ?? []));
|
|
596
|
-
};
|
|
597
|
-
measureRow(block.header);
|
|
598
|
-
for (const row of block.rows) measureRow(row);
|
|
599
|
-
const prefixCells = segmentsCellWidth(block.prefixSegments ?? []);
|
|
600
|
-
const available = Math.max(0, width - prefixCells - (columns * 3 + 1));
|
|
601
|
-
for (let index = 0; index < widths.length; index++) widths[index] = Math.min(widths[index] ?? 0, available);
|
|
602
|
-
let total = widths.reduce((sum, item) => sum + item, 0);
|
|
603
|
-
while (total > available) {
|
|
604
|
-
let widestIndex = 0;
|
|
605
|
-
for (let index = 1; index < widths.length; index++) if ((widths[index] ?? 0) > (widths[widestIndex] ?? 0)) widestIndex = index;
|
|
606
|
-
if ((widths[widestIndex] ?? 0) <= 0) break;
|
|
607
|
-
widths[widestIndex]--;
|
|
608
|
-
total--;
|
|
609
|
-
}
|
|
610
|
-
return widths;
|
|
611
|
-
}
|
|
612
|
-
function appendVisualSegment(segments, text, style) {
|
|
613
|
-
if (!text) return;
|
|
614
|
-
const cells = textCellWidth(text);
|
|
615
|
-
if (cells <= 0) return;
|
|
616
|
-
const prev = segments[segments.length - 1];
|
|
617
|
-
if (prev && prev.style === style) {
|
|
618
|
-
segments[segments.length - 1] = {
|
|
619
|
-
text: `${prev.text}${text}`,
|
|
620
|
-
style,
|
|
621
|
-
cells: prev.cells + cells
|
|
622
|
-
};
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
segments.push({
|
|
626
|
-
text,
|
|
627
|
-
style,
|
|
628
|
-
cells
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
function appendInlineVisualSegments(out, segments) {
|
|
632
|
-
for (const segment of segments) {
|
|
633
|
-
if (segment.hardBreak) {
|
|
634
|
-
appendVisualSegment(out, " ");
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
appendVisualSegment(out, segment.text, segment.style);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
function tableBorderRow(block, widths, width, rowInBlock, left, join, right) {
|
|
641
|
-
const prefix = (block.prefixSegments ?? []).map(toVisualSegment).filter(Boolean);
|
|
642
|
-
const segments = [...prefix];
|
|
643
|
-
let cells = prefix.reduce((sum, segment) => sum + segment.cells, 0);
|
|
644
|
-
const append = (text) => {
|
|
645
|
-
if (cells >= width) return;
|
|
646
|
-
appendVisualSegment(segments, text, block.borderStyle);
|
|
647
|
-
cells += textCellWidth(text);
|
|
648
|
-
};
|
|
649
|
-
append(left);
|
|
650
|
-
for (let index = 0; index < widths.length && cells < width; index++) {
|
|
651
|
-
append(repeatChar("─", (widths[index] ?? 0) + 2));
|
|
652
|
-
append(index === widths.length - 1 ? right : join);
|
|
653
|
-
}
|
|
654
|
-
const clipped = clipVisualSegmentsToWidth(segments, width);
|
|
655
|
-
return {
|
|
656
|
-
key: `${block.key}:${rowInBlock}`,
|
|
657
|
-
blockKey: block.key,
|
|
658
|
-
rowInBlock,
|
|
659
|
-
plainText: segmentsPlainText(clipped),
|
|
660
|
-
segments: clipped
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
function tableContentRow(block, row, widths, width, rowInBlock) {
|
|
664
|
-
const segments = (block.prefixSegments ?? []).map(toVisualSegment).filter(Boolean);
|
|
665
|
-
let cells = segments.reduce((sum, segment) => sum + segment.cells, 0);
|
|
666
|
-
const append = (text, style) => {
|
|
667
|
-
if (cells >= width) return;
|
|
668
|
-
appendVisualSegment(segments, text, style);
|
|
669
|
-
cells += textCellWidth(text);
|
|
670
|
-
};
|
|
671
|
-
append("│", block.borderStyle);
|
|
672
|
-
for (let index = 0; index < widths.length && cells < width; index++) {
|
|
673
|
-
const columnWidth = widths[index] ?? 0;
|
|
674
|
-
const cell = row[index];
|
|
675
|
-
const clipped$1 = clipInlineSegmentsToWidth(cell?.segments ?? [], columnWidth);
|
|
676
|
-
const contentCells = segmentsCellWidth(clipped$1);
|
|
677
|
-
const remaining = Math.max(0, columnWidth - contentCells);
|
|
678
|
-
const align = cell?.align ?? "left";
|
|
679
|
-
const leftPad = align === "right" ? remaining : align === "center" ? Math.floor(remaining / 2) : 0;
|
|
680
|
-
const rightPad = remaining - leftPad;
|
|
681
|
-
append(" ");
|
|
682
|
-
append(repeatChar(" ", leftPad));
|
|
683
|
-
if (cells < width) {
|
|
684
|
-
appendInlineVisualSegments(segments, clipped$1);
|
|
685
|
-
cells += contentCells;
|
|
686
|
-
}
|
|
687
|
-
append(repeatChar(" ", rightPad));
|
|
688
|
-
append(" ");
|
|
689
|
-
append("│", block.borderStyle);
|
|
690
|
-
}
|
|
691
|
-
const clipped = clipVisualSegmentsToWidth(segments, width);
|
|
692
|
-
return {
|
|
693
|
-
key: `${block.key}:${rowInBlock}`,
|
|
694
|
-
blockKey: block.key,
|
|
695
|
-
rowInBlock,
|
|
696
|
-
plainText: segmentsPlainText(clipped),
|
|
697
|
-
segments: clipped
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
function tableRows(block, width) {
|
|
701
|
-
const columns = tableColumnCount(block);
|
|
702
|
-
if (columns <= 0) return [];
|
|
703
|
-
const widths = tableColumnWidths(block, columns, width);
|
|
704
|
-
const rows = [];
|
|
705
|
-
rows.push(tableBorderRow(block, widths, width, rows.length, "╭", "┬", "╮"));
|
|
706
|
-
rows.push(tableContentRow(block, block.header, widths, width, rows.length));
|
|
707
|
-
rows.push(tableBorderRow(block, widths, width, rows.length, "├", "┼", "┤"));
|
|
708
|
-
for (const row of block.rows) rows.push(tableContentRow(block, row, widths, width, rows.length));
|
|
709
|
-
rows.push(tableBorderRow(block, widths, width, rows.length, "╰", "┴", "╯"));
|
|
710
|
-
return rows;
|
|
711
|
-
}
|
|
712
|
-
function styleSignature(style) {
|
|
713
|
-
if (!style) return "";
|
|
714
|
-
return [
|
|
715
|
-
style.fg ?? "",
|
|
716
|
-
style.bg ?? "",
|
|
717
|
-
style.bold ? "1" : "0",
|
|
718
|
-
style.dim ? "1" : "0",
|
|
719
|
-
style.italic ? "1" : "0",
|
|
720
|
-
style.underline ? "1" : "0",
|
|
721
|
-
style.inverse ? "1" : "0",
|
|
722
|
-
style.href ?? ""
|
|
723
|
-
].join("");
|
|
724
|
-
}
|
|
725
|
-
function inlineSegmentSignature(segment) {
|
|
726
|
-
return [
|
|
727
|
-
segment.text,
|
|
728
|
-
segment.hardBreak ? "1" : "0",
|
|
729
|
-
styleSignature(segment.style)
|
|
730
|
-
].join("");
|
|
731
|
-
}
|
|
732
|
-
function inlineSegmentsSignature(segments) {
|
|
733
|
-
return (segments ?? []).map(inlineSegmentSignature).join("");
|
|
734
|
-
}
|
|
735
|
-
function blockLayoutSignature(block) {
|
|
736
|
-
switch (block.type) {
|
|
737
|
-
case "inline": return [
|
|
738
|
-
block.type,
|
|
739
|
-
inlineSegmentsSignature(block.segments),
|
|
740
|
-
inlineSegmentsSignature(block.prefixSegments),
|
|
741
|
-
inlineSegmentsSignature(block.continuationPrefixSegments)
|
|
742
|
-
].join("");
|
|
743
|
-
case "code_block": return [
|
|
744
|
-
block.type,
|
|
745
|
-
block.lines.join(""),
|
|
746
|
-
styleSignature(block.style),
|
|
747
|
-
inlineSegmentsSignature(block.prefixSegments),
|
|
748
|
-
inlineSegmentsSignature(block.continuationPrefixSegments)
|
|
749
|
-
].join("");
|
|
750
|
-
case "thematic_break": return [
|
|
751
|
-
block.type,
|
|
752
|
-
block.char ?? "",
|
|
753
|
-
styleSignature(block.style),
|
|
754
|
-
inlineSegmentsSignature(block.prefixSegments)
|
|
755
|
-
].join("");
|
|
756
|
-
case "table": return [
|
|
757
|
-
block.type,
|
|
758
|
-
inlineSegmentsSignature(block.prefixSegments),
|
|
759
|
-
styleSignature(block.borderStyle),
|
|
760
|
-
block.header.map((cell) => `${inlineSegmentsSignature(cell.segments)}\u0005${cell.align ?? ""}`).join(""),
|
|
761
|
-
block.rows.map((row) => row.map((cell) => `${inlineSegmentsSignature(cell.segments)}\u0005${cell.align ?? ""}`).join("")).join("\x07")
|
|
762
|
-
].join("");
|
|
763
|
-
case "blank": return block.type;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
function layoutMarkdownBlock(block, width) {
|
|
767
|
-
switch (block.type) {
|
|
768
|
-
case "inline": return inlineBlockRows(block, width);
|
|
769
|
-
case "code_block": return codeBlockRows(block, width);
|
|
770
|
-
case "thematic_break": return thematicBreakRows(block, width);
|
|
771
|
-
case "table": return tableRows(block, width);
|
|
772
|
-
case "blank": return [{
|
|
773
|
-
key: `${block.key}:0`,
|
|
774
|
-
blockKey: block.key,
|
|
775
|
-
rowInBlock: 0,
|
|
776
|
-
plainText: "",
|
|
777
|
-
segments: []
|
|
778
|
-
}];
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
function layoutMarkdownBlocksCached(blocks, width, previous) {
|
|
782
|
-
const normalizedWidth = Math.max(0, Math.floor(width));
|
|
783
|
-
const widthProvider = currentTextWidthProvider();
|
|
784
|
-
if (normalizedWidth <= 0) return {
|
|
785
|
-
rows: [],
|
|
786
|
-
cache: {
|
|
787
|
-
width: normalizedWidth,
|
|
788
|
-
widthProvider,
|
|
789
|
-
entries: new Map()
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
|
-
const rows = [];
|
|
793
|
-
const entries = new Map();
|
|
794
|
-
const previousEntries = previous?.width === normalizedWidth && Object.is(previous.widthProvider ?? "default", widthProvider) ? previous.entries : void 0;
|
|
795
|
-
for (let index = 0; index < blocks.length; index++) {
|
|
796
|
-
const block = blocks[index];
|
|
797
|
-
const cacheKey = `${index}\u0000${block.key}`;
|
|
798
|
-
const signature = blockLayoutSignature(block);
|
|
799
|
-
const cached = previousEntries?.get(cacheKey);
|
|
800
|
-
const blockRows = cached?.signature === signature ? cached.rows : layoutMarkdownBlock(block, normalizedWidth);
|
|
801
|
-
if (blockRows.length) {
|
|
802
|
-
rows.push(...blockRows);
|
|
803
|
-
entries.set(cacheKey, {
|
|
804
|
-
signature,
|
|
805
|
-
rows: blockRows
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (!rows.length) {
|
|
810
|
-
const emptyRows = [{
|
|
811
|
-
key: "md-empty:0",
|
|
812
|
-
blockKey: "md-empty",
|
|
813
|
-
rowInBlock: 0,
|
|
814
|
-
plainText: "",
|
|
815
|
-
segments: []
|
|
816
|
-
}];
|
|
817
|
-
entries.set("0\0md-empty", {
|
|
818
|
-
signature: "empty",
|
|
819
|
-
rows: emptyRows
|
|
820
|
-
});
|
|
821
|
-
rows.push(...emptyRows);
|
|
822
|
-
}
|
|
823
|
-
return {
|
|
824
|
-
rows,
|
|
825
|
-
cache: {
|
|
826
|
-
width: normalizedWidth,
|
|
827
|
-
widthProvider,
|
|
828
|
-
entries
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
function layoutMarkdownBlocks(blocks, width, options) {
|
|
833
|
-
if (options?.widthProvider !== void 0) return withTextWidthProvider(options.widthProvider, () => layoutMarkdownBlocksCached(blocks, width).rows);
|
|
834
|
-
return layoutMarkdownBlocksCached(blocks, width).rows;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
//#endregion
|
|
838
|
-
//#region src/vue/markdown/theme.ts
|
|
839
|
-
function freezeStyle(style) {
|
|
840
|
-
return Object.freeze(style);
|
|
841
|
-
}
|
|
842
|
-
function mergeFrozenStyle(base, overlay) {
|
|
843
|
-
if (!overlay) return base;
|
|
844
|
-
return freezeStyle({
|
|
845
|
-
...base,
|
|
846
|
-
...overlay
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
const DEFAULT_HEADING = Object.freeze([
|
|
850
|
-
freezeStyle({
|
|
851
|
-
bold: true,
|
|
852
|
-
fg: "cyanBright"
|
|
853
|
-
}),
|
|
854
|
-
freezeStyle({
|
|
855
|
-
bold: true,
|
|
856
|
-
fg: "cyanBright"
|
|
857
|
-
}),
|
|
858
|
-
freezeStyle({
|
|
859
|
-
bold: true,
|
|
860
|
-
fg: "blueBright"
|
|
861
|
-
}),
|
|
862
|
-
freezeStyle({
|
|
863
|
-
bold: true,
|
|
864
|
-
fg: "blueBright"
|
|
865
|
-
}),
|
|
866
|
-
freezeStyle({ bold: true }),
|
|
867
|
-
freezeStyle({ bold: true })
|
|
868
|
-
]);
|
|
869
|
-
const DEFAULT_TUI_MARKDOWN_THEME = Object.freeze({
|
|
870
|
-
heading: DEFAULT_HEADING,
|
|
871
|
-
strong: freezeStyle({ bold: true }),
|
|
872
|
-
emphasis: freezeStyle({ italic: true }),
|
|
873
|
-
strikethrough: freezeStyle({ dim: true }),
|
|
874
|
-
inlineCode: freezeStyle({ fg: "yellowBright" }),
|
|
875
|
-
link: freezeStyle({
|
|
876
|
-
fg: "blueBright",
|
|
877
|
-
underline: true
|
|
878
|
-
}),
|
|
879
|
-
blockquote: freezeStyle({ dim: true }),
|
|
880
|
-
listMarker: freezeStyle({
|
|
881
|
-
fg: "cyanBright",
|
|
882
|
-
bold: true
|
|
883
|
-
}),
|
|
884
|
-
codeBlock: freezeStyle({ fg: "yellowBright" }),
|
|
885
|
-
thematicBreak: freezeStyle({ dim: true }),
|
|
886
|
-
html: freezeStyle({ dim: true })
|
|
887
|
-
});
|
|
888
|
-
function stableSerialize(value) {
|
|
889
|
-
if (value == null) return "null";
|
|
890
|
-
if (typeof value !== "object") return JSON.stringify(value);
|
|
891
|
-
if (Array.isArray(value)) return `[${value.map((item) => stableSerialize(item)).join(",")}]`;
|
|
892
|
-
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
893
|
-
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableSerialize(item)}`).join(",")}}`;
|
|
894
|
-
}
|
|
895
|
-
function markdownThemeSignature(overrides) {
|
|
896
|
-
return stableSerialize(overrides ?? null);
|
|
897
|
-
}
|
|
898
|
-
function resolveTuiMarkdownTheme(overrides) {
|
|
899
|
-
if (!overrides) return DEFAULT_TUI_MARKDOWN_THEME;
|
|
900
|
-
const heading = DEFAULT_TUI_MARKDOWN_THEME.heading.map((style, index) => mergeFrozenStyle(style, overrides.heading?.[index]));
|
|
901
|
-
return Object.freeze({
|
|
902
|
-
heading: Object.freeze(heading),
|
|
903
|
-
strong: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.strong, overrides.strong),
|
|
904
|
-
emphasis: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.emphasis, overrides.emphasis),
|
|
905
|
-
strikethrough: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.strikethrough, overrides.strikethrough),
|
|
906
|
-
inlineCode: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.inlineCode, overrides.inlineCode),
|
|
907
|
-
link: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.link, overrides.link),
|
|
908
|
-
blockquote: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.blockquote, overrides.blockquote),
|
|
909
|
-
listMarker: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.listMarker, overrides.listMarker),
|
|
910
|
-
codeBlock: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.codeBlock, overrides.codeBlock),
|
|
911
|
-
thematicBreak: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.thematicBreak, overrides.thematicBreak),
|
|
912
|
-
html: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.html, overrides.html)
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
//#endregion
|
|
917
|
-
//#region src/vue/markdown/document.ts
|
|
918
|
-
/**
|
|
919
|
-
* This helper always builds visual rows from the full source string. Components
|
|
920
|
-
* that need streaming reuse can call buildMarkdownBlocks and cache block layout.
|
|
921
|
-
*/
|
|
922
|
-
function buildMarkdownBlocks(content, parser, options) {
|
|
923
|
-
const theme = resolveTuiMarkdownTheme(options?.theme);
|
|
924
|
-
try {
|
|
925
|
-
const nodes = parser.parse(content, options?.final ?? true);
|
|
926
|
-
const blocks = markdownAstToBlocks(nodes, theme);
|
|
927
|
-
return {
|
|
928
|
-
nodes,
|
|
929
|
-
blocks
|
|
930
|
-
};
|
|
931
|
-
} catch (error) {
|
|
932
|
-
console.warn("[vue-tui] Markdown parse failed; falling back to plain text rendering.", error);
|
|
933
|
-
return {
|
|
934
|
-
nodes: [],
|
|
935
|
-
blocks: plainTextFallbackBlocks(content)
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
function buildMarkdownVisualRows(content, width, parser, options) {
|
|
940
|
-
const { blocks } = buildMarkdownBlocks(content, parser, options);
|
|
941
|
-
if (options?.widthProvider !== void 0) return layoutMarkdownBlocks(blocks, width, { widthProvider: options.widthProvider });
|
|
942
|
-
return layoutMarkdownBlocks(blocks, width);
|
|
943
|
-
}
|
|
944
|
-
function plainTextFallbackBlocks(content) {
|
|
945
|
-
const normalized = sanitizeTextBlock(content);
|
|
946
|
-
const lines = normalized.split("\n");
|
|
947
|
-
const blocks = [];
|
|
948
|
-
for (let index = 0; index < lines.length; index++) {
|
|
949
|
-
const line = sanitizeInlineText(lines[index] ?? "");
|
|
950
|
-
if (!line) {
|
|
951
|
-
blocks.push({
|
|
952
|
-
type: "blank",
|
|
953
|
-
key: `md-fallback:${index}`
|
|
954
|
-
});
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
blocks.push({
|
|
958
|
-
type: "inline",
|
|
959
|
-
key: `md-fallback:${index}`,
|
|
960
|
-
segments: [{ text: line }]
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
return blocks.length ? blocks : [{
|
|
964
|
-
type: "blank",
|
|
965
|
-
key: "md-fallback:0"
|
|
966
|
-
}];
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
//#endregion
|
|
970
|
-
//#region src/vue/markdown/render.ts
|
|
971
|
-
const mergedStyleCache = new WeakMap();
|
|
972
|
-
function mergeStyle(base, overlay) {
|
|
973
|
-
if (!overlay) return base;
|
|
974
|
-
let bucket = mergedStyleCache.get(base);
|
|
975
|
-
if (!bucket) {
|
|
976
|
-
bucket = new WeakMap();
|
|
977
|
-
mergedStyleCache.set(base, bucket);
|
|
978
|
-
}
|
|
979
|
-
const cached = bucket.get(overlay);
|
|
980
|
-
if (cached) return cached;
|
|
981
|
-
const merged = Object.freeze({
|
|
982
|
-
...base,
|
|
983
|
-
...overlay
|
|
984
|
-
});
|
|
985
|
-
bucket.set(overlay, merged);
|
|
986
|
-
return merged;
|
|
987
|
-
}
|
|
988
|
-
function paintMarkdownVisualRow(terminal, row, options) {
|
|
989
|
-
const clipStart = Math.max(0, Math.floor(options.clipStart ?? 0));
|
|
990
|
-
const clipEnd = clipStart + options.w;
|
|
991
|
-
if (!row) {
|
|
992
|
-
if (options.clear !== false) terminal.write(spaces(options.w), {
|
|
993
|
-
x: options.x,
|
|
994
|
-
y: options.y,
|
|
995
|
-
style: options.baseStyle
|
|
996
|
-
});
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
let logicalX = 0;
|
|
1000
|
-
let used = 0;
|
|
1001
|
-
for (const segment of row.segments) {
|
|
1002
|
-
if (used >= options.w || !segment.text) {
|
|
1003
|
-
logicalX += segment.cells;
|
|
1004
|
-
continue;
|
|
1005
|
-
}
|
|
1006
|
-
const segmentStart = logicalX;
|
|
1007
|
-
const segmentEnd = logicalX + segment.cells;
|
|
1008
|
-
logicalX += segment.cells;
|
|
1009
|
-
if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1010
|
-
const segmentStyle = mergeStyle(options.baseStyle, segment.style);
|
|
1011
|
-
let pieceStart = segmentStart;
|
|
1012
|
-
forEachTextCellSegment(segment.text, (piece) => {
|
|
1013
|
-
if (used >= options.w) return false;
|
|
1014
|
-
const pieceEnd = pieceStart + piece.cells;
|
|
1015
|
-
const visibleStart = Math.max(pieceStart, clipStart);
|
|
1016
|
-
const visibleEnd = Math.min(pieceEnd, clipEnd);
|
|
1017
|
-
pieceStart = pieceEnd;
|
|
1018
|
-
if (visibleEnd <= visibleStart) return;
|
|
1019
|
-
const expectedUsed = visibleStart - clipStart;
|
|
1020
|
-
if (expectedUsed > used) {
|
|
1021
|
-
const pad$1 = Math.min(expectedUsed - used, options.w - used);
|
|
1022
|
-
terminal.write(spaces(pad$1), {
|
|
1023
|
-
x: options.x + used,
|
|
1024
|
-
y: options.y,
|
|
1025
|
-
style: options.baseStyle
|
|
1026
|
-
});
|
|
1027
|
-
used += pad$1;
|
|
1028
|
-
}
|
|
1029
|
-
if (visibleStart === pieceStart - piece.cells && visibleEnd === pieceEnd) {
|
|
1030
|
-
terminal.write(piece.text, {
|
|
1031
|
-
x: options.x + used,
|
|
1032
|
-
y: options.y,
|
|
1033
|
-
style: segmentStyle
|
|
1034
|
-
});
|
|
1035
|
-
used += piece.cells;
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
|
-
const pad = Math.min(visibleEnd - visibleStart, options.w - used);
|
|
1039
|
-
if (pad > 0) {
|
|
1040
|
-
terminal.write(spaces(pad), {
|
|
1041
|
-
x: options.x + used,
|
|
1042
|
-
y: options.y,
|
|
1043
|
-
style: segmentStyle
|
|
1044
|
-
});
|
|
1045
|
-
used += pad;
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
if (used < options.w && options.clear !== false) terminal.write(spaces(options.w - used), {
|
|
1050
|
-
x: options.x + used,
|
|
1051
|
-
y: options.y,
|
|
1052
|
-
style: options.baseStyle
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
//#endregion
|
|
1057
|
-
//#region src/vue/components/TVirtualMarkdown.ts
|
|
1058
|
-
function clamp(n, min, max) {
|
|
1059
|
-
return Math.max(min, Math.min(max, n));
|
|
1060
|
-
}
|
|
1061
|
-
function markdownStyleSignature(style) {
|
|
1062
|
-
if (!style) return "";
|
|
1063
|
-
return [
|
|
1064
|
-
style.fg ?? "",
|
|
1065
|
-
style.bg ?? "",
|
|
1066
|
-
style.bold ? "1" : "0",
|
|
1067
|
-
style.dim ? "1" : "0",
|
|
1068
|
-
style.italic ? "1" : "0",
|
|
1069
|
-
style.underline ? "1" : "0",
|
|
1070
|
-
style.inverse ? "1" : "0",
|
|
1071
|
-
style.href ?? ""
|
|
1072
|
-
].join("");
|
|
1073
|
-
}
|
|
1074
|
-
function markdownRowSignature(row) {
|
|
1075
|
-
if (!row) return "";
|
|
1076
|
-
return [
|
|
1077
|
-
row.key,
|
|
1078
|
-
row.plainText,
|
|
1079
|
-
row.segments.map((segment) => `${segment.text}\u0001${segment.cells}\u0001${markdownStyleSignature(segment.style)}`).join("")
|
|
1080
|
-
].join("");
|
|
1081
|
-
}
|
|
1082
|
-
function getWheelScrollInput(e) {
|
|
1083
|
-
const deltaY = Number(e.deltaY ?? 0);
|
|
1084
|
-
const deltaMode = typeof e.deltaMode === "number" ? e.deltaMode : void 0;
|
|
1085
|
-
if (Number.isInteger(deltaY) && deltaY !== 0 && Math.abs(deltaY) >= 100 && Math.abs(deltaY) % 100 === 0 && deltaMode == null) return {
|
|
1086
|
-
deltaY: deltaY / 100,
|
|
1087
|
-
mode: "line"
|
|
1088
|
-
};
|
|
1089
|
-
if (deltaMode === 1) return {
|
|
1090
|
-
deltaY,
|
|
1091
|
-
mode: "line"
|
|
1092
|
-
};
|
|
1093
|
-
if (deltaMode === 0) return {
|
|
1094
|
-
deltaY,
|
|
1095
|
-
mode: "pixel"
|
|
1096
|
-
};
|
|
1097
|
-
return {
|
|
1098
|
-
deltaY,
|
|
1099
|
-
mode: "auto"
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
const TVirtualMarkdown = defineComponent({
|
|
1103
|
-
name: "TVirtualMarkdown",
|
|
1104
|
-
props: {
|
|
1105
|
-
x: {
|
|
1106
|
-
type: Number,
|
|
1107
|
-
required: true
|
|
1108
|
-
},
|
|
1109
|
-
y: {
|
|
1110
|
-
type: Number,
|
|
1111
|
-
required: true
|
|
1112
|
-
},
|
|
1113
|
-
w: {
|
|
1114
|
-
type: Number,
|
|
1115
|
-
required: true
|
|
1116
|
-
},
|
|
1117
|
-
h: {
|
|
1118
|
-
type: Number,
|
|
1119
|
-
required: true
|
|
1120
|
-
},
|
|
1121
|
-
zIndex: {
|
|
1122
|
-
type: Number,
|
|
1123
|
-
default: 0
|
|
1124
|
-
},
|
|
1125
|
-
content: {
|
|
1126
|
-
type: String,
|
|
1127
|
-
default: ""
|
|
1128
|
-
},
|
|
1129
|
-
blocks: {
|
|
1130
|
-
type: Array,
|
|
1131
|
-
default: void 0
|
|
1132
|
-
},
|
|
1133
|
-
scrollTop: {
|
|
1134
|
-
type: Number,
|
|
1135
|
-
default: 0
|
|
1136
|
-
},
|
|
1137
|
-
style: {
|
|
1138
|
-
type: Object,
|
|
1139
|
-
default: void 0
|
|
1140
|
-
},
|
|
1141
|
-
final: {
|
|
1142
|
-
type: Boolean,
|
|
1143
|
-
default: true
|
|
1144
|
-
},
|
|
1145
|
-
streaming: {
|
|
1146
|
-
type: Boolean,
|
|
1147
|
-
default: false
|
|
1148
|
-
},
|
|
1149
|
-
autoFocus: {
|
|
1150
|
-
type: Boolean,
|
|
1151
|
-
default: false
|
|
1152
|
-
},
|
|
1153
|
-
selectable: {
|
|
1154
|
-
type: Boolean,
|
|
1155
|
-
default: true
|
|
1156
|
-
},
|
|
1157
|
-
customHtmlTags: {
|
|
1158
|
-
type: Array,
|
|
1159
|
-
default: void 0
|
|
1160
|
-
},
|
|
1161
|
-
theme: {
|
|
1162
|
-
type: Object,
|
|
1163
|
-
default: void 0
|
|
1164
|
-
}
|
|
1165
|
-
},
|
|
1166
|
-
emits: [
|
|
1167
|
-
"update:scrollTop",
|
|
1168
|
-
"scroll",
|
|
1169
|
-
"focus",
|
|
1170
|
-
"blur",
|
|
1171
|
-
"keydown"
|
|
1172
|
-
],
|
|
1173
|
-
setup(props, { emit }) {
|
|
1174
|
-
const instance = getCurrentInstance();
|
|
1175
|
-
const { terminal, defaultStyle, events, scheduler, selection, widthProvider } = useTerminal();
|
|
1176
|
-
const layout = useLayout();
|
|
1177
|
-
const { visible, rootProps } = useVisibility();
|
|
1178
|
-
const parentEventZ = inject(EventZIndexContextKey, computed(() => 0));
|
|
1179
|
-
const eventZ = computed(() => (parentEventZ.value ?? 0) + (props.zIndex ?? 0));
|
|
1180
|
-
const internalScrollTop = ref(0);
|
|
1181
|
-
const documentVersion = ref(0);
|
|
1182
|
-
const rows = shallowRef(markRaw([]));
|
|
1183
|
-
let pendingSelectionScrollFocusRemap = false;
|
|
1184
|
-
const wheelState = createWheelScrollState();
|
|
1185
|
-
let builtOnce = false;
|
|
1186
|
-
let rebuildVersion = 0;
|
|
1187
|
-
let layoutCache;
|
|
1188
|
-
let alive = true;
|
|
1189
|
-
const parser = shallowRef(markRaw(createTuiMarkdownParser({
|
|
1190
|
-
streaming: props.streaming,
|
|
1191
|
-
customHtmlTags: props.customHtmlTags
|
|
1192
|
-
})));
|
|
1193
|
-
watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
|
|
1194
|
-
parser.value = markRaw(createTuiMarkdownParser({
|
|
1195
|
-
streaming: props.streaming,
|
|
1196
|
-
customHtmlTags: props.customHtmlTags
|
|
1197
|
-
}));
|
|
1198
|
-
});
|
|
1199
|
-
function rebuildRows() {
|
|
1200
|
-
const prevScrollTop = internalScrollTop.value;
|
|
1201
|
-
const prevVisibleRows = visibleRowSignatures(rows.value, prevScrollTop);
|
|
1202
|
-
const nextLayout = withTextWidthProvider(widthProvider, () => {
|
|
1203
|
-
const blocks = props.blocks ?? buildMarkdownBlocks(props.content, parser.value, {
|
|
1204
|
-
final: props.final,
|
|
1205
|
-
theme: props.theme
|
|
1206
|
-
}).blocks;
|
|
1207
|
-
return layoutMarkdownBlocksCached(blocks, props.w, layoutCache);
|
|
1208
|
-
});
|
|
1209
|
-
layoutCache = markRaw(nextLayout.cache);
|
|
1210
|
-
const nextRows = nextLayout.rows;
|
|
1211
|
-
rows.value = markRaw(nextRows);
|
|
1212
|
-
reconcileScrollTop();
|
|
1213
|
-
const nextScrollTop = internalScrollTop.value;
|
|
1214
|
-
const nextVisibleRows = visibleRowSignatures(nextRows, nextScrollTop);
|
|
1215
|
-
const visibleChanged = prevVisibleRows.length !== nextVisibleRows.length || prevVisibleRows.some((row, index) => row !== nextVisibleRows[index]);
|
|
1216
|
-
if (!builtOnce || prevScrollTop !== nextScrollTop || visibleChanged) {
|
|
1217
|
-
documentVersion.value++;
|
|
1218
|
-
selection.refresh();
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
function scheduleRebuild() {
|
|
1222
|
-
const currentVersion = ++rebuildVersion;
|
|
1223
|
-
if (!builtOnce || !props.streaming) {
|
|
1224
|
-
builtOnce = true;
|
|
1225
|
-
rebuildRows();
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
scheduler.queueFrameTask({
|
|
1229
|
-
id: `TVirtualMarkdown:${instance?.uid ?? "unknown"}:markdown`,
|
|
1230
|
-
reason: props.streaming ? "stream" : "data",
|
|
1231
|
-
priority: props.streaming ? "low" : "normal",
|
|
1232
|
-
sync: false,
|
|
1233
|
-
run: () => {
|
|
1234
|
-
if (!alive) return;
|
|
1235
|
-
if (currentVersion !== rebuildVersion) return;
|
|
1236
|
-
rebuildRows();
|
|
1237
|
-
}
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
const fullRect = computed(() => translateRect({
|
|
1241
|
-
x: props.x,
|
|
1242
|
-
y: props.y,
|
|
1243
|
-
w: props.w,
|
|
1244
|
-
h: props.h
|
|
1245
|
-
}, layout.originX, layout.originY));
|
|
1246
|
-
const absRect = computed(() => {
|
|
1247
|
-
const translated = fullRect.value;
|
|
1248
|
-
if (!layout.clipRect) return translated;
|
|
1249
|
-
return intersectRect(translated, layout.clipRect) ?? {
|
|
1250
|
-
x: 0,
|
|
1251
|
-
y: 0,
|
|
1252
|
-
w: 0,
|
|
1253
|
-
h: 0
|
|
1254
|
-
};
|
|
1255
|
-
});
|
|
1256
|
-
function normalizedRect() {
|
|
1257
|
-
return normalizeCellRect(absRect.value);
|
|
1258
|
-
}
|
|
1259
|
-
function normalizedFullRect() {
|
|
1260
|
-
return normalizeCellRect(fullRect.value);
|
|
1261
|
-
}
|
|
1262
|
-
function clipOffsets() {
|
|
1263
|
-
const full = normalizedFullRect();
|
|
1264
|
-
const clip = normalizedRect();
|
|
1265
|
-
return {
|
|
1266
|
-
x: Math.max(0, clip.x - full.x),
|
|
1267
|
-
y: Math.max(0, clip.y - full.y)
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1270
|
-
function maxScrollTop() {
|
|
1271
|
-
const clip = normalizedRect();
|
|
1272
|
-
const { y: clipY } = clipOffsets();
|
|
1273
|
-
return Math.max(0, rows.value.length - (clipY + clip.h));
|
|
1274
|
-
}
|
|
1275
|
-
function visibleRowSignatures(sourceRows, scrollTop) {
|
|
1276
|
-
const clip = normalizedRect();
|
|
1277
|
-
const { y: clipY } = clipOffsets();
|
|
1278
|
-
const start = Math.max(0, scrollTop + clipY);
|
|
1279
|
-
const end = Math.max(start, start + clip.h);
|
|
1280
|
-
const out = [];
|
|
1281
|
-
for (let index = start; index < end; index++) out.push(markdownRowSignature(sourceRows[index]));
|
|
1282
|
-
return out;
|
|
1283
|
-
}
|
|
1284
|
-
function hasControlledScrollTop() {
|
|
1285
|
-
return Object.prototype.hasOwnProperty.call(instance?.vnode.props ?? {}, "scrollTop");
|
|
1286
|
-
}
|
|
1287
|
-
function applyControlledScrollTop(next, remapSelectionFocus = false) {
|
|
1288
|
-
const desired = Math.floor(Number(next) || 0);
|
|
1289
|
-
const clamped = clamp(desired, 0, maxScrollTop());
|
|
1290
|
-
const changed = internalScrollTop.value !== clamped;
|
|
1291
|
-
if (changed) internalScrollTop.value = clamped;
|
|
1292
|
-
if (desired !== clamped) {
|
|
1293
|
-
emit("update:scrollTop", clamped);
|
|
1294
|
-
emit("scroll", clamped);
|
|
1295
|
-
}
|
|
1296
|
-
if (changed || remapSelectionFocus) selection.refresh(remapSelectionFocus ? { remapFocus: true } : void 0);
|
|
1297
|
-
}
|
|
1298
|
-
function setScrollTop(next, emitChange = true, refreshOptions) {
|
|
1299
|
-
const desired = Math.floor(Number(next) || 0);
|
|
1300
|
-
const clamped = clamp(desired, 0, maxScrollTop());
|
|
1301
|
-
const changed = internalScrollTop.value !== clamped;
|
|
1302
|
-
if (changed) internalScrollTop.value = clamped;
|
|
1303
|
-
if (emitChange && (changed || refreshOptions?.emitClampEvenIfUnchanged && desired !== clamped)) {
|
|
1304
|
-
emit("update:scrollTop", clamped);
|
|
1305
|
-
emit("scroll", clamped);
|
|
1306
|
-
}
|
|
1307
|
-
if (changed || refreshOptions?.remapSelectionFocus) selection.refresh(refreshOptions?.remapSelectionFocus ? { remapFocus: true } : void 0);
|
|
1308
|
-
}
|
|
1309
|
-
function reconcileScrollTop() {
|
|
1310
|
-
const desired = hasControlledScrollTop() ? Math.floor(Number(props.scrollTop) || 0) : internalScrollTop.value;
|
|
1311
|
-
const clamped = clamp(desired, 0, maxScrollTop());
|
|
1312
|
-
if (internalScrollTop.value === clamped) return;
|
|
1313
|
-
internalScrollTop.value = clamped;
|
|
1314
|
-
if (desired !== clamped) {
|
|
1315
|
-
emit("update:scrollTop", clamped);
|
|
1316
|
-
emit("scroll", clamped);
|
|
1317
|
-
}
|
|
1318
|
-
selection.refresh();
|
|
1319
|
-
}
|
|
1320
|
-
watch(() => props.scrollTop, () => {
|
|
1321
|
-
if (!hasControlledScrollTop()) return;
|
|
1322
|
-
const remap = pendingSelectionScrollFocusRemap;
|
|
1323
|
-
pendingSelectionScrollFocusRemap = false;
|
|
1324
|
-
applyControlledScrollTop(props.scrollTop, remap);
|
|
1325
|
-
});
|
|
1326
|
-
watch([
|
|
1327
|
-
() => props.content,
|
|
1328
|
-
() => props.blocks,
|
|
1329
|
-
() => props.w,
|
|
1330
|
-
() => parser.value,
|
|
1331
|
-
() => props.final,
|
|
1332
|
-
() => markdownThemeSignature(props.theme)
|
|
1333
|
-
], () => {
|
|
1334
|
-
scheduleRebuild();
|
|
1335
|
-
}, { immediate: true });
|
|
1336
|
-
watch([
|
|
1337
|
-
() => props.content,
|
|
1338
|
-
() => props.w,
|
|
1339
|
-
() => props.h,
|
|
1340
|
-
() => absRect.value.h
|
|
1341
|
-
], () => {
|
|
1342
|
-
resetWheelScrollState(wheelState);
|
|
1343
|
-
});
|
|
1344
|
-
watch([
|
|
1345
|
-
() => props.h,
|
|
1346
|
-
() => absRect.value.y,
|
|
1347
|
-
() => absRect.value.h,
|
|
1348
|
-
() => fullRect.value.y,
|
|
1349
|
-
() => fullRect.value.h
|
|
1350
|
-
], () => {
|
|
1351
|
-
reconcileScrollTop();
|
|
1352
|
-
}, { immediate: true });
|
|
1353
|
-
function scrollSelectionBy(delta) {
|
|
1354
|
-
const n = Math.trunc(Number(delta));
|
|
1355
|
-
if (!Number.isFinite(n) || n === 0) return false;
|
|
1356
|
-
if (hasControlledScrollTop()) {
|
|
1357
|
-
const nextTop = clamp(internalScrollTop.value + n, 0, maxScrollTop());
|
|
1358
|
-
if (nextTop === internalScrollTop.value) return false;
|
|
1359
|
-
if (pendingSelectionScrollFocusRemap) return false;
|
|
1360
|
-
pendingSelectionScrollFocusRemap = true;
|
|
1361
|
-
emit("update:scrollTop", nextTop);
|
|
1362
|
-
emit("scroll", nextTop);
|
|
1363
|
-
return true;
|
|
1364
|
-
}
|
|
1365
|
-
const before = internalScrollTop.value;
|
|
1366
|
-
setScrollTop(before + n, true, { remapSelectionFocus: true });
|
|
1367
|
-
return internalScrollTop.value !== before;
|
|
1368
|
-
}
|
|
1369
|
-
function selectionPointForCell(point) {
|
|
1370
|
-
const r = normalizedRect();
|
|
1371
|
-
if (point.x < r.x || point.y < r.y || point.x >= r.x + r.w || point.y >= r.y + r.h) return null;
|
|
1372
|
-
const { x: clipX, y: clipY } = clipOffsets();
|
|
1373
|
-
const visualY = internalScrollTop.value + clipY + (point.y - r.y);
|
|
1374
|
-
if (visualY < 0 || visualY >= rows.value.length) return null;
|
|
1375
|
-
return {
|
|
1376
|
-
x: clamp(clipX + (point.x - r.x), 0, Math.max(0, props.w - 1)),
|
|
1377
|
-
y: visualY
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
|
-
function canHandleSelectionRange(range) {
|
|
1381
|
-
return Boolean(selectionPointForCell(range.anchor) && selectionPointForCell(range.focus));
|
|
1382
|
-
}
|
|
1383
|
-
function textForSelectionRange(range) {
|
|
1384
|
-
const cols = Math.max(1, Math.floor(props.w));
|
|
1385
|
-
return withTextWidthProvider(widthProvider, () => terminalSelectionRowSpans(range, cols, rows.value.length).map((span) => {
|
|
1386
|
-
const text = sliceByCellsRange(rows.value[span.y]?.plainText ?? "", span.x0, span.x1);
|
|
1387
|
-
return span.x1 >= cols ? text.trimEnd() : text;
|
|
1388
|
-
}).join("\n"));
|
|
1389
|
-
}
|
|
1390
|
-
function visibleSpansForSelectionRange(providerRange, _screenRange) {
|
|
1391
|
-
const r = normalizedRect();
|
|
1392
|
-
const { x: clipX, y: clipY } = clipOffsets();
|
|
1393
|
-
const cols = Math.max(1, Math.floor(props.w));
|
|
1394
|
-
const top = internalScrollTop.value + clipY;
|
|
1395
|
-
const bottom = top + r.h;
|
|
1396
|
-
const providerSpans = terminalSelectionVisibleRowSpans(providerRange, cols, rows.value.length, top, bottom);
|
|
1397
|
-
const result = [];
|
|
1398
|
-
for (const span of providerSpans) {
|
|
1399
|
-
const screenY = r.y + (span.y - top);
|
|
1400
|
-
const screenX0 = r.x + span.x0 - clipX;
|
|
1401
|
-
const screenX1 = r.x + span.x1 - clipX;
|
|
1402
|
-
const x0 = Math.max(r.x, screenX0);
|
|
1403
|
-
const x1 = Math.min(r.x + r.w, screenX1);
|
|
1404
|
-
if (screenY >= r.y && screenY < r.y + r.h && x1 > x0) result.push({
|
|
1405
|
-
y: screenY,
|
|
1406
|
-
x0,
|
|
1407
|
-
x1
|
|
1408
|
-
});
|
|
1409
|
-
}
|
|
1410
|
-
return result;
|
|
1411
|
-
}
|
|
1412
|
-
function onKeydown(event) {
|
|
1413
|
-
emit("keydown", event);
|
|
1414
|
-
const page = Math.max(1, normalizedRect().h);
|
|
1415
|
-
if (event.key === "ArrowUp") {
|
|
1416
|
-
event.preventDefault();
|
|
1417
|
-
setScrollTop(internalScrollTop.value - 1);
|
|
1418
|
-
return;
|
|
1419
|
-
}
|
|
1420
|
-
if (event.key === "ArrowDown") {
|
|
1421
|
-
event.preventDefault();
|
|
1422
|
-
setScrollTop(internalScrollTop.value + 1);
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
if (event.key === "PageUp") {
|
|
1426
|
-
event.preventDefault();
|
|
1427
|
-
setScrollTop(internalScrollTop.value - page);
|
|
1428
|
-
return;
|
|
1429
|
-
}
|
|
1430
|
-
if (event.key === "PageDown") {
|
|
1431
|
-
event.preventDefault();
|
|
1432
|
-
setScrollTop(internalScrollTop.value + page);
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
if (event.key === "Home") {
|
|
1436
|
-
event.preventDefault();
|
|
1437
|
-
setScrollTop(0);
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
if (event.key === "End") {
|
|
1441
|
-
event.preventDefault();
|
|
1442
|
-
setScrollTop(maxScrollTop());
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
const selectionTextProvider = {
|
|
1446
|
-
id: `TVirtualMarkdown:${instance?.uid ?? "unknown"}:selection-text`,
|
|
1447
|
-
get rect() {
|
|
1448
|
-
return normalizedRect();
|
|
1449
|
-
},
|
|
1450
|
-
canHandle: canHandleSelectionRange,
|
|
1451
|
-
pointForCell: selectionPointForCell,
|
|
1452
|
-
getText: textForSelectionRange,
|
|
1453
|
-
getVisibleSpans: visibleSpansForSelectionRange
|
|
1454
|
-
};
|
|
1455
|
-
const unregisterSelectionTextProvider = selection.registerTextProvider(selectionTextProvider);
|
|
1456
|
-
onBeforeUnmount(unregisterSelectionTextProvider);
|
|
1457
|
-
const eventNode = useTerminalNode(() => ({
|
|
1458
|
-
rect: normalizedRect(),
|
|
1459
|
-
zIndex: eventZ.value,
|
|
1460
|
-
visible: visible.value,
|
|
1461
|
-
focusable: true,
|
|
1462
|
-
selectable: props.selectable,
|
|
1463
|
-
selectionScrollBy: scrollSelectionBy,
|
|
1464
|
-
handlers: {
|
|
1465
|
-
click: (_event) => {},
|
|
1466
|
-
wheel: (event) => {
|
|
1467
|
-
const { deltaY, mode } = getWheelScrollInput(event);
|
|
1468
|
-
if (!deltaY) return;
|
|
1469
|
-
const now = typeof event.time === "number" ? event.time : typeof event.timeStamp === "number" ? event.timeStamp : Date.now();
|
|
1470
|
-
const { nextTop, dir } = applyWheelScroll(wheelState, deltaY, internalScrollTop.value, maxScrollTop(), now, mode, { disableAcceleration: mode === "pixel" });
|
|
1471
|
-
if (!dir || nextTop === internalScrollTop.value) return;
|
|
1472
|
-
event.preventDefault?.();
|
|
1473
|
-
setScrollTop(nextTop);
|
|
1474
|
-
},
|
|
1475
|
-
focus: () => {
|
|
1476
|
-
emit("focus");
|
|
1477
|
-
},
|
|
1478
|
-
blur: () => {
|
|
1479
|
-
emit("blur");
|
|
1480
|
-
},
|
|
1481
|
-
keydown: onKeydown
|
|
1482
|
-
}
|
|
1483
|
-
}));
|
|
1484
|
-
watchEffect(() => {
|
|
1485
|
-
if (!props.autoFocus || !visible.value) return;
|
|
1486
|
-
const manager = events.value;
|
|
1487
|
-
const nodeId = eventNode.id.value;
|
|
1488
|
-
if (!nodeId) return;
|
|
1489
|
-
if (!manager) return;
|
|
1490
|
-
if (manager.getFocused() === nodeId) return;
|
|
1491
|
-
manager.focus(nodeId);
|
|
1492
|
-
});
|
|
1493
|
-
onBeforeUnmount(() => {
|
|
1494
|
-
alive = false;
|
|
1495
|
-
rebuildVersion++;
|
|
1496
|
-
});
|
|
1497
|
-
useRenderNode(() => ({
|
|
1498
|
-
zIndex: props.zIndex,
|
|
1499
|
-
rect: visible.value ? normalizedRect() : {
|
|
1500
|
-
x: 0,
|
|
1501
|
-
y: 0,
|
|
1502
|
-
w: 0,
|
|
1503
|
-
h: 0
|
|
1504
|
-
},
|
|
1505
|
-
deps: [
|
|
1506
|
-
visible.value,
|
|
1507
|
-
absRect.value,
|
|
1508
|
-
fullRect.value,
|
|
1509
|
-
internalScrollTop.value,
|
|
1510
|
-
props.style,
|
|
1511
|
-
defaultStyle.value,
|
|
1512
|
-
documentVersion.value
|
|
1513
|
-
],
|
|
1514
|
-
paint: (dirtyRows) => {
|
|
1515
|
-
withTextWidthProvider(widthProvider, () => {
|
|
1516
|
-
if (!visible.value) return;
|
|
1517
|
-
const r = normalizedRect();
|
|
1518
|
-
if (r.w <= 0 || r.h <= 0) return;
|
|
1519
|
-
const baseStyle = props.style ?? defaultStyle.value;
|
|
1520
|
-
const { x: clipX, y: clipY } = clipOffsets();
|
|
1521
|
-
const paintRow = (y) => {
|
|
1522
|
-
if (y < r.y || y >= r.y + r.h) return;
|
|
1523
|
-
const visualIndex = internalScrollTop.value + clipY + (y - r.y);
|
|
1524
|
-
paintMarkdownVisualRow(terminal, rows.value[visualIndex], {
|
|
1525
|
-
x: r.x,
|
|
1526
|
-
y,
|
|
1527
|
-
w: r.w,
|
|
1528
|
-
clipStart: clipX,
|
|
1529
|
-
baseStyle,
|
|
1530
|
-
clear: true
|
|
1531
|
-
});
|
|
1532
|
-
};
|
|
1533
|
-
if (dirtyRows?.length) {
|
|
1534
|
-
for (const y of dirtyRows) paintRow(y);
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
for (let y = r.y; y < r.y + r.h; y++) paintRow(y);
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
}));
|
|
1541
|
-
return () => h("span", rootProps);
|
|
1542
|
-
}
|
|
1543
|
-
});
|
|
1544
|
-
|
|
1545
|
-
//#endregion
|
|
1546
|
-
//#region src/vue/markdown/block-source.ts
|
|
1547
|
-
function withBlockKeyPrefix(block, prefix, index) {
|
|
1548
|
-
const key = `${prefix}:${index}:${block.key}`;
|
|
1549
|
-
switch (block.type) {
|
|
1550
|
-
case "inline": return {
|
|
1551
|
-
...block,
|
|
1552
|
-
key
|
|
1553
|
-
};
|
|
1554
|
-
case "code_block": return {
|
|
1555
|
-
...block,
|
|
1556
|
-
key
|
|
1557
|
-
};
|
|
1558
|
-
case "thematic_break": return {
|
|
1559
|
-
...block,
|
|
1560
|
-
key
|
|
1561
|
-
};
|
|
1562
|
-
case "table": return {
|
|
1563
|
-
...block,
|
|
1564
|
-
key
|
|
1565
|
-
};
|
|
1566
|
-
case "blank": return {
|
|
1567
|
-
...block,
|
|
1568
|
-
key
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
function parseBlockGroup(text, key, parser, theme, final) {
|
|
1573
|
-
const { blocks } = buildMarkdownBlocks(text, parser, {
|
|
1574
|
-
final,
|
|
1575
|
-
theme
|
|
1576
|
-
});
|
|
1577
|
-
return {
|
|
1578
|
-
key,
|
|
1579
|
-
blocks: blocks.map((block, index) => withBlockKeyPrefix(block, key, index))
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
function shouldInsertGap(previous, next) {
|
|
1583
|
-
return previous != null && next != null && previous.type !== "blank" && next.type !== "blank";
|
|
1584
|
-
}
|
|
1585
|
-
function mergeGroups(groups) {
|
|
1586
|
-
const out = [];
|
|
1587
|
-
for (const group of groups) {
|
|
1588
|
-
if (!group.blocks.length) continue;
|
|
1589
|
-
if (shouldInsertGap(out[out.length - 1], group.blocks[0])) out.push({
|
|
1590
|
-
type: "blank",
|
|
1591
|
-
key: `${group.key}:gap`
|
|
1592
|
-
});
|
|
1593
|
-
out.push(...group.blocks);
|
|
1594
|
-
}
|
|
1595
|
-
return out;
|
|
1596
|
-
}
|
|
1597
|
-
function createMarkdownBlockSource(options) {
|
|
1598
|
-
const parser = options?.parser ?? createTuiMarkdownParser({
|
|
1599
|
-
streaming: true,
|
|
1600
|
-
customHtmlTags: options?.customHtmlTags
|
|
1601
|
-
});
|
|
1602
|
-
const theme = options?.theme;
|
|
1603
|
-
const finalizedGroups = [];
|
|
1604
|
-
let tailText = "";
|
|
1605
|
-
let nextGroupIndex = 0;
|
|
1606
|
-
let version = 0;
|
|
1607
|
-
let cachedVersion = -1;
|
|
1608
|
-
let cachedBlocks = [];
|
|
1609
|
-
const invalidate = () => {
|
|
1610
|
-
version++;
|
|
1611
|
-
cachedVersion = -1;
|
|
1612
|
-
};
|
|
1613
|
-
const currentGroups = () => {
|
|
1614
|
-
if (!tailText) return finalizedGroups;
|
|
1615
|
-
return [...finalizedGroups, parseBlockGroup(tailText, `md-tail-${nextGroupIndex}`, parser, theme, false)];
|
|
1616
|
-
};
|
|
1617
|
-
const blocks = () => {
|
|
1618
|
-
if (cachedVersion === version) return cachedBlocks;
|
|
1619
|
-
cachedBlocks = mergeGroups(currentGroups());
|
|
1620
|
-
cachedVersion = version;
|
|
1621
|
-
return cachedBlocks;
|
|
1622
|
-
};
|
|
1623
|
-
const snapshot = () => ({
|
|
1624
|
-
version,
|
|
1625
|
-
blocks: blocks()
|
|
1626
|
-
});
|
|
1627
|
-
return {
|
|
1628
|
-
get version() {
|
|
1629
|
-
return version;
|
|
1630
|
-
},
|
|
1631
|
-
get blocks() {
|
|
1632
|
-
return blocks();
|
|
1633
|
-
},
|
|
1634
|
-
appendDelta(text) {
|
|
1635
|
-
if (!text) return;
|
|
1636
|
-
tailText += text;
|
|
1637
|
-
invalidate();
|
|
1638
|
-
},
|
|
1639
|
-
replaceTailBlock(text) {
|
|
1640
|
-
if (tailText === text) return;
|
|
1641
|
-
tailText = text;
|
|
1642
|
-
invalidate();
|
|
1643
|
-
},
|
|
1644
|
-
finalizeBlock() {
|
|
1645
|
-
if (!tailText) return;
|
|
1646
|
-
const key = `md-block-${nextGroupIndex++}`;
|
|
1647
|
-
finalizedGroups.push(parseBlockGroup(tailText, key, parser, theme, true));
|
|
1648
|
-
tailText = "";
|
|
1649
|
-
invalidate();
|
|
1650
|
-
},
|
|
1651
|
-
clear() {
|
|
1652
|
-
if (!tailText && finalizedGroups.length === 0) return;
|
|
1653
|
-
finalizedGroups.length = 0;
|
|
1654
|
-
tailText = "";
|
|
1655
|
-
nextGroupIndex = 0;
|
|
1656
|
-
invalidate();
|
|
1657
|
-
},
|
|
1658
|
-
snapshot
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
//#endregion
|
|
1663
|
-
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, createMarkdownBlockSource, createTuiMarkdownParser, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };
|