@simon_he/vue-tui 1.1.2 → 1.1.4
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/dist/{TMermaidText-frVr8Ysb.js → TMermaidText-wLnMMvVS.js} +1 -1
- package/dist/{TSelect-tJ87rKPM.js → TSelect-CMBOOFwK.js} +4 -4
- package/dist/{TTree-CGdE-CYu.js → TTree-kk8ntrpg.js} +6 -5
- package/dist/agent/mermaid.d.cts +2 -2
- package/dist/agent/mermaid.d.ts +2 -2
- package/dist/agent.cjs +3896 -13616
- package/dist/agent.js +25 -82
- package/dist/{block-source-CrlmaBM5.js → block-source-D3YGTErb.js} +447 -326
- package/dist/cli.cjs +52 -15
- package/dist/cli.js +69 -14
- package/dist/core.js +3 -3
- package/dist/{hyperlink-_1FAj7Wq.js → create-terminal-Db_-UGVQ.js} +474 -102
- package/dist/{dom-renderer-DXx8fWzD.js → dom-renderer-BfwAEQMf.js} +3 -2
- package/dist/experimental.cjs +99 -66
- package/dist/experimental.js +20 -74
- package/dist/hyperlink-clbb_ha_.js +100 -0
- package/dist/index.js +8 -8
- package/dist/{linkify-D68T7rdW.js → linkify-CL9wwnZN.js} +2 -2
- package/dist/markdown.cjs +3376 -13056
- package/dist/markdown.d.cts +4 -0
- package/dist/markdown.d.ts +4 -0
- package/dist/markdown.js +79 -8
- package/dist/math-image-BZhmfUeu.js +308 -0
- package/dist/mermaid-uEfRItDe.js +1205 -0
- package/dist/mermaid.cjs +1089 -3
- package/dist/mermaid.d.cts +4 -0
- package/dist/mermaid.d.ts +4 -0
- package/dist/mermaid.js +7 -5
- package/dist/{rect-DUpeDfBg.js → rect-JzWMJxxc.js} +1 -1
- package/dist/renderer/cli/stdout-renderer.d.ts +44 -0
- package/dist/renderer/terminal-graphics.d.ts +44 -0
- package/dist/renderer-dom.js +4 -4
- package/dist/{terminal-graphics-Bbe83wSr.js → terminal-graphics-DWZrl3oh.js} +85 -2
- package/dist/{tlog-theme-Bul2Na3u.js → tlog-theme-DqnF_zNe.js} +5 -4
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +1 -1
- package/dist/vue/components/TMarkdownText.d.ts +70 -2
- package/dist/vue/components/TMermaidImage.d.ts +733 -0
- package/dist/vue/components/TMermaidText.d.ts +2 -2
- package/dist/vue/components/TVirtualMarkdown.d.ts +70 -2
- package/dist/vue/markdown/ast.d.ts +5 -0
- package/dist/vue/markdown/document.d.ts +3 -0
- package/dist/vue/markdown/math-image.d.ts +68 -0
- package/dist/vue/markdown/math.d.ts +2 -0
- package/dist/vue/markdown/theme.d.ts +12 -0
- package/dist/vue/markdown/types.d.ts +12 -2
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +4 -4
- package/dist/vue/mermaid/mermaid-image.d.ts +65 -0
- package/dist/vue.js +9 -9
- package/package.json +20 -3
- package/dist/mermaid-BNhM1jAZ.js +0 -123
- package/dist/width-GqllnV8C.js +0 -474
- /package/dist/{ansi-palette-CH6jc2Z7.js → ansi-palette-BHF1rN7R.js} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { hasEncodedControl, sanitizeDomHref } from "./hyperlink-
|
|
2
|
-
import { EventZIndexContextKey, currentTextWidthProvider, forEachTextCellSegment, intersectRect, normalizeCellRect, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-
|
|
1
|
+
import { hasEncodedControl, sanitizeDomHref } from "./hyperlink-clbb_ha_.js";
|
|
2
|
+
import { EventZIndexContextKey, currentTextWidthProvider, forEachTextCellSegment, intersectRect, normalizeCellRect, repeatChar, sanitizeInlineText, sanitizeTextBlock, sliceByCellsRange, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-JzWMJxxc.js";
|
|
3
3
|
import { terminalSelectionRowSpans, terminalSelectionVisibleRowSpans } from "./terminal-selection-4W1SJj4c.js";
|
|
4
4
|
import { createDebugLogger, isDebugEnabled } from "./debug-logger-CmsGI4of.js";
|
|
5
5
|
import { applyWheelScroll, createWheelScrollState, resetWheelScrollState } from "./wheel-scroll-DASkEZr-.js";
|
|
6
|
-
import { createIterm2InlineImageSequence, createKittyDeleteGraphicsSequence, createKittyGraphicsSequence, createKittyPlacementSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, isTerminalGraphicsProtocol, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-
|
|
6
|
+
import { createIterm2InlineImageSequence, createKittyDeleteGraphicsSequence, createKittyGraphicsSequence, createKittyPlacementSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, isTerminalGraphicsProtocol, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-DWZrl3oh.js";
|
|
7
|
+
import { enqueueMarkdownMathImages, getCachedMarkdownMathImage, isMarkdownMathImageRendererReady, loadMarkdownMathImageRenderer, resolveMarkdownMathColor, subscribeMarkdownMathImage } from "./math-image-BZhmfUeu.js";
|
|
7
8
|
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, ref, shallowRef, watch, watchEffect } from "vue";
|
|
8
|
-
import katex from "katex";
|
|
9
9
|
import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
|
|
10
10
|
|
|
11
11
|
//#region src/vue/markdown/image.ts
|
|
@@ -114,182 +114,6 @@ function readMarkdownImageDimensions(base64) {
|
|
|
114
114
|
return null;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
//#endregion
|
|
118
|
-
//#region src/vue/markdown/math.ts
|
|
119
|
-
const KATEX_TEXT_OPTIONS = Object.freeze({
|
|
120
|
-
output: "mathml",
|
|
121
|
-
throwOnError: false,
|
|
122
|
-
strict: "ignore",
|
|
123
|
-
trust: false,
|
|
124
|
-
maxSize: 20,
|
|
125
|
-
maxExpand: 200
|
|
126
|
-
});
|
|
127
|
-
const KATEX_VALIDATE_OPTIONS = Object.freeze({
|
|
128
|
-
...KATEX_TEXT_OPTIONS,
|
|
129
|
-
throwOnError: true
|
|
130
|
-
});
|
|
131
|
-
const TEX_SYMBOLS = Object.freeze({
|
|
132
|
-
alpha: "α",
|
|
133
|
-
beta: "β",
|
|
134
|
-
gamma: "γ",
|
|
135
|
-
delta: "δ",
|
|
136
|
-
epsilon: "ε",
|
|
137
|
-
theta: "θ",
|
|
138
|
-
lambda: "λ",
|
|
139
|
-
mu: "μ",
|
|
140
|
-
pi: "π",
|
|
141
|
-
rho: "ρ",
|
|
142
|
-
sigma: "σ",
|
|
143
|
-
tau: "τ",
|
|
144
|
-
phi: "φ",
|
|
145
|
-
omega: "ω",
|
|
146
|
-
Gamma: "Γ",
|
|
147
|
-
Delta: "Δ",
|
|
148
|
-
Theta: "Θ",
|
|
149
|
-
Lambda: "Λ",
|
|
150
|
-
Pi: "Π",
|
|
151
|
-
Sigma: "Σ",
|
|
152
|
-
Phi: "Φ",
|
|
153
|
-
Omega: "Ω",
|
|
154
|
-
int: "∫",
|
|
155
|
-
sum: "∑",
|
|
156
|
-
prod: "∏",
|
|
157
|
-
infty: "∞",
|
|
158
|
-
infinity: "∞",
|
|
159
|
-
pm: "±",
|
|
160
|
-
times: "×",
|
|
161
|
-
cdot: "·",
|
|
162
|
-
div: "÷",
|
|
163
|
-
le: "≤",
|
|
164
|
-
leq: "≤",
|
|
165
|
-
ge: "≥",
|
|
166
|
-
geq: "≥",
|
|
167
|
-
neq: "≠",
|
|
168
|
-
ne: "≠",
|
|
169
|
-
approx: "≈",
|
|
170
|
-
equiv: "≡",
|
|
171
|
-
to: "→",
|
|
172
|
-
rightarrow: "→",
|
|
173
|
-
leftarrow: "←",
|
|
174
|
-
leftrightarrow: "↔",
|
|
175
|
-
partial: "∂",
|
|
176
|
-
nabla: "∇",
|
|
177
|
-
sqrt: "√"
|
|
178
|
-
});
|
|
179
|
-
const SUPPORTED_COMMANDS = new Set([
|
|
180
|
-
...Object.keys(TEX_SYMBOLS),
|
|
181
|
-
"frac",
|
|
182
|
-
"sqrt"
|
|
183
|
-
]);
|
|
184
|
-
function decodeXmlEntities(value) {
|
|
185
|
-
return value.replace(/&(?:#(\d+)|#x([0-9a-f]+)|([a-z]+));/gi, (match, dec, hex, named) => {
|
|
186
|
-
if (dec) return String.fromCodePoint(Number(dec));
|
|
187
|
-
if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
188
|
-
switch (String(named).toLowerCase()) {
|
|
189
|
-
case "amp": return "&";
|
|
190
|
-
case "lt": return "<";
|
|
191
|
-
case "gt": return ">";
|
|
192
|
-
case "quot": return "\"";
|
|
193
|
-
case "apos": return "'";
|
|
194
|
-
default: return match;
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
function mathmlTagName(tag) {
|
|
199
|
-
const start = tag.startsWith("/") ? 1 : 0;
|
|
200
|
-
let end = start;
|
|
201
|
-
while (end < tag.length) {
|
|
202
|
-
const ch = tag[end];
|
|
203
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "/") break;
|
|
204
|
-
end++;
|
|
205
|
-
}
|
|
206
|
-
return tag.slice(start, end).toLowerCase();
|
|
207
|
-
}
|
|
208
|
-
function textFromKatexMathml(html) {
|
|
209
|
-
let readable = "";
|
|
210
|
-
let offset = 0;
|
|
211
|
-
const lowerHtml = html.toLowerCase();
|
|
212
|
-
while (offset < html.length) {
|
|
213
|
-
const tagStart = html.indexOf("<", offset);
|
|
214
|
-
if (tagStart < 0) {
|
|
215
|
-
readable += html.slice(offset);
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
readable += html.slice(offset, tagStart);
|
|
219
|
-
const tagEnd = html.indexOf(">", tagStart + 1);
|
|
220
|
-
if (tagEnd < 0) break;
|
|
221
|
-
const tag = html.slice(tagStart + 1, tagEnd).trim();
|
|
222
|
-
const tagName = mathmlTagName(tag);
|
|
223
|
-
const closing = tag.startsWith("/");
|
|
224
|
-
if (!closing && tagName === "annotation") {
|
|
225
|
-
const annotationEnd = lowerHtml.indexOf("</annotation>", tagEnd + 1);
|
|
226
|
-
offset = annotationEnd < 0 ? tagEnd + 1 : annotationEnd + 13;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (!closing && tagName === "mfrac") readable += "(";
|
|
230
|
-
else if (closing && tagName === "mfrac") readable += ")";
|
|
231
|
-
else if (!closing && tagName === "mspace") readable += " ";
|
|
232
|
-
offset = tagEnd + 1;
|
|
233
|
-
}
|
|
234
|
-
const text = sanitizeInlineText(decodeXmlEntities(readable).replace(/[<>]/g, "").replace(/\s+/g, " ").trim());
|
|
235
|
-
return text;
|
|
236
|
-
}
|
|
237
|
-
function approximateTexToUnicode(tex) {
|
|
238
|
-
let out = String(tex ?? "");
|
|
239
|
-
out = out.replace(/\\frac\s*\{([^{}]*)\}\s*\{([^{}]*)\}/g, "$1/$2");
|
|
240
|
-
out = out.replace(/\\sqrt\s*\{([^{}]*)\}/g, "√($1)");
|
|
241
|
-
out = out.replace(/\\([A-Za-z]+)\b/g, (_match, command) => TEX_SYMBOLS[command] ?? command);
|
|
242
|
-
out = out.replace(/\\([^A-Za-z])/g, "$1");
|
|
243
|
-
out = out.replace(/[{}]/g, "");
|
|
244
|
-
out = out.replace(/\s+/g, " ").trim();
|
|
245
|
-
return sanitizeInlineText(out.replace(/[<>]/g, ""));
|
|
246
|
-
}
|
|
247
|
-
function canRenderTerminalMath(tex) {
|
|
248
|
-
const source = String(tex ?? "").trim();
|
|
249
|
-
if (!source || source.length > 160) return false;
|
|
250
|
-
if (/\\(?:begin|end|left|right|matrix|cases|array|align|color|href|html|includegraphics|class|style|tag|text|operatorname)\b/.test(source)) return false;
|
|
251
|
-
const commands = source.matchAll(/\\([A-Za-z]+)\b/g);
|
|
252
|
-
for (const match of commands) if (!SUPPORTED_COMMANDS.has(match[1] ?? "")) return false;
|
|
253
|
-
try {
|
|
254
|
-
katex.renderToString(source, KATEX_VALIDATE_OPTIONS);
|
|
255
|
-
} catch {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
function renderMarkdownInlineMathSegment(tex) {
|
|
261
|
-
const source = String(tex ?? "");
|
|
262
|
-
if (!source.trim()) return {
|
|
263
|
-
text: "",
|
|
264
|
-
supported: false
|
|
265
|
-
};
|
|
266
|
-
if (!canRenderTerminalMath(source)) return {
|
|
267
|
-
text: "",
|
|
268
|
-
supported: false
|
|
269
|
-
};
|
|
270
|
-
try {
|
|
271
|
-
const html = katex.renderToString(source, KATEX_TEXT_OPTIONS);
|
|
272
|
-
const rendered = /\\frac\b/.test(source) ? approximateTexToUnicode(source) : textFromKatexMathml(html);
|
|
273
|
-
if (rendered && !rendered.includes("\\")) return {
|
|
274
|
-
text: rendered,
|
|
275
|
-
supported: true
|
|
276
|
-
};
|
|
277
|
-
} catch {
|
|
278
|
-
return {
|
|
279
|
-
text: "",
|
|
280
|
-
supported: false
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
const approximate = approximateTexToUnicode(source);
|
|
284
|
-
return approximate && !approximate.includes("\\") ? {
|
|
285
|
-
text: approximate,
|
|
286
|
-
supported: true
|
|
287
|
-
} : {
|
|
288
|
-
text: "",
|
|
289
|
-
supported: false
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
117
|
//#endregion
|
|
294
118
|
//#region src/vue/markdown/parser.ts
|
|
295
119
|
const markdownInstanceCache = new Map();
|
|
@@ -370,6 +194,15 @@ function stringProp(node, key) {
|
|
|
370
194
|
const value = node[key];
|
|
371
195
|
return typeof value === "string" ? value : "";
|
|
372
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Temporary fallback: stream-markdown-parser only recovers ``
|
|
199
|
+
* destinations (Windows drive letters, fixed in 1.1.8). Unix-style `file:///...`
|
|
200
|
+
* destinations at inline start are still emitted as `text("!") + link`. Remove this
|
|
201
|
+
* once the parser covers all `file://` destinations.
|
|
202
|
+
*/
|
|
203
|
+
function isUnixFileUrlNeedingImageFallback(href) {
|
|
204
|
+
return /^file:\/\//i.test(href) && !/^file:\/\/\/[a-z]:\//i.test(href);
|
|
205
|
+
}
|
|
373
206
|
function imageSourceFromRaw(raw) {
|
|
374
207
|
if (!raw.startsWith(";
|
|
@@ -466,132 +299,172 @@ function codeBlockLanguage(node) {
|
|
|
466
299
|
}
|
|
467
300
|
function inlineNodeSegments(nodes, theme, inheritedStyle, options = {}) {
|
|
468
301
|
const out = [];
|
|
469
|
-
for (
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
break;
|
|
486
|
-
case "strikethrough":
|
|
487
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
|
|
488
|
-
break;
|
|
489
|
-
case "highlight":
|
|
490
|
-
case "insert":
|
|
491
|
-
case "subscript":
|
|
492
|
-
case "superscript":
|
|
493
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
494
|
-
break;
|
|
495
|
-
case "inline_code":
|
|
496
|
-
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
497
|
-
break;
|
|
498
|
-
case "hardbreak":
|
|
499
|
-
out.push(HARD_BREAK_SEGMENT);
|
|
500
|
-
break;
|
|
501
|
-
case "link": {
|
|
502
|
-
const href = stringProp(node, "href");
|
|
503
|
-
const safeHref = href ? sanitizeMarkdownLink(href) : null;
|
|
504
|
-
const linkStyle = safeHref ? mergeStyle$1(inheritedStyle, {
|
|
505
|
-
...theme.link,
|
|
506
|
-
href: safeHref
|
|
507
|
-
}) : inheritedStyle;
|
|
508
|
-
const children = nodeChildren(node);
|
|
509
|
-
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
|
|
510
|
-
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
511
|
-
break;
|
|
302
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
303
|
+
let node = nodes[index];
|
|
304
|
+
const next = nodes[index + 1];
|
|
305
|
+
const text = node.type === "text" ? stringProp(node, "content") : "";
|
|
306
|
+
if (node.type === "text" && text.endsWith("!") && stringProp(node, "raw").endsWith("!") && next?.type === "link" && isUnixFileUrlNeedingImageFallback(stringProp(next, "href"))) {
|
|
307
|
+
const src = stringProp(next, "href");
|
|
308
|
+
if (sanitizeMarkdownImageSource(src)) {
|
|
309
|
+
pushTextSegments(out, text.slice(0, -1), inheritedStyle);
|
|
310
|
+
node = {
|
|
311
|
+
type: "image",
|
|
312
|
+
src,
|
|
313
|
+
alt: stringProp(next, "text"),
|
|
314
|
+
raw: `!${stringProp(next, "raw")}`
|
|
315
|
+
};
|
|
316
|
+
index++;
|
|
317
|
+
}
|
|
512
318
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
319
|
+
switch (node.type) {
|
|
320
|
+
case "text":
|
|
321
|
+
pushTextSegments(out, stringProp(node, "content"), inheritedStyle);
|
|
322
|
+
break;
|
|
323
|
+
case "inline":
|
|
324
|
+
case "paragraph":
|
|
325
|
+
case "heading":
|
|
326
|
+
case "list_item":
|
|
327
|
+
case "blockquote":
|
|
328
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
329
|
+
break;
|
|
330
|
+
case "strong":
|
|
331
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong), options));
|
|
332
|
+
break;
|
|
333
|
+
case "emphasis":
|
|
334
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis), options));
|
|
335
|
+
break;
|
|
336
|
+
case "strikethrough":
|
|
337
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
|
|
338
|
+
break;
|
|
339
|
+
case "highlight":
|
|
340
|
+
case "insert":
|
|
341
|
+
case "subscript":
|
|
342
|
+
case "superscript":
|
|
343
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
344
|
+
break;
|
|
345
|
+
case "inline_code":
|
|
346
|
+
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
347
|
+
break;
|
|
348
|
+
case "hardbreak":
|
|
349
|
+
out.push(HARD_BREAK_SEGMENT);
|
|
350
|
+
break;
|
|
351
|
+
case "link": {
|
|
352
|
+
const href = stringProp(node, "href");
|
|
353
|
+
const safeHref = href ? sanitizeMarkdownLink(href) : null;
|
|
354
|
+
const linkStyle = safeHref ? mergeStyle$1(inheritedStyle, {
|
|
521
355
|
...theme.link,
|
|
522
|
-
href:
|
|
523
|
-
});
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
...
|
|
546
|
-
...
|
|
547
|
-
...
|
|
548
|
-
naturalWidth:
|
|
549
|
-
naturalHeight:
|
|
356
|
+
href: safeHref
|
|
357
|
+
}) : inheritedStyle;
|
|
358
|
+
const children = nodeChildren(node);
|
|
359
|
+
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
|
|
360
|
+
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
case "image": {
|
|
364
|
+
const raw = stringProp(node, "raw");
|
|
365
|
+
const rawSource = stringProp(node, "src") || imageSourceFromRaw(raw);
|
|
366
|
+
const source = sanitizeMarkdownImageSource(rawSource);
|
|
367
|
+
const alt = stringProp(node, "alt") || imageFallbackText(raw, rawSource, source?.src);
|
|
368
|
+
if (source) {
|
|
369
|
+
const size = options.imageSize;
|
|
370
|
+
const imageLinkStyle = mergeStyle$1(inheritedStyle, {
|
|
371
|
+
...theme.link,
|
|
372
|
+
href: source.src
|
|
373
|
+
});
|
|
374
|
+
const sourceDimensions = readMarkdownImageDimensions(source.base64);
|
|
375
|
+
const graphicBase = {
|
|
376
|
+
kind: "image",
|
|
377
|
+
src: source.src,
|
|
378
|
+
alt,
|
|
379
|
+
...source.mime ? { mime: source.mime } : {},
|
|
380
|
+
...source.base64 ? { base64: source.base64 } : {},
|
|
381
|
+
...sourceDimensions ? {
|
|
382
|
+
naturalWidth: sourceDimensions.width,
|
|
383
|
+
naturalHeight: sourceDimensions.height
|
|
550
384
|
} : {},
|
|
551
|
-
...resolveImageDisplaySize(alt, size,
|
|
385
|
+
...resolveImageDisplaySize(alt, size, sourceDimensions?.width, sourceDimensions?.height)
|
|
552
386
|
};
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
raw,
|
|
573
|
-
rendered: rendered.supported
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
break;
|
|
577
|
-
case "reference":
|
|
578
|
-
case "footnote_reference":
|
|
579
|
-
case "footnote_anchor":
|
|
580
|
-
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
581
|
-
break;
|
|
582
|
-
case "html_inline":
|
|
583
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
584
|
-
break;
|
|
585
|
-
case "html_block":
|
|
586
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
587
|
-
break;
|
|
588
|
-
default:
|
|
589
|
-
if (nodeChildren(node).length) {
|
|
590
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
387
|
+
const resolved = normalizeImageResolverResult(options.imageResolver?.(graphicBase));
|
|
388
|
+
const base64 = resolved.base64 || source.base64;
|
|
389
|
+
if (base64) {
|
|
390
|
+
const dimensions = readMarkdownImageDimensions(base64) ?? sourceDimensions;
|
|
391
|
+
const graphic = {
|
|
392
|
+
...graphicBase,
|
|
393
|
+
base64,
|
|
394
|
+
...resolved.originalBase64 ? { originalBase64: resolved.originalBase64 } : {},
|
|
395
|
+
...resolved.mime ? { mime: resolved.mime } : {},
|
|
396
|
+
...resolved.originalMime ? { originalMime: resolved.originalMime } : {},
|
|
397
|
+
...dimensions ? {
|
|
398
|
+
naturalWidth: dimensions.width,
|
|
399
|
+
naturalHeight: dimensions.height
|
|
400
|
+
} : {},
|
|
401
|
+
...resolveImageDisplaySize(alt, size, dimensions?.width, dimensions?.height)
|
|
402
|
+
};
|
|
403
|
+
pushTextSegments(out, alt, imageLinkStyle, graphic);
|
|
404
|
+
} else pushTextSegments(out, alt, imageLinkStyle);
|
|
405
|
+
} else pushTextSegments(out, alt, mergeStyle$1(inheritedStyle, theme.link));
|
|
591
406
|
break;
|
|
592
407
|
}
|
|
593
|
-
|
|
594
|
-
|
|
408
|
+
case "checkbox":
|
|
409
|
+
case "checkbox_input":
|
|
410
|
+
pushTextSegments(out, booleanProp(node, "checked") ? "[x]" : "[ ]", inheritedStyle);
|
|
411
|
+
break;
|
|
412
|
+
case "emoji":
|
|
413
|
+
pushTextSegments(out, stringProp(node, "markup") || stringProp(node, "name"), inheritedStyle);
|
|
414
|
+
break;
|
|
415
|
+
case "math_inline":
|
|
416
|
+
{
|
|
417
|
+
const source = stringProp(node, "content");
|
|
418
|
+
const markup = stringProp(node, "markup") || "$";
|
|
419
|
+
const raw = stringProp(node, "raw") || `${markup}${source}${markup}`;
|
|
420
|
+
const mathStyle = mergeStyle$1(inheritedStyle, theme.inlineCode);
|
|
421
|
+
const tex = sanitizeInlineText(source);
|
|
422
|
+
const mathEnabled = options.math?.enabled !== false;
|
|
423
|
+
const cached = tex && mathEnabled ? getCachedMarkdownMathImage(tex, "inline", options.math) : null;
|
|
424
|
+
if (cached) pushTextSegments(out, raw, mathStyle, {
|
|
425
|
+
kind: "math",
|
|
426
|
+
tex,
|
|
427
|
+
raw,
|
|
428
|
+
base64: cached.base64,
|
|
429
|
+
displayWidth: cached.widthCells,
|
|
430
|
+
displayHeight: cached.heightCells
|
|
431
|
+
}, {
|
|
432
|
+
source: tex,
|
|
433
|
+
raw,
|
|
434
|
+
rendered: true
|
|
435
|
+
});
|
|
436
|
+
else pushTextSegments(out, raw, mathStyle, void 0, tex && mathEnabled ? {
|
|
437
|
+
source: tex,
|
|
438
|
+
raw,
|
|
439
|
+
rendered: false,
|
|
440
|
+
pendingImage: true,
|
|
441
|
+
mode: "inline"
|
|
442
|
+
} : {
|
|
443
|
+
source,
|
|
444
|
+
raw,
|
|
445
|
+
rendered: false
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
case "reference":
|
|
450
|
+
case "footnote_reference":
|
|
451
|
+
case "footnote_anchor":
|
|
452
|
+
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
453
|
+
break;
|
|
454
|
+
case "html_inline":
|
|
455
|
+
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
456
|
+
break;
|
|
457
|
+
case "html_block":
|
|
458
|
+
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
459
|
+
break;
|
|
460
|
+
default:
|
|
461
|
+
if (nodeChildren(node).length) {
|
|
462
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
595
468
|
}
|
|
596
469
|
return out;
|
|
597
470
|
}
|
|
@@ -607,14 +480,16 @@ function inlineBlock(key, segments, context) {
|
|
|
607
480
|
function blockFromParagraph(node, key, context, theme) {
|
|
608
481
|
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, void 0, {
|
|
609
482
|
imageResolver: context.imageResolver,
|
|
610
|
-
imageSize: context.imageSize
|
|
483
|
+
imageSize: context.imageSize,
|
|
484
|
+
math: context.math
|
|
611
485
|
}), context);
|
|
612
486
|
}
|
|
613
487
|
function blockFromHeading(node, key, context, theme) {
|
|
614
488
|
const level = Math.min(6, Math.max(1, Math.floor(numberProp(node, "level") ?? 1)));
|
|
615
489
|
return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1], {
|
|
616
490
|
imageResolver: context.imageResolver,
|
|
617
|
-
imageSize: context.imageSize
|
|
491
|
+
imageSize: context.imageSize,
|
|
492
|
+
math: context.math
|
|
618
493
|
}), context);
|
|
619
494
|
}
|
|
620
495
|
function blockFromCodeBlock(node, key, context, theme) {
|
|
@@ -633,7 +508,10 @@ function blockFromHtmlBlock(node, key, context, theme) {
|
|
|
633
508
|
type: "html_block",
|
|
634
509
|
raw: stringProp(node, "raw"),
|
|
635
510
|
content: stringProp(node, "content")
|
|
636
|
-
}], theme, void 0, {
|
|
511
|
+
}], theme, void 0, {
|
|
512
|
+
imageResolver: context.imageResolver,
|
|
513
|
+
math: context.math
|
|
514
|
+
}), context);
|
|
637
515
|
}
|
|
638
516
|
function tableRows$1(node, key) {
|
|
639
517
|
const value = node[key];
|
|
@@ -653,14 +531,16 @@ function blockFromTable(node, key, context, theme) {
|
|
|
653
531
|
const header = tableCells(headerRow ?? { type: "table_row" }).map((cell) => ({
|
|
654
532
|
segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong, {
|
|
655
533
|
imageResolver: context.imageResolver,
|
|
656
|
-
imageSize: context.imageSize
|
|
534
|
+
imageSize: context.imageSize,
|
|
535
|
+
math: context.math
|
|
657
536
|
}),
|
|
658
537
|
align: tableCellAlign(cell)
|
|
659
538
|
}));
|
|
660
539
|
const rows = tableRows$1(node, "rows").map((row) => tableCells(row).map((cell) => ({
|
|
661
540
|
segments: inlineNodeSegments(nodeChildren(cell), theme, void 0, {
|
|
662
541
|
imageResolver: context.imageResolver,
|
|
663
|
-
imageSize: context.imageSize
|
|
542
|
+
imageSize: context.imageSize,
|
|
543
|
+
math: context.math
|
|
664
544
|
}),
|
|
665
545
|
align: tableCellAlign(cell)
|
|
666
546
|
})));
|
|
@@ -698,11 +578,13 @@ function listItemBlocks(item, index, ordered, start, context, theme, keyPrefix)
|
|
|
698
578
|
const indentSegment = { text: spaces(markerCells) };
|
|
699
579
|
const firstContext = {
|
|
700
580
|
prefixSegments: [...context.prefixSegments, markerSegment],
|
|
701
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
|
|
581
|
+
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment],
|
|
582
|
+
math: context.math
|
|
702
583
|
};
|
|
703
584
|
const nestedContext = {
|
|
704
585
|
prefixSegments: [...context.continuationPrefixSegments, indentSegment],
|
|
705
|
-
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
|
|
586
|
+
continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment],
|
|
587
|
+
math: context.math
|
|
706
588
|
};
|
|
707
589
|
const children = nodeChildren(item);
|
|
708
590
|
if (!children.length) return [inlineBlock(`${keyPrefix}-empty`, [], firstContext)];
|
|
@@ -734,6 +616,61 @@ function listBlocks(node, context, theme, keyPrefix) {
|
|
|
734
616
|
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}`));
|
|
735
617
|
return out;
|
|
736
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Block-level display math (`$...$`). When a rasterized PNG is available this
|
|
621
|
+
* becomes a graphic segment rendered through the terminal graphics pipeline
|
|
622
|
+
* (Kitty/iTerm2/Sixel); otherwise it is wrapped in a box with the raw TeX text
|
|
623
|
+
* (also the permanent presentation when graphics/raster support is missing).
|
|
624
|
+
* Clicking either form emits `mathAction` so consumers can copy the raw content.
|
|
625
|
+
*/
|
|
626
|
+
function blockMathSegments(source, raw, math, theme) {
|
|
627
|
+
const tex = sanitizeInlineText(source);
|
|
628
|
+
const canUseImage = Boolean(tex) && math.enabled !== false;
|
|
629
|
+
const cached = canUseImage ? getCachedMarkdownMathImage(tex, "display", math) : null;
|
|
630
|
+
if (canUseImage && cached) return [{
|
|
631
|
+
text: raw,
|
|
632
|
+
style: theme.math,
|
|
633
|
+
graphic: {
|
|
634
|
+
kind: "math",
|
|
635
|
+
tex,
|
|
636
|
+
raw,
|
|
637
|
+
base64: cached.base64,
|
|
638
|
+
displayWidth: cached.widthCells,
|
|
639
|
+
displayHeight: cached.heightCells
|
|
640
|
+
},
|
|
641
|
+
mathAction: {
|
|
642
|
+
source: tex,
|
|
643
|
+
raw,
|
|
644
|
+
rendered: true
|
|
645
|
+
}
|
|
646
|
+
}];
|
|
647
|
+
const maxWidth = Math.max(8, Math.floor(math.maxWidthCells ?? 0) || 40);
|
|
648
|
+
const rawCells = textCellWidth(raw);
|
|
649
|
+
const inner = Math.min(Math.max(1, rawCells + 2), maxWidth);
|
|
650
|
+
const pad = Math.max(0, inner - rawCells);
|
|
651
|
+
return [
|
|
652
|
+
{
|
|
653
|
+
text: `┌${"─".repeat(inner)}┐`,
|
|
654
|
+
style: theme.math
|
|
655
|
+
},
|
|
656
|
+
HARD_BREAK_SEGMENT,
|
|
657
|
+
{
|
|
658
|
+
text: `│ ${raw}${" ".repeat(pad)}│`,
|
|
659
|
+
style: theme.math,
|
|
660
|
+
mathAction: {
|
|
661
|
+
source: tex,
|
|
662
|
+
raw,
|
|
663
|
+
rendered: false,
|
|
664
|
+
pendingImage: true
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
HARD_BREAK_SEGMENT,
|
|
668
|
+
{
|
|
669
|
+
text: `└${"─".repeat(inner)}┘`,
|
|
670
|
+
style: theme.math
|
|
671
|
+
}
|
|
672
|
+
];
|
|
673
|
+
}
|
|
737
674
|
function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
738
675
|
switch (node.type) {
|
|
739
676
|
case "paragraph":
|
|
@@ -752,6 +689,7 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
|
752
689
|
style: theme.thematicBreak,
|
|
753
690
|
prefixSegments: context.prefixSegments
|
|
754
691
|
}];
|
|
692
|
+
case "math_block": return [inlineBlock(keyPrefix, blockMathSegments(stringProp(node, "content"), stringProp(node, "raw") || `$${stringProp(node, "content")}$`, context.math ?? { enabled: true }, theme), context)];
|
|
755
693
|
case "blockquote": {
|
|
756
694
|
const quoteSegment = {
|
|
757
695
|
text: "│ ",
|
|
@@ -761,7 +699,8 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
|
761
699
|
prefixSegments: [...context.prefixSegments, quoteSegment],
|
|
762
700
|
continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment],
|
|
763
701
|
imageResolver: context.imageResolver,
|
|
764
|
-
imageSize: context.imageSize
|
|
702
|
+
imageSize: context.imageSize,
|
|
703
|
+
math: context.math
|
|
765
704
|
};
|
|
766
705
|
return childSequenceToBlocks(nodeChildren(node), quoteContext, theme, keyPrefix);
|
|
767
706
|
}
|
|
@@ -771,22 +710,30 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
|
|
|
771
710
|
case "table": return [blockFromTable(node, keyPrefix, context, theme)];
|
|
772
711
|
case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme, void 0, {
|
|
773
712
|
imageResolver: context.imageResolver,
|
|
774
|
-
imageSize: context.imageSize
|
|
713
|
+
imageSize: context.imageSize,
|
|
714
|
+
math: context.math
|
|
775
715
|
}), context)];
|
|
776
716
|
case "html_block":
|
|
777
717
|
if ("content" in node) return [blockFromHtmlBlock(node, keyPrefix, context, theme)];
|
|
778
718
|
break;
|
|
779
|
-
case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
|
|
719
|
+
case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
|
|
720
|
+
imageResolver: context.imageResolver,
|
|
721
|
+
math: context.math
|
|
722
|
+
}), context)];
|
|
780
723
|
}
|
|
781
724
|
if (nodeChildren(node).length) return childSequenceToBlocks(nodeChildren(node), context, theme, keyPrefix);
|
|
782
|
-
return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
|
|
725
|
+
return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
|
|
726
|
+
imageResolver: context.imageResolver,
|
|
727
|
+
math: context.math
|
|
728
|
+
}), context)];
|
|
783
729
|
}
|
|
784
730
|
function markdownAstToBlocks(nodes, theme, options = {}) {
|
|
785
731
|
const blocks = trimBlockArray(childSequenceToBlocks(nodes, {
|
|
786
732
|
prefixSegments: EMPTY_PREFIX,
|
|
787
733
|
continuationPrefixSegments: EMPTY_PREFIX,
|
|
788
734
|
imageResolver: options.imageResolver,
|
|
789
|
-
imageSize: options.imageSize
|
|
735
|
+
imageSize: options.imageSize,
|
|
736
|
+
math: options.math
|
|
790
737
|
}, theme, "md"));
|
|
791
738
|
return blocks.length ? blocks : [{
|
|
792
739
|
type: "blank",
|
|
@@ -935,7 +882,8 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
|
|
|
935
882
|
style: segment.style,
|
|
936
883
|
cells,
|
|
937
884
|
graphic: segment.graphic,
|
|
938
|
-
fallbackText: segment.text
|
|
885
|
+
fallbackText: segment.text,
|
|
886
|
+
...segment.mathAction ? { mathAction: segment.mathAction } : {}
|
|
939
887
|
});
|
|
940
888
|
row.remaining -= cells;
|
|
941
889
|
continue;
|
|
@@ -1203,7 +1151,8 @@ function styleSignature(style) {
|
|
|
1203
1151
|
function inlineSegmentSignature(segment) {
|
|
1204
1152
|
const graphic = segment.graphic ? [
|
|
1205
1153
|
segment.graphic.kind,
|
|
1206
|
-
segment.graphic.src,
|
|
1154
|
+
segment.graphic.src ?? "",
|
|
1155
|
+
segment.graphic.tex ?? "",
|
|
1207
1156
|
segment.graphic.alt ?? "",
|
|
1208
1157
|
segment.graphic.mime ?? "",
|
|
1209
1158
|
segment.graphic.base64 ?? "",
|
|
@@ -1378,7 +1327,8 @@ const DEFAULT_TUI_MARKDOWN_THEME = Object.freeze({
|
|
|
1378
1327
|
}),
|
|
1379
1328
|
codeBlock: freezeStyle({ fg: "yellowBright" }),
|
|
1380
1329
|
thematicBreak: freezeStyle({ dim: true }),
|
|
1381
|
-
html: freezeStyle({ dim: true })
|
|
1330
|
+
html: freezeStyle({ dim: true }),
|
|
1331
|
+
math: freezeStyle({ fg: "cyanBright" })
|
|
1382
1332
|
});
|
|
1383
1333
|
function stableSerialize(value) {
|
|
1384
1334
|
if (value == null) return "null";
|
|
@@ -1404,7 +1354,8 @@ function resolveTuiMarkdownTheme(overrides) {
|
|
|
1404
1354
|
listMarker: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.listMarker, overrides.listMarker),
|
|
1405
1355
|
codeBlock: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.codeBlock, overrides.codeBlock),
|
|
1406
1356
|
thematicBreak: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.thematicBreak, overrides.thematicBreak),
|
|
1407
|
-
html: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.html, overrides.html)
|
|
1357
|
+
html: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.html, overrides.html),
|
|
1358
|
+
math: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.math, overrides.math)
|
|
1408
1359
|
});
|
|
1409
1360
|
}
|
|
1410
1361
|
|
|
@@ -1420,7 +1371,8 @@ function buildMarkdownBlocks(content, parser, options) {
|
|
|
1420
1371
|
const nodes = parser.parse(content, options?.final ?? true);
|
|
1421
1372
|
const blocks = markdownAstToBlocks(nodes, theme, {
|
|
1422
1373
|
imageResolver: options?.imageResolver,
|
|
1423
|
-
imageSize: options?.imageSize
|
|
1374
|
+
imageSize: options?.imageSize,
|
|
1375
|
+
math: options?.math
|
|
1424
1376
|
});
|
|
1425
1377
|
return {
|
|
1426
1378
|
nodes,
|
|
@@ -1607,11 +1559,12 @@ function findMarkdownMathActionAt(rows, point, options) {
|
|
|
1607
1559
|
if (!math || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
|
|
1608
1560
|
const visibleStart = Math.max(segmentStart, clipStart);
|
|
1609
1561
|
const visibleEnd = Math.min(segmentEnd, clipEnd);
|
|
1562
|
+
const graphicHeight = segment.graphic ? Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1)) : 1;
|
|
1610
1563
|
const rect = {
|
|
1611
1564
|
x: r.x + visibleStart - clipStart,
|
|
1612
1565
|
y: r.y + rowIndex - firstRow,
|
|
1613
1566
|
w: visibleEnd - visibleStart,
|
|
1614
|
-
h:
|
|
1567
|
+
h: graphicHeight
|
|
1615
1568
|
};
|
|
1616
1569
|
if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
|
|
1617
1570
|
return {
|
|
@@ -1627,6 +1580,97 @@ function findMarkdownMathActionAt(rows, point, options) {
|
|
|
1627
1580
|
return null;
|
|
1628
1581
|
}
|
|
1629
1582
|
|
|
1583
|
+
//#endregion
|
|
1584
|
+
//#region src/vue/markdown/math.ts
|
|
1585
|
+
let loadedKatex = null;
|
|
1586
|
+
let katexLoad = null;
|
|
1587
|
+
const katexListeners = new Set();
|
|
1588
|
+
function resolveKatexRenderer(value) {
|
|
1589
|
+
if (!value || typeof value !== "object") return null;
|
|
1590
|
+
const renderToString = value.renderToString;
|
|
1591
|
+
return typeof renderToString === "function" ? { renderToString } : null;
|
|
1592
|
+
}
|
|
1593
|
+
function subscribeMarkdownMathRenderer(listener) {
|
|
1594
|
+
katexListeners.add(listener);
|
|
1595
|
+
return () => katexListeners.delete(listener);
|
|
1596
|
+
}
|
|
1597
|
+
async function loadMarkdownMathRenderer() {
|
|
1598
|
+
if (loadedKatex) return true;
|
|
1599
|
+
if (!katexLoad) katexLoad = import("katex").then((mod) => {
|
|
1600
|
+
const renderer = resolveKatexRenderer(mod.default) ?? resolveKatexRenderer(mod);
|
|
1601
|
+
if (!renderer) return false;
|
|
1602
|
+
loadedKatex = renderer;
|
|
1603
|
+
for (const listener of katexListeners) listener();
|
|
1604
|
+
return true;
|
|
1605
|
+
}).catch(() => false);
|
|
1606
|
+
return katexLoad;
|
|
1607
|
+
}
|
|
1608
|
+
const KATEX_TEXT_OPTIONS = Object.freeze({
|
|
1609
|
+
output: "mathml",
|
|
1610
|
+
throwOnError: false,
|
|
1611
|
+
strict: "ignore",
|
|
1612
|
+
trust: false,
|
|
1613
|
+
maxSize: 20,
|
|
1614
|
+
maxExpand: 200
|
|
1615
|
+
});
|
|
1616
|
+
const KATEX_VALIDATE_OPTIONS = Object.freeze({
|
|
1617
|
+
...KATEX_TEXT_OPTIONS,
|
|
1618
|
+
throwOnError: true
|
|
1619
|
+
});
|
|
1620
|
+
const TEX_SYMBOLS = Object.freeze({
|
|
1621
|
+
alpha: "α",
|
|
1622
|
+
beta: "β",
|
|
1623
|
+
gamma: "γ",
|
|
1624
|
+
delta: "δ",
|
|
1625
|
+
epsilon: "ε",
|
|
1626
|
+
theta: "θ",
|
|
1627
|
+
lambda: "λ",
|
|
1628
|
+
mu: "μ",
|
|
1629
|
+
pi: "π",
|
|
1630
|
+
rho: "ρ",
|
|
1631
|
+
sigma: "σ",
|
|
1632
|
+
tau: "τ",
|
|
1633
|
+
phi: "φ",
|
|
1634
|
+
omega: "ω",
|
|
1635
|
+
Gamma: "Γ",
|
|
1636
|
+
Delta: "Δ",
|
|
1637
|
+
Theta: "Θ",
|
|
1638
|
+
Lambda: "Λ",
|
|
1639
|
+
Pi: "Π",
|
|
1640
|
+
Sigma: "Σ",
|
|
1641
|
+
Phi: "Φ",
|
|
1642
|
+
Omega: "Ω",
|
|
1643
|
+
int: "∫",
|
|
1644
|
+
sum: "∑",
|
|
1645
|
+
prod: "∏",
|
|
1646
|
+
infty: "∞",
|
|
1647
|
+
infinity: "∞",
|
|
1648
|
+
pm: "±",
|
|
1649
|
+
times: "×",
|
|
1650
|
+
cdot: "·",
|
|
1651
|
+
div: "÷",
|
|
1652
|
+
le: "≤",
|
|
1653
|
+
leq: "≤",
|
|
1654
|
+
ge: "≥",
|
|
1655
|
+
geq: "≥",
|
|
1656
|
+
neq: "≠",
|
|
1657
|
+
ne: "≠",
|
|
1658
|
+
approx: "≈",
|
|
1659
|
+
equiv: "≡",
|
|
1660
|
+
to: "→",
|
|
1661
|
+
rightarrow: "→",
|
|
1662
|
+
leftarrow: "←",
|
|
1663
|
+
leftrightarrow: "↔",
|
|
1664
|
+
partial: "∂",
|
|
1665
|
+
nabla: "∇",
|
|
1666
|
+
sqrt: "√"
|
|
1667
|
+
});
|
|
1668
|
+
const SUPPORTED_COMMANDS = new Set([
|
|
1669
|
+
...Object.keys(TEX_SYMBOLS),
|
|
1670
|
+
"frac",
|
|
1671
|
+
"sqrt"
|
|
1672
|
+
]);
|
|
1673
|
+
|
|
1630
1674
|
//#endregion
|
|
1631
1675
|
//#region src/vue/markdown/render.ts
|
|
1632
1676
|
const mergedStyleCache = new WeakMap();
|
|
@@ -1653,7 +1697,7 @@ function mergeStyle(base, overlay) {
|
|
|
1653
1697
|
}
|
|
1654
1698
|
const activeMarkdownImageGraphics = new WeakMap();
|
|
1655
1699
|
function markdownImageGraphicId(segment, identity) {
|
|
1656
|
-
return `md-image:${stableTerminalGraphicNumericId(`${identity}:${segment.src}:${segment.displayWidth ?? 0}:${segment.displayHeight ?? 0}:${segment.base64 ?? ""}`)}`;
|
|
1700
|
+
return `md-image:${stableTerminalGraphicNumericId(`${identity}:${segment.src ?? segment.tex ?? ""}:${segment.displayWidth ?? 0}:${segment.displayHeight ?? 0}:${segment.base64 ?? ""}`)}`;
|
|
1657
1701
|
}
|
|
1658
1702
|
function rememberMarkdownImageGraphic(terminal, id, rect) {
|
|
1659
1703
|
let active = activeMarkdownImageGraphics.get(terminal);
|
|
@@ -1679,7 +1723,7 @@ function clearStaleMarkdownImageGraphicsForRow(terminal, y, keepIds, keepVisible
|
|
|
1679
1723
|
}
|
|
1680
1724
|
}
|
|
1681
1725
|
function canAttemptMarkdownImageGraphic(terminal, segment) {
|
|
1682
|
-
if (
|
|
1726
|
+
if (!segment.base64) return false;
|
|
1683
1727
|
const output = getTerminalGraphicsOutput(terminal);
|
|
1684
1728
|
const protocol = output?.capabilities.preferredProtocol;
|
|
1685
1729
|
return Boolean(output?.capabilities.supported && isTerminalGraphicsProtocol(protocol));
|
|
@@ -1693,7 +1737,7 @@ function clearMarkdownImageGraphics(terminal, rect) {
|
|
|
1693
1737
|
}
|
|
1694
1738
|
}
|
|
1695
1739
|
function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
|
|
1696
|
-
if (
|
|
1740
|
+
if (!segment.base64) return "unavailable";
|
|
1697
1741
|
const output = getTerminalGraphicsOutput(terminal);
|
|
1698
1742
|
const protocol = output?.capabilities.preferredProtocol;
|
|
1699
1743
|
if (!output?.capabilities.supported || !isTerminalGraphicsProtocol(protocol)) return "unavailable";
|
|
@@ -1735,7 +1779,7 @@ function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
|
|
|
1735
1779
|
sequence,
|
|
1736
1780
|
resizeSequence,
|
|
1737
1781
|
clearSequence,
|
|
1738
|
-
fallbackText: segment.alt ?? "image"
|
|
1782
|
+
fallbackText: segment.alt ?? (segment.kind === "math" ? segment.raw ?? segment.tex ?? "math" : "image")
|
|
1739
1783
|
});
|
|
1740
1784
|
markdownImageDebug(`queue id=${id}`, `accepted=${accepted ? "true" : "false"}`, `x=${rect.x}`, `y=${rect.y}`, `w=${width}`, `h=${height}`, `visibleW=${rect.w}`, `displayW=${segment.displayWidth ?? ""}`, `displayH=${segment.displayHeight ?? ""}`);
|
|
1741
1785
|
if (accepted) rememberMarkdownImageGraphic(terminal, id, {
|
|
@@ -1839,7 +1883,7 @@ function paintMarkdownVisualRow(terminal, row, options) {
|
|
|
1839
1883
|
const queuedGraphic = queueResult === "queued";
|
|
1840
1884
|
const suppressFallback = queuedGraphic || queueResult === "rejected" || graphicAvailable === true;
|
|
1841
1885
|
if (queuedGraphic) queuedGraphics.add(markdownImageGraphicId(segment.graphic, `${row.key}:${segmentIndex}`));
|
|
1842
|
-
terminal.write(suppressFallback ? spaces(visibleCells) : sliceByCellsRange(segment.fallbackText ?? segment.graphic.alt ?? "image", 0, visibleCells) || spaces(visibleCells), {
|
|
1886
|
+
terminal.write(suppressFallback ? spaces(visibleCells) : sliceByCellsRange(segment.fallbackText ?? segment.graphic.alt ?? (segment.graphic.kind === "math" ? segment.graphic.raw ?? segment.graphic.tex ?? "math" : "image"), 0, visibleCells) || spaces(visibleCells), {
|
|
1843
1887
|
x: cellX,
|
|
1844
1888
|
y: options.y,
|
|
1845
1889
|
style: suppressFallback ? options.baseStyle : segmentStyle
|
|
@@ -2031,6 +2075,34 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2031
2075
|
type: Boolean,
|
|
2032
2076
|
default: true
|
|
2033
2077
|
},
|
|
2078
|
+
mathImages: {
|
|
2079
|
+
type: Boolean,
|
|
2080
|
+
default: true
|
|
2081
|
+
},
|
|
2082
|
+
mathCellWidthPx: {
|
|
2083
|
+
type: Number,
|
|
2084
|
+
default: void 0
|
|
2085
|
+
},
|
|
2086
|
+
mathCellHeightPx: {
|
|
2087
|
+
type: Number,
|
|
2088
|
+
default: void 0
|
|
2089
|
+
},
|
|
2090
|
+
mathScale: {
|
|
2091
|
+
type: Number,
|
|
2092
|
+
default: void 0
|
|
2093
|
+
},
|
|
2094
|
+
mathColor: {
|
|
2095
|
+
type: String,
|
|
2096
|
+
default: void 0
|
|
2097
|
+
},
|
|
2098
|
+
mathMaxWidthCells: {
|
|
2099
|
+
type: Number,
|
|
2100
|
+
default: void 0
|
|
2101
|
+
},
|
|
2102
|
+
mathBaselineRatio: {
|
|
2103
|
+
type: Number,
|
|
2104
|
+
default: void 0
|
|
2105
|
+
},
|
|
2034
2106
|
imageActions: {
|
|
2035
2107
|
type: Boolean,
|
|
2036
2108
|
default: false
|
|
@@ -2081,7 +2153,42 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2081
2153
|
const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
|
|
2082
2154
|
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
2083
2155
|
graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
|
|
2156
|
+
enqueuePendingMathImages();
|
|
2157
|
+
});
|
|
2158
|
+
const unsubscribeMathRenderer = subscribeMarkdownMathRenderer(() => {
|
|
2159
|
+
scheduleRebuild();
|
|
2084
2160
|
});
|
|
2161
|
+
const unsubscribeMathImage = subscribeMarkdownMathImage(() => {
|
|
2162
|
+
scheduleRebuild();
|
|
2163
|
+
});
|
|
2164
|
+
function mathOptions() {
|
|
2165
|
+
if (props.mathImages === false) return { enabled: false };
|
|
2166
|
+
const resolvedColor = props.mathColor ?? resolveMarkdownMathColor(defaultStyle.value.fg);
|
|
2167
|
+
return {
|
|
2168
|
+
enabled: true,
|
|
2169
|
+
...props.mathCellWidthPx != null ? { cellWidthPx: props.mathCellWidthPx } : {},
|
|
2170
|
+
...props.mathCellHeightPx != null ? { cellHeightPx: props.mathCellHeightPx } : {},
|
|
2171
|
+
...props.mathScale != null ? { scale: props.mathScale } : {},
|
|
2172
|
+
...resolvedColor != null ? { color: resolvedColor } : {},
|
|
2173
|
+
...props.mathBaselineRatio != null ? { baselineRatio: props.mathBaselineRatio } : {},
|
|
2174
|
+
maxWidthCells: props.mathMaxWidthCells ?? props.w
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
function enqueuePendingMathImages() {
|
|
2178
|
+
if (props.mathImages === false) return;
|
|
2179
|
+
if (!getTerminalGraphicsOutput(terminal)?.capabilities.supported) return;
|
|
2180
|
+
if (!isMarkdownMathImageRendererReady()) {
|
|
2181
|
+
loadMarkdownMathImageRenderer().then((ready) => {
|
|
2182
|
+
if (ready && alive) scheduleRebuild();
|
|
2183
|
+
});
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
const h$1 = Math.max(0, Math.floor(props.h ?? absRect.value.h));
|
|
2187
|
+
enqueueMarkdownMathImages(rows.value, mathOptions(), {
|
|
2188
|
+
firstRow: internalScrollTop.value,
|
|
2189
|
+
lastRow: internalScrollTop.value + h$1
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2085
2192
|
watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
|
|
2086
2193
|
parser.value = markRaw(createTuiMarkdownParser({
|
|
2087
2194
|
streaming: props.streaming,
|
|
@@ -2103,13 +2210,15 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2103
2210
|
minHeight: props.imageMinHeight,
|
|
2104
2211
|
maxHeight: props.imageMaxHeight,
|
|
2105
2212
|
preserveAspectRatio: props.imagePreserveAspectRatio
|
|
2106
|
-
}
|
|
2213
|
+
},
|
|
2214
|
+
math: mathOptions()
|
|
2107
2215
|
}).blocks;
|
|
2108
2216
|
return layoutMarkdownBlocksCached(blocks, props.w, layoutCache);
|
|
2109
2217
|
});
|
|
2110
2218
|
layoutCache = markRaw(nextLayout.cache);
|
|
2111
2219
|
const nextRows = nextLayout.rows;
|
|
2112
2220
|
rows.value = markRaw(nextRows);
|
|
2221
|
+
enqueuePendingMathImages();
|
|
2113
2222
|
reconcileScrollTop();
|
|
2114
2223
|
const nextScrollTop = internalScrollTop.value;
|
|
2115
2224
|
const nextVisibleRows = visibleRowSignatures(nextRows, nextScrollTop);
|
|
@@ -2234,6 +2343,9 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2234
2343
|
pendingSelectionScrollFocusRemap = false;
|
|
2235
2344
|
applyControlledScrollTop(props.scrollTop, remap);
|
|
2236
2345
|
});
|
|
2346
|
+
watch(internalScrollTop, () => {
|
|
2347
|
+
enqueuePendingMathImages();
|
|
2348
|
+
});
|
|
2237
2349
|
watch([
|
|
2238
2350
|
() => props.content,
|
|
2239
2351
|
() => props.blocks,
|
|
@@ -2246,7 +2358,14 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2246
2358
|
() => props.imageMaxWidth,
|
|
2247
2359
|
() => props.imageMinHeight,
|
|
2248
2360
|
() => props.imageMaxHeight,
|
|
2249
|
-
() => props.imagePreserveAspectRatio
|
|
2361
|
+
() => props.imagePreserveAspectRatio,
|
|
2362
|
+
() => props.mathImages,
|
|
2363
|
+
() => props.mathCellWidthPx,
|
|
2364
|
+
() => props.mathCellHeightPx,
|
|
2365
|
+
() => props.mathScale,
|
|
2366
|
+
() => props.mathColor,
|
|
2367
|
+
() => props.mathMaxWidthCells,
|
|
2368
|
+
() => props.mathBaselineRatio
|
|
2250
2369
|
], () => {
|
|
2251
2370
|
scheduleRebuild();
|
|
2252
2371
|
}, { immediate: true });
|
|
@@ -2449,6 +2568,8 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2449
2568
|
alive = false;
|
|
2450
2569
|
rebuildVersion++;
|
|
2451
2570
|
unsubscribeGraphicsOutput();
|
|
2571
|
+
unsubscribeMathRenderer();
|
|
2572
|
+
unsubscribeMathImage();
|
|
2452
2573
|
clearMarkdownImageGraphics(terminal, fullRect.value);
|
|
2453
2574
|
});
|
|
2454
2575
|
useRenderNode(() => ({
|
|
@@ -2633,4 +2754,4 @@ function createMarkdownBlockSource(options) {
|
|
|
2633
2754
|
}
|
|
2634
2755
|
|
|
2635
2756
|
//#endregion
|
|
2636
|
-
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };
|
|
2757
|
+
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathRenderer, markdownThemeSignature, paintMarkdownVisualRow, subscribeMarkdownMathRenderer };
|