@simon_he/vue-tui 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +18 -18
  2. package/dist/{TMermaidText-frVr8Ysb.js → TMermaidText-wLnMMvVS.js} +1 -1
  3. package/dist/{TSelect-Dx34M1pF.js → TSelect-CMBOOFwK.js} +5 -5
  4. package/dist/{TTree-DdAjCPHu.js → TTree-kk8ntrpg.js} +6 -5
  5. package/dist/agent/mermaid.d.cts +2 -2
  6. package/dist/agent/mermaid.d.ts +2 -2
  7. package/dist/agent.cjs +542 -266
  8. package/dist/agent.js +25 -82
  9. package/dist/{block-source-Crro3mxi.js → block-source-DhzajB9H.js} +299 -239
  10. package/dist/cli.cjs +57 -16
  11. package/dist/cli.js +70 -15
  12. package/dist/core.js +3 -3
  13. package/dist/{hyperlink-_1FAj7Wq.js → create-terminal-Db_-UGVQ.js} +474 -102
  14. package/dist/{dom-renderer-DXx8fWzD.js → dom-renderer-BfwAEQMf.js} +3 -2
  15. package/dist/experimental.cjs +103 -66
  16. package/dist/experimental.js +20 -74
  17. package/dist/hyperlink-clbb_ha_.js +100 -0
  18. package/dist/index.js +8 -8
  19. package/dist/{linkify-BjI4zVfY.js → linkify-CL9wwnZN.js} +3 -3
  20. package/dist/markdown.cjs +521 -212
  21. package/dist/markdown.d.cts +3 -0
  22. package/dist/markdown.d.ts +3 -0
  23. package/dist/markdown.js +75 -8
  24. package/dist/math-image-BZhmfUeu.js +308 -0
  25. package/dist/mermaid-N3kGTHFy.js +1697 -0
  26. package/dist/mermaid.cjs +1460 -6
  27. package/dist/mermaid.d.cts +4 -0
  28. package/dist/mermaid.d.ts +4 -0
  29. package/dist/mermaid.js +7 -5
  30. package/dist/{rect-DUpeDfBg.js → rect-JzWMJxxc.js} +1 -1
  31. package/dist/renderer/cli/stdout-renderer.d.ts +44 -0
  32. package/dist/renderer/terminal-graphics.d.ts +61 -0
  33. package/dist/renderer-dom.js +4 -4
  34. package/dist/{terminal-graphics-Bbe83wSr.js → terminal-graphics-2FTRg9x3.js} +90 -2
  35. package/dist/{tlog-theme-i6S_Oftd.js → tlog-theme-B91Wv7Zj.js} +5 -4
  36. package/dist/vue/components/TAgentTerminalGraphic.d.ts +1 -1
  37. package/dist/vue/components/TMarkdownText.d.ts +70 -2
  38. package/dist/vue/components/TMermaidImage.d.ts +920 -0
  39. package/dist/vue/components/TMermaidText.d.ts +2 -2
  40. package/dist/vue/components/TVirtualMarkdown.d.ts +70 -2
  41. package/dist/vue/markdown/ast.d.ts +5 -0
  42. package/dist/vue/markdown/document.d.ts +3 -0
  43. package/dist/vue/markdown/math-image.d.ts +68 -0
  44. package/dist/vue/markdown/theme.d.ts +12 -0
  45. package/dist/vue/markdown/types.d.ts +12 -2
  46. package/dist/vue/mermaid/beautiful-mermaid.d.ts +284 -240
  47. package/dist/vue/mermaid/mermaid-image.d.ts +73 -0
  48. package/dist/vue.js +9 -9
  49. package/package.json +24 -10
  50. package/dist/mermaid-BNhM1jAZ.js +0 -123
  51. package/dist/width-GqllnV8C.js +0 -474
  52. /package/dist/{ansi-palette-CH6jc2Z7.js → ansi-palette-BHF1rN7R.js} +0 -0
@@ -1,9 +1,10 @@
1
- import { hasEncodedControl, sanitizeDomHref } from "./hyperlink-_1FAj7Wq.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-DUpeDfBg.js";
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-Bbe83wSr.js";
6
+ import { createIterm2InlineImageSequence, createKittyDeleteGraphicsSequence, createKittyGraphicsSequence, createKittyPlacementSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, isTerminalGraphicsProtocol, stableTerminalGraphicNumericId, subscribeTerminalGraphicsOutput } from "./terminal-graphics-2FTRg9x3.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
9
  import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
9
10
 
@@ -113,213 +114,6 @@ function readMarkdownImageDimensions(base64) {
113
114
  return null;
114
115
  }
115
116
 
116
- //#endregion
117
- //#region src/vue/markdown/math.ts
118
- let loadedKatex = null;
119
- let katexLoad = null;
120
- const katexListeners = new Set();
121
- function resolveKatexRenderer(value) {
122
- if (!value || typeof value !== "object") return null;
123
- const renderToString = value.renderToString;
124
- return typeof renderToString === "function" ? { renderToString } : null;
125
- }
126
- function subscribeMarkdownMathRenderer(listener) {
127
- katexListeners.add(listener);
128
- return () => katexListeners.delete(listener);
129
- }
130
- async function loadMarkdownMathRenderer() {
131
- if (loadedKatex) return true;
132
- if (!katexLoad) katexLoad = import("katex").then((mod) => {
133
- const renderer = resolveKatexRenderer(mod.default) ?? resolveKatexRenderer(mod);
134
- if (!renderer) return false;
135
- loadedKatex = renderer;
136
- for (const listener of katexListeners) listener();
137
- return true;
138
- }).catch(() => false);
139
- return katexLoad;
140
- }
141
- const KATEX_TEXT_OPTIONS = Object.freeze({
142
- output: "mathml",
143
- throwOnError: false,
144
- strict: "ignore",
145
- trust: false,
146
- maxSize: 20,
147
- maxExpand: 200
148
- });
149
- const KATEX_VALIDATE_OPTIONS = Object.freeze({
150
- ...KATEX_TEXT_OPTIONS,
151
- throwOnError: true
152
- });
153
- const TEX_SYMBOLS = Object.freeze({
154
- alpha: "α",
155
- beta: "β",
156
- gamma: "γ",
157
- delta: "δ",
158
- epsilon: "ε",
159
- theta: "θ",
160
- lambda: "λ",
161
- mu: "μ",
162
- pi: "π",
163
- rho: "ρ",
164
- sigma: "σ",
165
- tau: "τ",
166
- phi: "φ",
167
- omega: "ω",
168
- Gamma: "Γ",
169
- Delta: "Δ",
170
- Theta: "Θ",
171
- Lambda: "Λ",
172
- Pi: "Π",
173
- Sigma: "Σ",
174
- Phi: "Φ",
175
- Omega: "Ω",
176
- int: "∫",
177
- sum: "∑",
178
- prod: "∏",
179
- infty: "∞",
180
- infinity: "∞",
181
- pm: "±",
182
- times: "×",
183
- cdot: "·",
184
- div: "÷",
185
- le: "≤",
186
- leq: "≤",
187
- ge: "≥",
188
- geq: "≥",
189
- neq: "≠",
190
- ne: "≠",
191
- approx: "≈",
192
- equiv: "≡",
193
- to: "→",
194
- rightarrow: "→",
195
- leftarrow: "←",
196
- leftrightarrow: "↔",
197
- partial: "∂",
198
- nabla: "∇",
199
- sqrt: "√"
200
- });
201
- const SUPPORTED_COMMANDS = new Set([
202
- ...Object.keys(TEX_SYMBOLS),
203
- "frac",
204
- "sqrt"
205
- ]);
206
- function decodeXmlEntities(value) {
207
- return value.replace(/&(?:#(\d+)|#x([0-9a-f]+)|([a-z]+));/gi, (match, dec, hex, named) => {
208
- if (dec) return String.fromCodePoint(Number(dec));
209
- if (hex) return String.fromCodePoint(Number.parseInt(hex, 16));
210
- switch (String(named).toLowerCase()) {
211
- case "amp": return "&";
212
- case "lt": return "<";
213
- case "gt": return ">";
214
- case "quot": return "\"";
215
- case "apos": return "'";
216
- default: return match;
217
- }
218
- });
219
- }
220
- function mathmlTagName(tag) {
221
- const start = tag.startsWith("/") ? 1 : 0;
222
- let end = start;
223
- while (end < tag.length) {
224
- const ch = tag[end];
225
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "/") break;
226
- end++;
227
- }
228
- return tag.slice(start, end).toLowerCase();
229
- }
230
- function textFromKatexMathml(html) {
231
- let readable = "";
232
- let offset = 0;
233
- const lowerHtml = html.toLowerCase();
234
- while (offset < html.length) {
235
- const tagStart = html.indexOf("<", offset);
236
- if (tagStart < 0) {
237
- readable += html.slice(offset);
238
- break;
239
- }
240
- readable += html.slice(offset, tagStart);
241
- const tagEnd = html.indexOf(">", tagStart + 1);
242
- if (tagEnd < 0) break;
243
- const tag = html.slice(tagStart + 1, tagEnd).trim();
244
- const tagName = mathmlTagName(tag);
245
- const closing = tag.startsWith("/");
246
- if (!closing && tagName === "annotation") {
247
- const annotationEnd = lowerHtml.indexOf("</annotation>", tagEnd + 1);
248
- offset = annotationEnd < 0 ? tagEnd + 1 : annotationEnd + 13;
249
- continue;
250
- }
251
- if (!closing && tagName === "mfrac") readable += "(";
252
- else if (closing && tagName === "mfrac") readable += ")";
253
- else if (!closing && tagName === "mspace") readable += " ";
254
- offset = tagEnd + 1;
255
- }
256
- const text = sanitizeInlineText(decodeXmlEntities(readable).replace(/[<>]/g, "").replace(/\s+/g, " ").trim());
257
- return text;
258
- }
259
- function approximateTexToUnicode(tex) {
260
- let out = String(tex ?? "");
261
- out = out.replace(/\\frac\s*\{([^{}]*)\}\s*\{([^{}]*)\}/g, "$1/$2");
262
- out = out.replace(/\\sqrt\s*\{([^{}]*)\}/g, "√($1)");
263
- out = out.replace(/\\([A-Za-z]+)\b/g, (_match, command) => TEX_SYMBOLS[command] ?? command);
264
- out = out.replace(/\\([^A-Za-z])/g, "$1");
265
- out = out.replace(/[{}]/g, "");
266
- out = out.replace(/\s+/g, " ").trim();
267
- return sanitizeInlineText(out.replace(/[<>]/g, ""));
268
- }
269
- function canRenderTerminalMath(tex, katex) {
270
- const source = String(tex ?? "").trim();
271
- if (!source || source.length > 160) return false;
272
- if (/\\(?:begin|end|left|right|matrix|cases|array|align|color|href|html|includegraphics|class|style|tag|text|operatorname)\b/.test(source)) return false;
273
- const commands = source.matchAll(/\\([A-Za-z]+)\b/g);
274
- for (const match of commands) if (!SUPPORTED_COMMANDS.has(match[1] ?? "")) return false;
275
- try {
276
- katex.renderToString(source, KATEX_VALIDATE_OPTIONS);
277
- } catch {
278
- return false;
279
- }
280
- return true;
281
- }
282
- function renderMarkdownInlineMathSegment(tex) {
283
- const source = String(tex ?? "");
284
- if (!source.trim()) return {
285
- text: "",
286
- supported: false
287
- };
288
- const katex = loadedKatex;
289
- if (!katex) {
290
- loadMarkdownMathRenderer();
291
- return {
292
- text: "",
293
- supported: false
294
- };
295
- }
296
- if (!canRenderTerminalMath(source, katex)) return {
297
- text: "",
298
- supported: false
299
- };
300
- try {
301
- const html = katex.renderToString(source, KATEX_TEXT_OPTIONS);
302
- const rendered = /\\frac\b/.test(source) ? approximateTexToUnicode(source) : textFromKatexMathml(html);
303
- if (rendered && !rendered.includes("\\")) return {
304
- text: rendered,
305
- supported: true
306
- };
307
- } catch {
308
- return {
309
- text: "",
310
- supported: false
311
- };
312
- }
313
- const approximate = approximateTexToUnicode(source);
314
- return approximate && !approximate.includes("\\") ? {
315
- text: approximate,
316
- supported: true
317
- } : {
318
- text: "",
319
- supported: false
320
- };
321
- }
322
-
323
117
  //#endregion
324
118
  //#region src/vue/markdown/parser.ts
325
119
  const markdownInstanceCache = new Map();
@@ -621,12 +415,34 @@ function inlineNodeSegments(nodes, theme, inheritedStyle, options = {}) {
621
415
  case "math_inline":
622
416
  {
623
417
  const source = stringProp(node, "content");
624
- const raw = stringProp(node, "raw") || `$${source}$`;
625
- const rendered = renderMarkdownInlineMathSegment(source);
626
- pushTextSegments(out, rendered.supported ? rendered.text : raw, mergeStyle$1(inheritedStyle, theme.inlineCode), void 0, {
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
+ } : {
627
443
  source,
628
444
  raw,
629
- rendered: rendered.supported
445
+ rendered: false
630
446
  });
631
447
  }
632
448
  break;
@@ -664,14 +480,16 @@ function inlineBlock(key, segments, context) {
664
480
  function blockFromParagraph(node, key, context, theme) {
665
481
  return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, void 0, {
666
482
  imageResolver: context.imageResolver,
667
- imageSize: context.imageSize
483
+ imageSize: context.imageSize,
484
+ math: context.math
668
485
  }), context);
669
486
  }
670
487
  function blockFromHeading(node, key, context, theme) {
671
488
  const level = Math.min(6, Math.max(1, Math.floor(numberProp(node, "level") ?? 1)));
672
489
  return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1], {
673
490
  imageResolver: context.imageResolver,
674
- imageSize: context.imageSize
491
+ imageSize: context.imageSize,
492
+ math: context.math
675
493
  }), context);
676
494
  }
677
495
  function blockFromCodeBlock(node, key, context, theme) {
@@ -690,7 +508,10 @@ function blockFromHtmlBlock(node, key, context, theme) {
690
508
  type: "html_block",
691
509
  raw: stringProp(node, "raw"),
692
510
  content: stringProp(node, "content")
693
- }], theme, void 0, { imageResolver: context.imageResolver }), context);
511
+ }], theme, void 0, {
512
+ imageResolver: context.imageResolver,
513
+ math: context.math
514
+ }), context);
694
515
  }
695
516
  function tableRows$1(node, key) {
696
517
  const value = node[key];
@@ -710,14 +531,16 @@ function blockFromTable(node, key, context, theme) {
710
531
  const header = tableCells(headerRow ?? { type: "table_row" }).map((cell) => ({
711
532
  segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong, {
712
533
  imageResolver: context.imageResolver,
713
- imageSize: context.imageSize
534
+ imageSize: context.imageSize,
535
+ math: context.math
714
536
  }),
715
537
  align: tableCellAlign(cell)
716
538
  }));
717
539
  const rows = tableRows$1(node, "rows").map((row) => tableCells(row).map((cell) => ({
718
540
  segments: inlineNodeSegments(nodeChildren(cell), theme, void 0, {
719
541
  imageResolver: context.imageResolver,
720
- imageSize: context.imageSize
542
+ imageSize: context.imageSize,
543
+ math: context.math
721
544
  }),
722
545
  align: tableCellAlign(cell)
723
546
  })));
@@ -755,11 +578,13 @@ function listItemBlocks(item, index, ordered, start, context, theme, keyPrefix)
755
578
  const indentSegment = { text: spaces(markerCells) };
756
579
  const firstContext = {
757
580
  prefixSegments: [...context.prefixSegments, markerSegment],
758
- continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
581
+ continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment],
582
+ math: context.math
759
583
  };
760
584
  const nestedContext = {
761
585
  prefixSegments: [...context.continuationPrefixSegments, indentSegment],
762
- continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment]
586
+ continuationPrefixSegments: [...context.continuationPrefixSegments, indentSegment],
587
+ math: context.math
763
588
  };
764
589
  const children = nodeChildren(item);
765
590
  if (!children.length) return [inlineBlock(`${keyPrefix}-empty`, [], firstContext)];
@@ -791,6 +616,61 @@ function listBlocks(node, context, theme, keyPrefix) {
791
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}`));
792
617
  return out;
793
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
+ }
794
674
  function nodeToBlocks(node, context, theme, keyPrefix) {
795
675
  switch (node.type) {
796
676
  case "paragraph":
@@ -809,6 +689,7 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
809
689
  style: theme.thematicBreak,
810
690
  prefixSegments: context.prefixSegments
811
691
  }];
692
+ case "math_block": return [inlineBlock(keyPrefix, blockMathSegments(stringProp(node, "content"), stringProp(node, "raw") || `$${stringProp(node, "content")}$`, context.math ?? { enabled: true }, theme), context)];
812
693
  case "blockquote": {
813
694
  const quoteSegment = {
814
695
  text: "│ ",
@@ -818,7 +699,8 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
818
699
  prefixSegments: [...context.prefixSegments, quoteSegment],
819
700
  continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment],
820
701
  imageResolver: context.imageResolver,
821
- imageSize: context.imageSize
702
+ imageSize: context.imageSize,
703
+ math: context.math
822
704
  };
823
705
  return childSequenceToBlocks(nodeChildren(node), quoteContext, theme, keyPrefix);
824
706
  }
@@ -828,22 +710,30 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
828
710
  case "table": return [blockFromTable(node, keyPrefix, context, theme)];
829
711
  case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme, void 0, {
830
712
  imageResolver: context.imageResolver,
831
- imageSize: context.imageSize
713
+ imageSize: context.imageSize,
714
+ math: context.math
832
715
  }), context)];
833
716
  case "html_block":
834
717
  if ("content" in node) return [blockFromHtmlBlock(node, keyPrefix, context, theme)];
835
718
  break;
836
- case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
719
+ case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
720
+ imageResolver: context.imageResolver,
721
+ math: context.math
722
+ }), context)];
837
723
  }
838
724
  if (nodeChildren(node).length) return childSequenceToBlocks(nodeChildren(node), context, theme, keyPrefix);
839
- return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
725
+ return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, {
726
+ imageResolver: context.imageResolver,
727
+ math: context.math
728
+ }), context)];
840
729
  }
841
730
  function markdownAstToBlocks(nodes, theme, options = {}) {
842
731
  const blocks = trimBlockArray(childSequenceToBlocks(nodes, {
843
732
  prefixSegments: EMPTY_PREFIX,
844
733
  continuationPrefixSegments: EMPTY_PREFIX,
845
734
  imageResolver: options.imageResolver,
846
- imageSize: options.imageSize
735
+ imageSize: options.imageSize,
736
+ math: options.math
847
737
  }, theme, "md"));
848
738
  return blocks.length ? blocks : [{
849
739
  type: "blank",
@@ -992,7 +882,8 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
992
882
  style: segment.style,
993
883
  cells,
994
884
  graphic: segment.graphic,
995
- fallbackText: segment.text
885
+ fallbackText: segment.text,
886
+ ...segment.mathAction ? { mathAction: segment.mathAction } : {}
996
887
  });
997
888
  row.remaining -= cells;
998
889
  continue;
@@ -1260,7 +1151,8 @@ function styleSignature(style) {
1260
1151
  function inlineSegmentSignature(segment) {
1261
1152
  const graphic = segment.graphic ? [
1262
1153
  segment.graphic.kind,
1263
- segment.graphic.src,
1154
+ segment.graphic.src ?? "",
1155
+ segment.graphic.tex ?? "",
1264
1156
  segment.graphic.alt ?? "",
1265
1157
  segment.graphic.mime ?? "",
1266
1158
  segment.graphic.base64 ?? "",
@@ -1435,7 +1327,8 @@ const DEFAULT_TUI_MARKDOWN_THEME = Object.freeze({
1435
1327
  }),
1436
1328
  codeBlock: freezeStyle({ fg: "yellowBright" }),
1437
1329
  thematicBreak: freezeStyle({ dim: true }),
1438
- html: freezeStyle({ dim: true })
1330
+ html: freezeStyle({ dim: true }),
1331
+ math: freezeStyle({ fg: "cyanBright" })
1439
1332
  });
1440
1333
  function stableSerialize(value) {
1441
1334
  if (value == null) return "null";
@@ -1461,7 +1354,8 @@ function resolveTuiMarkdownTheme(overrides) {
1461
1354
  listMarker: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.listMarker, overrides.listMarker),
1462
1355
  codeBlock: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.codeBlock, overrides.codeBlock),
1463
1356
  thematicBreak: mergeFrozenStyle(DEFAULT_TUI_MARKDOWN_THEME.thematicBreak, overrides.thematicBreak),
1464
- 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)
1465
1359
  });
1466
1360
  }
1467
1361
 
@@ -1477,7 +1371,8 @@ function buildMarkdownBlocks(content, parser, options) {
1477
1371
  const nodes = parser.parse(content, options?.final ?? true);
1478
1372
  const blocks = markdownAstToBlocks(nodes, theme, {
1479
1373
  imageResolver: options?.imageResolver,
1480
- imageSize: options?.imageSize
1374
+ imageSize: options?.imageSize,
1375
+ math: options?.math
1481
1376
  });
1482
1377
  return {
1483
1378
  nodes,
@@ -1664,11 +1559,12 @@ function findMarkdownMathActionAt(rows, point, options) {
1664
1559
  if (!math || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1665
1560
  const visibleStart = Math.max(segmentStart, clipStart);
1666
1561
  const visibleEnd = Math.min(segmentEnd, clipEnd);
1562
+ const graphicHeight = segment.graphic ? Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1)) : 1;
1667
1563
  const rect = {
1668
1564
  x: r.x + visibleStart - clipStart,
1669
1565
  y: r.y + rowIndex - firstRow,
1670
1566
  w: visibleEnd - visibleStart,
1671
- h: 1
1567
+ h: graphicHeight
1672
1568
  };
1673
1569
  if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
1674
1570
  return {
@@ -1684,6 +1580,97 @@ function findMarkdownMathActionAt(rows, point, options) {
1684
1580
  return null;
1685
1581
  }
1686
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
+
1687
1674
  //#endregion
1688
1675
  //#region src/vue/markdown/render.ts
1689
1676
  const mergedStyleCache = new WeakMap();
@@ -1710,7 +1697,7 @@ function mergeStyle(base, overlay) {
1710
1697
  }
1711
1698
  const activeMarkdownImageGraphics = new WeakMap();
1712
1699
  function markdownImageGraphicId(segment, identity) {
1713
- 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 ?? ""}`)}`;
1714
1701
  }
1715
1702
  function rememberMarkdownImageGraphic(terminal, id, rect) {
1716
1703
  let active = activeMarkdownImageGraphics.get(terminal);
@@ -1736,7 +1723,7 @@ function clearStaleMarkdownImageGraphicsForRow(terminal, y, keepIds, keepVisible
1736
1723
  }
1737
1724
  }
1738
1725
  function canAttemptMarkdownImageGraphic(terminal, segment) {
1739
- if (segment.kind !== "image" || !segment.base64) return false;
1726
+ if (!segment.base64) return false;
1740
1727
  const output = getTerminalGraphicsOutput(terminal);
1741
1728
  const protocol = output?.capabilities.preferredProtocol;
1742
1729
  return Boolean(output?.capabilities.supported && isTerminalGraphicsProtocol(protocol));
@@ -1750,7 +1737,7 @@ function clearMarkdownImageGraphics(terminal, rect) {
1750
1737
  }
1751
1738
  }
1752
1739
  function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
1753
- if (segment.kind !== "image" || !segment.base64) return "unavailable";
1740
+ if (!segment.base64) return "unavailable";
1754
1741
  const output = getTerminalGraphicsOutput(terminal);
1755
1742
  const protocol = output?.capabilities.preferredProtocol;
1756
1743
  if (!output?.capabilities.supported || !isTerminalGraphicsProtocol(protocol)) return "unavailable";
@@ -1792,7 +1779,7 @@ function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
1792
1779
  sequence,
1793
1780
  resizeSequence,
1794
1781
  clearSequence,
1795
- fallbackText: segment.alt ?? "image"
1782
+ fallbackText: segment.alt ?? (segment.kind === "math" ? segment.raw ?? segment.tex ?? "math" : "image")
1796
1783
  });
1797
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 ?? ""}`);
1798
1785
  if (accepted) rememberMarkdownImageGraphic(terminal, id, {
@@ -1896,7 +1883,7 @@ function paintMarkdownVisualRow(terminal, row, options) {
1896
1883
  const queuedGraphic = queueResult === "queued";
1897
1884
  const suppressFallback = queuedGraphic || queueResult === "rejected" || graphicAvailable === true;
1898
1885
  if (queuedGraphic) queuedGraphics.add(markdownImageGraphicId(segment.graphic, `${row.key}:${segmentIndex}`));
1899
- 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), {
1900
1887
  x: cellX,
1901
1888
  y: options.y,
1902
1889
  style: suppressFallback ? options.baseStyle : segmentStyle
@@ -2088,6 +2075,34 @@ const TVirtualMarkdown = defineComponent({
2088
2075
  type: Boolean,
2089
2076
  default: true
2090
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
+ },
2091
2106
  imageActions: {
2092
2107
  type: Boolean,
2093
2108
  default: false
@@ -2138,10 +2153,42 @@ const TVirtualMarkdown = defineComponent({
2138
2153
  const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
2139
2154
  const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
2140
2155
  graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
2156
+ enqueuePendingMathImages();
2141
2157
  });
2142
2158
  const unsubscribeMathRenderer = subscribeMarkdownMathRenderer(() => {
2143
2159
  scheduleRebuild();
2144
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
+ }
2145
2192
  watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
2146
2193
  parser.value = markRaw(createTuiMarkdownParser({
2147
2194
  streaming: props.streaming,
@@ -2163,13 +2210,15 @@ const TVirtualMarkdown = defineComponent({
2163
2210
  minHeight: props.imageMinHeight,
2164
2211
  maxHeight: props.imageMaxHeight,
2165
2212
  preserveAspectRatio: props.imagePreserveAspectRatio
2166
- }
2213
+ },
2214
+ math: mathOptions()
2167
2215
  }).blocks;
2168
2216
  return layoutMarkdownBlocksCached(blocks, props.w, layoutCache);
2169
2217
  });
2170
2218
  layoutCache = markRaw(nextLayout.cache);
2171
2219
  const nextRows = nextLayout.rows;
2172
2220
  rows.value = markRaw(nextRows);
2221
+ enqueuePendingMathImages();
2173
2222
  reconcileScrollTop();
2174
2223
  const nextScrollTop = internalScrollTop.value;
2175
2224
  const nextVisibleRows = visibleRowSignatures(nextRows, nextScrollTop);
@@ -2294,6 +2343,9 @@ const TVirtualMarkdown = defineComponent({
2294
2343
  pendingSelectionScrollFocusRemap = false;
2295
2344
  applyControlledScrollTop(props.scrollTop, remap);
2296
2345
  });
2346
+ watch(internalScrollTop, () => {
2347
+ enqueuePendingMathImages();
2348
+ });
2297
2349
  watch([
2298
2350
  () => props.content,
2299
2351
  () => props.blocks,
@@ -2306,7 +2358,14 @@ const TVirtualMarkdown = defineComponent({
2306
2358
  () => props.imageMaxWidth,
2307
2359
  () => props.imageMinHeight,
2308
2360
  () => props.imageMaxHeight,
2309
- () => 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
2310
2369
  ], () => {
2311
2370
  scheduleRebuild();
2312
2371
  }, { immediate: true });
@@ -2510,6 +2569,7 @@ const TVirtualMarkdown = defineComponent({
2510
2569
  rebuildVersion++;
2511
2570
  unsubscribeGraphicsOutput();
2512
2571
  unsubscribeMathRenderer();
2572
+ unsubscribeMathImage();
2513
2573
  clearMarkdownImageGraphics(terminal, fullRect.value);
2514
2574
  });
2515
2575
  useRenderNode(() => ({