@simon_he/vue-tui 1.0.1 → 1.1.0

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 (128) hide show
  1. package/README.md +139 -46
  2. package/dist/TMermaidText-frVr8Ysb.js +885 -0
  3. package/dist/{TSelect-HzILuti9.js → TSelect-tJ87rKPM.js} +59 -242
  4. package/dist/{TTree-ChOot2Bh.js → TTree-CGdE-CYu.js} +138 -102
  5. package/dist/agent/mermaid.cjs +2 -0
  6. package/dist/agent/mermaid.d.cts +2 -0
  7. package/dist/agent/mermaid.d.ts +2 -0
  8. package/dist/agent/mermaid.js +1 -0
  9. package/dist/agent.cjs +31188 -24907
  10. package/dist/agent.d.cts +11 -1
  11. package/dist/agent.d.ts +11 -1
  12. package/dist/agent.js +516 -11
  13. package/dist/{block-source-CKUOnkgT.js → block-source-CrlmaBM5.js} +1039 -66
  14. package/dist/cli/input.d.ts +8 -0
  15. package/dist/cli/stdin-buffer.d.ts +1 -1
  16. package/dist/cli/video/ffmpeg-frame-source.d.ts +10 -0
  17. package/dist/cli/video/png-frame-parser.d.ts +4 -0
  18. package/dist/cli/video/raw-frame-parser.d.ts +1 -0
  19. package/dist/cli/video/yt-dlp-frame-source.d.ts +11 -0
  20. package/dist/cli.cjs +5470 -6176
  21. package/dist/cli.d.cts +6 -2
  22. package/dist/cli.d.ts +6 -2
  23. package/dist/cli.js +3747 -350
  24. package/dist/core/buffer/buffer.d.ts +2 -2
  25. package/dist/core/buffer/eaw-ranges-unicode-17.d.ts +14 -0
  26. package/dist/core/perf/instrumentation-noop.d.ts +48 -0
  27. package/dist/core/perf/instrumentation.d.ts +136 -0
  28. package/dist/core/terminal/create-terminal.d.ts +1 -0
  29. package/dist/core/types.d.ts +0 -4
  30. package/dist/core.cjs +851 -1462
  31. package/dist/core.js +4 -5
  32. package/dist/create-terminal-app.d.ts +24 -1
  33. package/dist/debug-logger-CmsGI4of.js +74 -0
  34. package/dist/{dom-renderer-BqGijQID.js → dom-renderer-DXx8fWzD.js} +2 -3
  35. package/dist/experimental/3d/bun/contributor-avatar-atlas.d.ts +12 -0
  36. package/dist/experimental/3d/bun/png.d.ts +1 -0
  37. package/dist/experimental/3d/bun/terminal-badge-scene.d.ts +11 -0
  38. package/dist/experimental/3d/bun/terminal-badge.wgsl.d.ts +1 -0
  39. package/dist/experimental/3d/bun/webgpu-renderer.d.ts +26 -0
  40. package/dist/experimental/3d/bun.cjs +7271 -0
  41. package/dist/experimental/3d/bun.d.cts +4 -0
  42. package/dist/experimental/3d/bun.d.ts +4 -0
  43. package/dist/experimental/3d/bun.js +7390 -0
  44. package/dist/experimental/video/node.cjs +497 -0
  45. package/dist/experimental/video/node.d.cts +4 -0
  46. package/dist/experimental/video/node.d.ts +4 -0
  47. package/dist/experimental/video/node.js +610 -0
  48. package/dist/experimental.cjs +6876 -6192
  49. package/dist/experimental.d.cts +7 -0
  50. package/dist/experimental.d.ts +7 -0
  51. package/dist/experimental.js +3005 -9
  52. package/dist/frame-perf-CHpWhbC4.js +111 -0
  53. package/dist/{create-terminal-C2oiWFJU.js → hyperlink-_1FAj7Wq.js} +455 -19
  54. package/dist/index.cjs +5025 -10332
  55. package/dist/index.js +14 -13
  56. package/dist/{linkify-D_gzn9Xq.js → linkify-D68T7rdW.js} +193 -3
  57. package/dist/markdown.cjs +21295 -12691
  58. package/dist/markdown.d.cts +1 -1
  59. package/dist/markdown.d.ts +1 -1
  60. package/dist/markdown.js +211 -51
  61. package/dist/mermaid-BNhM1jAZ.js +123 -0
  62. package/dist/mermaid.cjs +1707 -0
  63. package/dist/mermaid.d.cts +3 -0
  64. package/dist/mermaid.d.ts +3 -0
  65. package/dist/mermaid.js +6 -0
  66. package/dist/observability/frame-perf-store.d.ts +2 -1
  67. package/dist/observability/frame-perf.d.ts +33 -0
  68. package/dist/observability.cjs +106 -107
  69. package/dist/observability.d.cts +2 -2
  70. package/dist/observability.d.ts +2 -2
  71. package/dist/observability.js +3 -3
  72. package/dist/{wheel-scroll-vBRaQ7p-.js → rect-DUpeDfBg.js} +253 -117
  73. package/dist/renderer/cli/index.d.ts +1 -1
  74. package/dist/renderer/cli/stdout-metrics.d.ts +10 -0
  75. package/dist/renderer/cli/stdout-renderer.d.ts +53 -7
  76. package/dist/renderer/terminal-graphic-render-queue.d.ts +50 -0
  77. package/dist/renderer/terminal-graphics-trace.d.ts +53 -0
  78. package/dist/renderer/terminal-graphics.d.ts +147 -0
  79. package/dist/renderer-dom.cjs +364 -903
  80. package/dist/renderer-dom.js +4 -5
  81. package/dist/runtime/index.d.ts +2 -0
  82. package/dist/{runtime-rfcZCyJq.js → runtime-Byf8XXOT.js} +28 -15
  83. package/dist/runtime.cjs +427 -889
  84. package/dist/runtime.js +2 -2
  85. package/dist/terminal-graphics-Bbe83wSr.js +597 -0
  86. package/dist/{terminal-selection-DBinO6p5.js → terminal-selection-4W1SJj4c.js} +4 -1
  87. package/dist/{tlog-theme-CvLFNMwk.js → tlog-theme-Bul2Na3u.js} +1237 -12
  88. package/dist/{trace-CjzYeIxN.js → trace-Bh8WnoMo.js} +5 -0
  89. package/dist/vue/agent/create-png-terminal-graphic-renderer.d.ts +19 -0
  90. package/dist/vue/components/T3DViewport.d.ts +473 -0
  91. package/dist/vue/components/TAgentTerminalGraphic.d.ts +494 -0
  92. package/dist/vue/components/TCharts.d.ts +1121 -0
  93. package/dist/vue/components/TFeedback.d.ts +1 -1
  94. package/dist/vue/components/TFlex.d.ts +389 -0
  95. package/dist/vue/components/TForm.d.ts +4 -4
  96. package/dist/vue/components/TLogSearchBar.d.ts +3 -3
  97. package/dist/vue/components/TLogSearchPager.d.ts +2 -2
  98. package/dist/vue/components/TMarkdownText.d.ts +162 -4
  99. package/dist/vue/components/TMermaidText.d.ts +900 -0
  100. package/dist/vue/components/TVideo.d.ts +291 -0
  101. package/dist/vue/components/TVirtualList.d.ts +9 -0
  102. package/dist/vue/components/TVirtualMarkdown.d.ts +170 -9
  103. package/dist/vue/components/TVirtualRows.d.ts +9 -0
  104. package/dist/vue/components/TerminalProvider.d.ts +2 -0
  105. package/dist/vue/context.d.ts +24 -0
  106. package/dist/vue/index.d.ts +5 -0
  107. package/dist/vue/layout/flex.d.ts +67 -0
  108. package/dist/vue/markdown/ast.d.ts +5 -2
  109. package/dist/vue/markdown/document.d.ts +5 -0
  110. package/dist/vue/markdown/image-actions.d.ts +14 -0
  111. package/dist/vue/markdown/image.d.ts +11 -0
  112. package/dist/vue/markdown/link-actions.d.ts +14 -0
  113. package/dist/vue/markdown/math-actions.d.ts +14 -0
  114. package/dist/vue/markdown/math.d.ts +6 -0
  115. package/dist/vue/markdown/render.d.ts +28 -0
  116. package/dist/vue/markdown/types.d.ts +78 -0
  117. package/dist/vue/mermaid/beautiful-mermaid.d.ts +1436 -0
  118. package/dist/vue/render/render-manager.d.ts +7 -0
  119. package/dist/vue/video/types.d.ts +39 -0
  120. package/dist/vue.cjs +7215 -12430
  121. package/dist/vue.js +704 -38
  122. package/dist/wheel-scroll-DASkEZr-.js +106 -0
  123. package/dist/{hyperlink-Bn02UXJb.js → width-GqllnV8C.js} +166 -111
  124. package/package.json +141 -18
  125. package/dist/frame-perf-C6Xfg85O.js +0 -24
  126. package/dist/sgr-BmzxEjAO.js +0 -304
  127. /package/dist/{ansi-palette-CNXqFciC.js → ansi-palette-CH6jc2Z7.js} +0 -0
  128. /package/dist/{path-suggest-BNqcuE3-.js → path-suggest-Dndu4dTi.js} +0 -0
@@ -1,9 +1,296 @@
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";
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";
3
+ import { terminalSelectionRowSpans, terminalSelectionVisibleRowSpans } from "./terminal-selection-4W1SJj4c.js";
4
+ import { createDebugLogger, isDebugEnabled } from "./debug-logger-CmsGI4of.js";
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";
4
7
  import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, ref, shallowRef, watch, watchEffect } from "vue";
8
+ import katex from "katex";
5
9
  import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
6
10
 
11
+ //#region src/vue/markdown/image.ts
12
+ const DATA_IMAGE_RE = /^data:(image\/[A-Za-z0-9.+-]+);base64,([A-Za-z0-9+/=\s]+)$/i;
13
+ const SAFE_IMAGE_MIME_RE = /^image\/(?:png|jpeg|jpg|gif|webp)$/i;
14
+ function sanitizeMarkdownImageSource(src) {
15
+ if (typeof src !== "string") return null;
16
+ const raw = src.trim();
17
+ if (!raw || raw !== src) return null;
18
+ const data = raw.match(DATA_IMAGE_RE);
19
+ if (data) {
20
+ const mime = data[1].toLowerCase();
21
+ const base64 = data[2].replace(/\s+/g, "");
22
+ if (!SAFE_IMAGE_MIME_RE.test(mime)) return null;
23
+ if (!/^[A-Za-z0-9+/=]+$/.test(base64)) return null;
24
+ return {
25
+ src: raw,
26
+ mime,
27
+ base64
28
+ };
29
+ }
30
+ if (/^(?:blob|file):/i.test(raw)) {
31
+ if (raw.includes("\\") || /\s/u.test(raw) || hasEncodedControl(raw)) return null;
32
+ try {
33
+ const url = new URL(raw);
34
+ if (url.protocol === "blob:" || url.protocol === "file:") return { src: url.toString() };
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ const safe = sanitizeDomHref(raw, { allowRelative: true });
40
+ return safe ? { src: safe } : null;
41
+ }
42
+ const BASE64_TABLE = (() => {
43
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
44
+ const table = new Map();
45
+ for (let i = 0; i < chars.length; i++) table.set(chars[i], i);
46
+ return table;
47
+ })();
48
+ function decodeBase64Prefix(value, maxBytes) {
49
+ const bytes = [];
50
+ let buffer = 0;
51
+ let bits = 0;
52
+ for (const ch of value.replace(/\s+/g, "")) {
53
+ if (ch === "=") break;
54
+ const sextet = BASE64_TABLE.get(ch);
55
+ if (sextet == null) continue;
56
+ buffer = buffer << 6 | sextet;
57
+ bits += 6;
58
+ while (bits >= 8) {
59
+ bits -= 8;
60
+ bytes.push(buffer >> bits & 255);
61
+ if (bytes.length >= maxBytes) return Uint8Array.from(bytes);
62
+ }
63
+ }
64
+ return Uint8Array.from(bytes);
65
+ }
66
+ function u16be(bytes, offset) {
67
+ return (bytes[offset] ?? 0) << 8 | (bytes[offset + 1] ?? 0);
68
+ }
69
+ function u16le(bytes, offset) {
70
+ return (bytes[offset] ?? 0) | (bytes[offset + 1] ?? 0) << 8;
71
+ }
72
+ function u24le(bytes, offset) {
73
+ return (bytes[offset] ?? 0) | (bytes[offset + 1] ?? 0) << 8 | (bytes[offset + 2] ?? 0) << 16;
74
+ }
75
+ function u32be(bytes, offset) {
76
+ return (bytes[offset] ?? 0) * 16777216 + ((bytes[offset + 1] ?? 0) << 16 | (bytes[offset + 2] ?? 0) << 8 | (bytes[offset + 3] ?? 0));
77
+ }
78
+ function validDimensions(width, height) {
79
+ return Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0 ? {
80
+ width,
81
+ height
82
+ } : null;
83
+ }
84
+ function readMarkdownImageDimensions(base64) {
85
+ if (!base64) return null;
86
+ const bytes = decodeBase64Prefix(base64, 4096);
87
+ if (bytes.length < 10) return null;
88
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[12] === 73 && bytes[13] === 72 && bytes[14] === 68 && bytes[15] === 82) return validDimensions(u32be(bytes, 16), u32be(bytes, 20));
89
+ if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return validDimensions(u16le(bytes, 6), u16le(bytes, 8));
90
+ if (bytes[0] === 255 && bytes[1] === 216) for (let offset = 2; offset + 8 < bytes.length;) {
91
+ if (bytes[offset] !== 255) {
92
+ offset++;
93
+ continue;
94
+ }
95
+ const marker = bytes[offset + 1] ?? 0;
96
+ if (marker === 217 || marker === 218) break;
97
+ const length = u16be(bytes, offset + 2);
98
+ if (length < 2) break;
99
+ if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) return validDimensions(u16be(bytes, offset + 7), u16be(bytes, offset + 5));
100
+ offset += 2 + length;
101
+ }
102
+ if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
103
+ const format = String.fromCharCode(bytes[12] ?? 0, bytes[13] ?? 0, bytes[14] ?? 0, bytes[15] ?? 0);
104
+ if (format === "VP8X" && bytes.length >= 30) return validDimensions(u24le(bytes, 24) + 1, u24le(bytes, 27) + 1);
105
+ if (format === "VP8 " && bytes.length >= 30) return validDimensions(u16le(bytes, 26) & 16383, u16le(bytes, 28) & 16383);
106
+ if (format === "VP8L" && bytes.length >= 25 && bytes[20] === 47) {
107
+ const b0 = bytes[21] ?? 0;
108
+ const b1 = bytes[22] ?? 0;
109
+ const b2 = bytes[23] ?? 0;
110
+ const b3 = bytes[24] ?? 0;
111
+ return validDimensions(1 + ((b1 & 63) << 8 | b0), 1 + (b3 << 6 | b2 >> 2));
112
+ }
113
+ }
114
+ return null;
115
+ }
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
+ //#endregion
7
294
  //#region src/vue/markdown/parser.ts
8
295
  const markdownInstanceCache = new Map();
9
296
  const MAX_MARKDOWN_INSTANCE_CACHE = 32;
@@ -13,6 +300,9 @@ function sanitizeMarkdownLink(url) {
13
300
  function isSafeMarkdownLink(url) {
14
301
  return sanitizeMarkdownLink(url) != null;
15
302
  }
303
+ function isSafeMarkdownDestination(url) {
304
+ return isSafeMarkdownLink(url) || sanitizeMarkdownImageSource(url) != null;
305
+ }
16
306
  function normalizeCustomHtmlTags(config) {
17
307
  return Array.from(new Set(config?.customHtmlTags?.filter(Boolean) ?? [])).sort();
18
308
  }
@@ -44,7 +334,7 @@ function createTuiMarkdownParser(config) {
44
334
  final,
45
335
  customHtmlTags,
46
336
  requireClosingStrong: final || !config?.streaming,
47
- validateLink: isSafeMarkdownLink
337
+ validateLink: isSafeMarkdownDestination
48
338
  });
49
339
  }
50
340
  return { parse };
@@ -57,6 +347,7 @@ const HARD_BREAK_SEGMENT = Object.freeze({
57
347
  text: "",
58
348
  hardBreak: true
59
349
  });
350
+ const TERMINAL_CELL_WIDTH_TO_HEIGHT_RATIO = .5;
60
351
  function mergeStyle$1(base, overlay) {
61
352
  if (!base) return overlay;
62
353
  if (!overlay) return base;
@@ -79,6 +370,32 @@ function stringProp(node, key) {
79
370
  const value = node[key];
80
371
  return typeof value === "string" ? value : "";
81
372
  }
373
+ function imageSourceFromRaw(raw) {
374
+ if (!raw.startsWith("![")) return "";
375
+ const marker = raw.indexOf("](");
376
+ if (marker < 0 || !raw.endsWith(")")) return "";
377
+ const destination = raw.slice(marker + 2, -1).trim();
378
+ if (destination.startsWith("<")) {
379
+ const end = destination.indexOf(">");
380
+ return end > 1 ? destination.slice(1, end) : "";
381
+ }
382
+ return destination.split(/\s+/u)[0] ?? "";
383
+ }
384
+ function imageFallbackText(raw, rawSource, sourceSrc) {
385
+ if (raw) return raw;
386
+ const src = rawSource || sourceSrc;
387
+ return src ? `![](${src})` : "image";
388
+ }
389
+ function normalizeImageResolverResult(result) {
390
+ if (typeof result === "string") return { base64: result.replace(/\s+/g, "") };
391
+ if (!result) return {};
392
+ return {
393
+ base64: result.base64.replace(/\s+/g, ""),
394
+ ...result.originalBase64 ? { originalBase64: result.originalBase64.replace(/\s+/g, "") } : {},
395
+ ...result.mime ? { mime: result.mime } : {},
396
+ ...result.originalMime ? { originalMime: result.originalMime } : {}
397
+ };
398
+ }
82
399
  function booleanProp(node, key) {
83
400
  return node[key] === true;
84
401
  }
@@ -90,17 +407,51 @@ function nodeItems(node) {
90
407
  const value = node.items;
91
408
  return Array.isArray(value) ? value : [];
92
409
  }
93
- function pushTextSegments(out, text, style) {
410
+ function clampImageCells(value, min, max) {
411
+ return Math.max(min, Math.min(max, Math.round(value)));
412
+ }
413
+ function resolveImageDisplaySize(alt, size, naturalWidth, naturalHeight) {
414
+ if (size?.minWidth == null && size?.maxWidth == null && size?.minHeight == null && size?.maxHeight == null) return {};
415
+ const minWidth = Math.max(1, Math.floor(size?.minWidth ?? 1));
416
+ const maxWidth = Math.max(minWidth, Math.floor(size?.maxWidth ?? Number.MAX_SAFE_INTEGER));
417
+ const minHeight = Math.max(1, Math.floor(size?.minHeight ?? 1));
418
+ const maxHeight = Math.max(minHeight, Math.floor(size?.maxHeight ?? Number.MAX_SAFE_INTEGER));
419
+ if (size?.preserveAspectRatio !== false && naturalWidth != null && naturalHeight != null && naturalWidth > 0 && naturalHeight > 0) {
420
+ const cellRatio = naturalWidth / naturalHeight / TERMINAL_CELL_WIDTH_TO_HEIGHT_RATIO;
421
+ let width = minWidth;
422
+ let height = Math.max(minHeight, Math.ceil(width / cellRatio));
423
+ width = Math.max(width, Math.ceil(height * cellRatio));
424
+ if (width > maxWidth) {
425
+ width = maxWidth;
426
+ height = Math.max(minHeight, Math.ceil(width / cellRatio));
427
+ }
428
+ if (height > maxHeight) {
429
+ height = maxHeight;
430
+ width = Math.max(minWidth, Math.floor(height * cellRatio));
431
+ }
432
+ return {
433
+ displayWidth: clampImageCells(width, minWidth, maxWidth),
434
+ displayHeight: clampImageCells(height, minHeight, maxHeight)
435
+ };
436
+ }
437
+ return {
438
+ displayWidth: size?.maxWidth != null ? Math.min(maxWidth, Math.max(minWidth, textCellWidth(alt))) : Math.max(minWidth, textCellWidth(alt)),
439
+ displayHeight: size?.maxHeight != null ? Math.min(maxHeight, Math.max(minHeight, 1)) : minHeight
440
+ };
441
+ }
442
+ function pushTextSegments(out, text, style, graphic, mathAction) {
94
443
  if (!text) return;
95
444
  const normalized = sanitizeTextBlock(text);
96
445
  if (!normalized) return;
97
446
  const parts = normalized.split("\n");
98
447
  for (let i = 0; i < parts.length; i++) {
99
448
  const part = sanitizeInlineText(parts[i] ?? "");
100
- if (part) out.push(style ? {
449
+ if (part) out.push({
101
450
  text: part,
102
- style
103
- } : { text: part });
451
+ ...style ? { style } : {},
452
+ ...i === 0 && graphic ? { graphic } : {},
453
+ ...mathAction ? { mathAction } : {}
454
+ });
104
455
  if (i < parts.length - 1) out.push(HARD_BREAK_SEGMENT);
105
456
  }
106
457
  }
@@ -108,7 +459,12 @@ function sanitizeCodeBlockText(text, tabSize = 4) {
108
459
  const normalized = String(text ?? "").replace(/\r\n?/g, "\n").replace(/\t/g, spaces(tabSize)).replace(/\u00a0/g, " ");
109
460
  return sanitizeTextBlock(normalized);
110
461
  }
111
- function inlineNodeSegments(nodes, theme, inheritedStyle) {
462
+ function codeBlockLanguage(node) {
463
+ const raw = stringProp(node, "lang") || stringProp(node, "language") || stringProp(node, "info") || stringProp(node, "meta");
464
+ const language = raw.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
465
+ return language || void 0;
466
+ }
467
+ function inlineNodeSegments(nodes, theme, inheritedStyle, options = {}) {
112
468
  const out = [];
113
469
  for (const node of nodes) switch (node.type) {
114
470
  case "text":
@@ -119,22 +475,22 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
119
475
  case "heading":
120
476
  case "list_item":
121
477
  case "blockquote":
122
- out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
478
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
123
479
  break;
124
480
  case "strong":
125
- out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong)));
481
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong), options));
126
482
  break;
127
483
  case "emphasis":
128
- out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis)));
484
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis), options));
129
485
  break;
130
486
  case "strikethrough":
131
- out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough)));
487
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
132
488
  break;
133
489
  case "highlight":
134
490
  case "insert":
135
491
  case "subscript":
136
492
  case "superscript":
137
- out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
493
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
138
494
  break;
139
495
  case "inline_code":
140
496
  pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
@@ -150,13 +506,55 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
150
506
  href: safeHref
151
507
  }) : inheritedStyle;
152
508
  const children = nodeChildren(node);
153
- if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle));
509
+ if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
154
510
  else pushTextSegments(out, stringProp(node, "text"), linkStyle);
155
511
  break;
156
512
  }
157
- case "image":
158
- pushTextSegments(out, stringProp(node, "alt") || stringProp(node, "src"), mergeStyle$1(inheritedStyle, theme.link));
513
+ case "image": {
514
+ const raw = stringProp(node, "raw");
515
+ const rawSource = stringProp(node, "src") || imageSourceFromRaw(raw);
516
+ const source = sanitizeMarkdownImageSource(rawSource);
517
+ const alt = stringProp(node, "alt") || imageFallbackText(raw, rawSource, source?.src);
518
+ if (source) {
519
+ const size = options.imageSize;
520
+ const imageLinkStyle = mergeStyle$1(inheritedStyle, {
521
+ ...theme.link,
522
+ href: source.src
523
+ });
524
+ const sourceDimensions = readMarkdownImageDimensions(source.base64);
525
+ const graphicBase = {
526
+ kind: "image",
527
+ src: source.src,
528
+ alt,
529
+ ...source.mime ? { mime: source.mime } : {},
530
+ ...source.base64 ? { base64: source.base64 } : {},
531
+ ...sourceDimensions ? {
532
+ naturalWidth: sourceDimensions.width,
533
+ naturalHeight: sourceDimensions.height
534
+ } : {},
535
+ ...resolveImageDisplaySize(alt, size, sourceDimensions?.width, sourceDimensions?.height)
536
+ };
537
+ const resolved = normalizeImageResolverResult(options.imageResolver?.(graphicBase));
538
+ const base64 = resolved.base64 || source.base64;
539
+ if (base64) {
540
+ const dimensions = readMarkdownImageDimensions(base64) ?? sourceDimensions;
541
+ const graphic = {
542
+ ...graphicBase,
543
+ base64,
544
+ ...resolved.originalBase64 ? { originalBase64: resolved.originalBase64 } : {},
545
+ ...resolved.mime ? { mime: resolved.mime } : {},
546
+ ...resolved.originalMime ? { originalMime: resolved.originalMime } : {},
547
+ ...dimensions ? {
548
+ naturalWidth: dimensions.width,
549
+ naturalHeight: dimensions.height
550
+ } : {},
551
+ ...resolveImageDisplaySize(alt, size, dimensions?.width, dimensions?.height)
552
+ };
553
+ pushTextSegments(out, alt, imageLinkStyle, graphic);
554
+ } else pushTextSegments(out, alt, imageLinkStyle);
555
+ } else pushTextSegments(out, alt, mergeStyle$1(inheritedStyle, theme.link));
159
556
  break;
557
+ }
160
558
  case "checkbox":
161
559
  case "checkbox_input":
162
560
  pushTextSegments(out, booleanProp(node, "checked") ? "[x]" : "[ ]", inheritedStyle);
@@ -165,7 +563,16 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
165
563
  pushTextSegments(out, stringProp(node, "markup") || stringProp(node, "name"), inheritedStyle);
166
564
  break;
167
565
  case "math_inline":
168
- pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.inlineCode));
566
+ {
567
+ const source = stringProp(node, "content");
568
+ const raw = stringProp(node, "raw") || `$${source}$`;
569
+ const rendered = renderMarkdownInlineMathSegment(source);
570
+ pushTextSegments(out, rendered.supported ? rendered.text : raw, mergeStyle$1(inheritedStyle, theme.inlineCode), void 0, {
571
+ source,
572
+ raw,
573
+ rendered: rendered.supported
574
+ });
575
+ }
169
576
  break;
170
577
  case "reference":
171
578
  case "footnote_reference":
@@ -180,7 +587,7 @@ function inlineNodeSegments(nodes, theme, inheritedStyle) {
180
587
  break;
181
588
  default:
182
589
  if (nodeChildren(node).length) {
183
- out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle));
590
+ out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
184
591
  break;
185
592
  }
186
593
  pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
@@ -198,16 +605,23 @@ function inlineBlock(key, segments, context) {
198
605
  };
199
606
  }
200
607
  function blockFromParagraph(node, key, context, theme) {
201
- return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme), context);
608
+ return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, void 0, {
609
+ imageResolver: context.imageResolver,
610
+ imageSize: context.imageSize
611
+ }), context);
202
612
  }
203
613
  function blockFromHeading(node, key, context, theme) {
204
614
  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);
615
+ return inlineBlock(key, inlineNodeSegments(nodeChildren(node), theme, theme.heading[level - 1], {
616
+ imageResolver: context.imageResolver,
617
+ imageSize: context.imageSize
618
+ }), context);
206
619
  }
207
620
  function blockFromCodeBlock(node, key, context, theme) {
208
621
  return {
209
622
  type: "code_block",
210
623
  key,
624
+ language: codeBlockLanguage(node),
211
625
  lines: sanitizeCodeBlockText(stringProp(node, "code")).split("\n"),
212
626
  style: theme.codeBlock,
213
627
  prefixSegments: context.prefixSegments,
@@ -219,7 +633,7 @@ function blockFromHtmlBlock(node, key, context, theme) {
219
633
  type: "html_block",
220
634
  raw: stringProp(node, "raw"),
221
635
  content: stringProp(node, "content")
222
- }], theme), context);
636
+ }], theme, void 0, { imageResolver: context.imageResolver }), context);
223
637
  }
224
638
  function tableRows$1(node, key) {
225
639
  const value = node[key];
@@ -237,11 +651,17 @@ function tableCellAlign(cell) {
237
651
  function blockFromTable(node, key, context, theme) {
238
652
  const headerRow = tableRows$1(node, "header")[0];
239
653
  const header = tableCells(headerRow ?? { type: "table_row" }).map((cell) => ({
240
- segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong),
654
+ segments: inlineNodeSegments(nodeChildren(cell), theme, theme.strong, {
655
+ imageResolver: context.imageResolver,
656
+ imageSize: context.imageSize
657
+ }),
241
658
  align: tableCellAlign(cell)
242
659
  }));
243
660
  const rows = tableRows$1(node, "rows").map((row) => tableCells(row).map((cell) => ({
244
- segments: inlineNodeSegments(nodeChildren(cell), theme),
661
+ segments: inlineNodeSegments(nodeChildren(cell), theme, void 0, {
662
+ imageResolver: context.imageResolver,
663
+ imageSize: context.imageSize
664
+ }),
245
665
  align: tableCellAlign(cell)
246
666
  })));
247
667
  return {
@@ -339,7 +759,9 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
339
759
  };
340
760
  const quoteContext = {
341
761
  prefixSegments: [...context.prefixSegments, quoteSegment],
342
- continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment]
762
+ continuationPrefixSegments: [...context.continuationPrefixSegments, quoteSegment],
763
+ imageResolver: context.imageResolver,
764
+ imageSize: context.imageSize
343
765
  };
344
766
  return childSequenceToBlocks(nodeChildren(node), quoteContext, theme, keyPrefix);
345
767
  }
@@ -347,19 +769,24 @@ function nodeToBlocks(node, context, theme, keyPrefix) {
347
769
  if ("items" in node && Array.isArray(node.items)) return listBlocks(node, context, theme, keyPrefix);
348
770
  break;
349
771
  case "table": return [blockFromTable(node, keyPrefix, context, theme)];
350
- case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme), context)];
772
+ case "inline": return [inlineBlock(keyPrefix, inlineNodeSegments(nodeChildren(node), theme, void 0, {
773
+ imageResolver: context.imageResolver,
774
+ imageSize: context.imageSize
775
+ }), context)];
351
776
  case "html_block":
352
777
  if ("content" in node) return [blockFromHtmlBlock(node, keyPrefix, context, theme)];
353
778
  break;
354
- case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
779
+ case "text": return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
355
780
  }
356
781
  if (nodeChildren(node).length) return childSequenceToBlocks(nodeChildren(node), context, theme, keyPrefix);
357
- return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme), context)];
782
+ return [inlineBlock(keyPrefix, inlineNodeSegments([node], theme, void 0, { imageResolver: context.imageResolver }), context)];
358
783
  }
359
- function markdownAstToBlocks(nodes, theme) {
784
+ function markdownAstToBlocks(nodes, theme, options = {}) {
360
785
  const blocks = trimBlockArray(childSequenceToBlocks(nodes, {
361
786
  prefixSegments: EMPTY_PREFIX,
362
- continuationPrefixSegments: EMPTY_PREFIX
787
+ continuationPrefixSegments: EMPTY_PREFIX,
788
+ imageResolver: options.imageResolver,
789
+ imageSize: options.imageSize
363
790
  }, theme, "md"));
364
791
  return blocks.length ? blocks : [{
365
792
  type: "blank",
@@ -376,11 +803,21 @@ function toVisualSegment(segment) {
376
803
  return {
377
804
  text: segment.text,
378
805
  style: segment.style,
379
- cells
806
+ cells,
807
+ ...segment.graphic ? { graphic: segment.graphic } : {},
808
+ ...segment.mathAction ? { mathAction: segment.mathAction } : {}
380
809
  };
381
810
  }
382
811
  function segmentsPlainText(segments) {
383
- return segments.map((segment) => segment.text).join("");
812
+ return segments.map((segment) => segment.fallbackText ?? segment.text).join("");
813
+ }
814
+ function segmentsVisualHeight(segments) {
815
+ let height = 1;
816
+ for (const segment of segments) {
817
+ if (!segment.graphic) continue;
818
+ height = Math.max(height, Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1)));
819
+ }
820
+ return height;
384
821
  }
385
822
  function segmentsCellWidth(segments) {
386
823
  let cells = 0;
@@ -410,10 +847,15 @@ function clipInlineSegmentsToWidth(segments, width) {
410
847
  const piece = sliceByCellsRange(segment.text, 0, remaining);
411
848
  const cells = textCellWidth(piece);
412
849
  if (!piece || cells <= 0) continue;
413
- out.push(segment.style ? {
850
+ const clipped = {
414
851
  text: piece,
415
- style: segment.style
416
- } : { text: piece });
852
+ ...segment.style ? { style: segment.style } : {},
853
+ ...segment.mathAction ? { mathAction: segment.mathAction } : {}
854
+ };
855
+ out.push(piece === segment.text && segment.graphic ? {
856
+ ...clipped,
857
+ graphic: segment.graphic
858
+ } : clipped);
417
859
  remaining -= cells;
418
860
  }
419
861
  return out;
@@ -435,7 +877,9 @@ function clipVisualSegmentsToWidth(segments, width) {
435
877
  out.push({
436
878
  text,
437
879
  style: segment.style,
438
- cells
880
+ cells,
881
+ ...text === segment.text && segment.graphic ? { graphic: segment.graphic } : {},
882
+ ...segment.mathAction ? { mathAction: segment.mathAction } : {}
439
883
  });
440
884
  remaining -= cells;
441
885
  }
@@ -477,6 +921,25 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
477
921
  const prefixLengthForRow = () => row.useContinuation ? continuationPrefix.length : firstPrefix.length;
478
922
  const rowHasBody = () => row.segments.length > prefixLengthForRow();
479
923
  for (const segment of source) {
924
+ if (segment.graphic) {
925
+ const displayCells = Math.max(1, Math.floor(segment.graphic.displayWidth ?? 1));
926
+ if (row.remaining <= 0 && !rowHasBody()) {
927
+ if (!row.useContinuation) {
928
+ rows.push(row.segments);
929
+ row = openDegradedRow();
930
+ }
931
+ }
932
+ const cells = Math.min(displayCells, Math.max(1, row.remaining || width));
933
+ row.segments.push({
934
+ text: spaces(cells),
935
+ style: segment.style,
936
+ cells,
937
+ graphic: segment.graphic,
938
+ fallbackText: segment.text
939
+ });
940
+ row.remaining -= cells;
941
+ continue;
942
+ }
480
943
  let aborted = false;
481
944
  forEachTextCellSegment(segment.text, (piece) => {
482
945
  while (true) {
@@ -504,7 +967,8 @@ function wrapLineSegments(source, prefixSegments, continuationPrefixSegments, wi
504
967
  row.segments.push({
505
968
  text: piece.text,
506
969
  style: segment.style,
507
- cells: piece.cells
970
+ cells: piece.cells,
971
+ ...segment.mathAction ? { mathAction: segment.mathAction } : {}
508
972
  });
509
973
  row.remaining -= piece.cells;
510
974
  return void 0;
@@ -532,6 +996,17 @@ function inlineBlockRows(block, width) {
532
996
  segments: visualSegments
533
997
  });
534
998
  rowInBlock++;
999
+ const height = segmentsVisualHeight(visualSegments);
1000
+ for (let extra = 1; extra < height; extra++) {
1001
+ rows.push({
1002
+ key: `${block.key}:${rowInBlock}`,
1003
+ blockKey: block.key,
1004
+ rowInBlock,
1005
+ plainText: "",
1006
+ segments: []
1007
+ });
1008
+ rowInBlock++;
1009
+ }
535
1010
  }
536
1011
  }
537
1012
  return rows.length ? rows : [{
@@ -609,23 +1084,26 @@ function tableColumnWidths(block, columns, width) {
609
1084
  }
610
1085
  return widths;
611
1086
  }
612
- function appendVisualSegment(segments, text, style) {
1087
+ function appendVisualSegment(segments, text, style, graphic, mathAction) {
613
1088
  if (!text) return;
614
1089
  const cells = textCellWidth(text);
615
1090
  if (cells <= 0) return;
616
1091
  const prev = segments[segments.length - 1];
617
- if (prev && prev.style === style) {
1092
+ if (prev && prev.style === style && prev.mathAction === mathAction && !prev.graphic && !graphic) {
618
1093
  segments[segments.length - 1] = {
619
1094
  text: `${prev.text}${text}`,
620
1095
  style,
621
- cells: prev.cells + cells
1096
+ cells: prev.cells + cells,
1097
+ ...mathAction ? { mathAction } : {}
622
1098
  };
623
1099
  return;
624
1100
  }
625
1101
  segments.push({
626
1102
  text,
627
1103
  style,
628
- cells
1104
+ cells,
1105
+ ...graphic ? { graphic } : {},
1106
+ ...mathAction ? { mathAction } : {}
629
1107
  });
630
1108
  }
631
1109
  function appendInlineVisualSegments(out, segments) {
@@ -634,7 +1112,7 @@ function appendInlineVisualSegments(out, segments) {
634
1112
  appendVisualSegment(out, " ");
635
1113
  continue;
636
1114
  }
637
- appendVisualSegment(out, segment.text, segment.style);
1115
+ appendVisualSegment(out, segment.text, segment.style, segment.graphic, segment.mathAction);
638
1116
  }
639
1117
  }
640
1118
  function tableBorderRow(block, widths, width, rowInBlock, left, join, right) {
@@ -723,10 +1201,26 @@ function styleSignature(style) {
723
1201
  ].join("");
724
1202
  }
725
1203
  function inlineSegmentSignature(segment) {
1204
+ const graphic = segment.graphic ? [
1205
+ segment.graphic.kind,
1206
+ segment.graphic.src,
1207
+ segment.graphic.alt ?? "",
1208
+ segment.graphic.mime ?? "",
1209
+ segment.graphic.base64 ?? "",
1210
+ segment.graphic.displayWidth ?? "",
1211
+ segment.graphic.displayHeight ?? ""
1212
+ ].join("") : "";
1213
+ const mathAction = segment.mathAction ? [
1214
+ segment.mathAction.source,
1215
+ segment.mathAction.raw,
1216
+ segment.mathAction.rendered ? "1" : "0"
1217
+ ].join("") : "";
726
1218
  return [
727
1219
  segment.text,
728
1220
  segment.hardBreak ? "1" : "0",
729
- styleSignature(segment.style)
1221
+ styleSignature(segment.style),
1222
+ graphic,
1223
+ mathAction
730
1224
  ].join("");
731
1225
  }
732
1226
  function inlineSegmentsSignature(segments) {
@@ -742,6 +1236,7 @@ function blockLayoutSignature(block) {
742
1236
  ].join("");
743
1237
  case "code_block": return [
744
1238
  block.type,
1239
+ block.language ?? "",
745
1240
  block.lines.join(""),
746
1241
  styleSignature(block.style),
747
1242
  inlineSegmentsSignature(block.prefixSegments),
@@ -923,7 +1418,10 @@ function buildMarkdownBlocks(content, parser, options) {
923
1418
  const theme = resolveTuiMarkdownTheme(options?.theme);
924
1419
  try {
925
1420
  const nodes = parser.parse(content, options?.final ?? true);
926
- const blocks = markdownAstToBlocks(nodes, theme);
1421
+ const blocks = markdownAstToBlocks(nodes, theme, {
1422
+ imageResolver: options?.imageResolver,
1423
+ imageSize: options?.imageSize
1424
+ });
927
1425
  return {
928
1426
  nodes,
929
1427
  blocks
@@ -966,9 +1464,177 @@ function plainTextFallbackBlocks(content) {
966
1464
  }];
967
1465
  }
968
1466
 
1467
+ //#endregion
1468
+ //#region src/vue/markdown/image-actions.ts
1469
+ function imageDisplayWidth(segment) {
1470
+ return Math.max(1, Math.floor(segment.graphic?.displayWidth ?? segment.cells));
1471
+ }
1472
+ function imageDisplayHeight(segment) {
1473
+ return Math.max(1, Math.floor(segment.graphic?.displayHeight ?? 1));
1474
+ }
1475
+ function findMarkdownImageActionAt(rows, point, options) {
1476
+ const r = options.screenRect;
1477
+ if (r.w <= 0 || r.h <= 0) return null;
1478
+ if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
1479
+ const clipStart = Math.max(0, Math.floor(options.clipStart));
1480
+ const clipEnd = clipStart + r.w;
1481
+ const firstRow = Math.max(0, Math.floor(options.rowOffset));
1482
+ const lastRow = Math.min(rows.length, firstRow + r.h);
1483
+ for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
1484
+ const row = rows[rowIndex];
1485
+ if (!row) continue;
1486
+ let logicalX = 0;
1487
+ for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
1488
+ const segment = row.segments[segmentIndex];
1489
+ const segmentStart = logicalX;
1490
+ const segmentEnd = logicalX + segment.cells;
1491
+ logicalX = segmentEnd;
1492
+ const image = segment.graphic;
1493
+ if (!image) continue;
1494
+ if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1495
+ const imageX = r.x + segmentStart - clipStart;
1496
+ const imageY = r.y + rowIndex - firstRow;
1497
+ const imageRect = {
1498
+ x: imageX,
1499
+ y: imageY,
1500
+ w: imageDisplayWidth(segment),
1501
+ h: imageDisplayHeight(segment)
1502
+ };
1503
+ const visibleLeft = Math.max(r.x, imageRect.x);
1504
+ const visibleTop = Math.max(r.y, imageRect.y);
1505
+ const visibleRight = Math.min(r.x + r.w, imageRect.x + imageRect.w);
1506
+ const visibleBottom = Math.min(r.y + r.h, imageRect.y + imageRect.h);
1507
+ if (point.cellX < visibleLeft || point.cellY < visibleTop || point.cellX >= visibleRight || point.cellY >= visibleBottom) continue;
1508
+ return {
1509
+ image,
1510
+ rect: imageRect,
1511
+ cellX: point.cellX,
1512
+ cellY: point.cellY,
1513
+ rowIndex,
1514
+ segmentIndex
1515
+ };
1516
+ }
1517
+ }
1518
+ return null;
1519
+ }
1520
+
1521
+ //#endregion
1522
+ //#region src/vue/markdown/link-actions.ts
1523
+ function findMarkdownLinkActionAt(rows, point, options) {
1524
+ const r = options.screenRect;
1525
+ if (r.w <= 0 || r.h <= 0) return null;
1526
+ if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
1527
+ const clipStart = Math.max(0, Math.floor(options.clipStart));
1528
+ const clipEnd = clipStart + r.w;
1529
+ const firstRow = Math.max(0, Math.floor(options.rowOffset));
1530
+ const lastRow = Math.min(rows.length, firstRow + r.h);
1531
+ for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
1532
+ const row = rows[rowIndex];
1533
+ if (!row) continue;
1534
+ let logicalX = 0;
1535
+ for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
1536
+ const segment = row.segments[segmentIndex];
1537
+ const segmentStart = logicalX;
1538
+ const segmentEnd = logicalX + segment.cells;
1539
+ logicalX = segmentEnd;
1540
+ const href = segment.style?.href;
1541
+ if (!href || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1542
+ const visibleStart = Math.max(segmentStart, clipStart);
1543
+ const visibleEnd = Math.min(segmentEnd, clipEnd);
1544
+ const rect = {
1545
+ x: r.x + visibleStart - clipStart,
1546
+ y: r.y + rowIndex - firstRow,
1547
+ w: visibleEnd - visibleStart,
1548
+ h: 1
1549
+ };
1550
+ if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
1551
+ let groupStart = segmentStart;
1552
+ let groupEnd = segmentEnd;
1553
+ const textParts = [segment.fallbackText ?? segment.text];
1554
+ for (let index = segmentIndex - 1; index >= 0; index--) {
1555
+ const previous = row.segments[index];
1556
+ if (previous.style?.href !== href) break;
1557
+ groupStart -= previous.cells;
1558
+ textParts.unshift(previous.fallbackText ?? previous.text);
1559
+ }
1560
+ for (let index = segmentIndex + 1; index < row.segments.length; index++) {
1561
+ const next = row.segments[index];
1562
+ if (next.style?.href !== href) break;
1563
+ groupEnd += next.cells;
1564
+ textParts.push(next.fallbackText ?? next.text);
1565
+ }
1566
+ const visibleGroupStart = Math.max(groupStart, clipStart);
1567
+ const visibleGroupEnd = Math.min(groupEnd, clipEnd);
1568
+ return {
1569
+ href,
1570
+ text: textParts.join(""),
1571
+ rect: {
1572
+ x: r.x + visibleGroupStart - clipStart,
1573
+ y: rect.y,
1574
+ w: visibleGroupEnd - visibleGroupStart,
1575
+ h: 1
1576
+ },
1577
+ cellX: point.cellX,
1578
+ cellY: point.cellY,
1579
+ rowIndex,
1580
+ segmentIndex
1581
+ };
1582
+ }
1583
+ }
1584
+ return null;
1585
+ }
1586
+
1587
+ //#endregion
1588
+ //#region src/vue/markdown/math-actions.ts
1589
+ function findMarkdownMathActionAt(rows, point, options) {
1590
+ const r = options.screenRect;
1591
+ if (r.w <= 0 || r.h <= 0) return null;
1592
+ if (point.cellX < r.x || point.cellY < r.y || point.cellX >= r.x + r.w || point.cellY >= r.y + r.h) return null;
1593
+ const clipStart = Math.max(0, Math.floor(options.clipStart));
1594
+ const clipEnd = clipStart + r.w;
1595
+ const firstRow = Math.max(0, Math.floor(options.rowOffset));
1596
+ const lastRow = Math.min(rows.length, firstRow + r.h);
1597
+ for (let rowIndex = firstRow; rowIndex < lastRow; rowIndex++) {
1598
+ const row = rows[rowIndex];
1599
+ if (!row) continue;
1600
+ let logicalX = 0;
1601
+ for (let segmentIndex = 0; segmentIndex < row.segments.length; segmentIndex++) {
1602
+ const segment = row.segments[segmentIndex];
1603
+ const segmentStart = logicalX;
1604
+ const segmentEnd = logicalX + segment.cells;
1605
+ logicalX = segmentEnd;
1606
+ const math = segment.mathAction;
1607
+ if (!math || segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1608
+ const visibleStart = Math.max(segmentStart, clipStart);
1609
+ const visibleEnd = Math.min(segmentEnd, clipEnd);
1610
+ const rect = {
1611
+ x: r.x + visibleStart - clipStart,
1612
+ y: r.y + rowIndex - firstRow,
1613
+ w: visibleEnd - visibleStart,
1614
+ h: 1
1615
+ };
1616
+ if (point.cellX < rect.x || point.cellY < rect.y || point.cellX >= rect.x + rect.w || point.cellY >= rect.y + rect.h) continue;
1617
+ return {
1618
+ math,
1619
+ rect,
1620
+ cellX: point.cellX,
1621
+ cellY: point.cellY,
1622
+ rowIndex,
1623
+ segmentIndex
1624
+ };
1625
+ }
1626
+ }
1627
+ return null;
1628
+ }
1629
+
969
1630
  //#endregion
970
1631
  //#region src/vue/markdown/render.ts
971
1632
  const mergedStyleCache = new WeakMap();
1633
+ const markdownImageDebugLog = createDebugLogger(isDebugEnabled());
1634
+ const markdownImageDebug = (...parts) => {
1635
+ if (!isDebugEnabled()) return;
1636
+ markdownImageDebugLog.render(`markdown image ${parts.join(" ")}`);
1637
+ };
972
1638
  function mergeStyle(base, overlay) {
973
1639
  if (!overlay) return base;
974
1640
  let bucket = mergedStyleCache.get(base);
@@ -985,20 +1651,153 @@ function mergeStyle(base, overlay) {
985
1651
  bucket.set(overlay, merged);
986
1652
  return merged;
987
1653
  }
1654
+ const activeMarkdownImageGraphics = new WeakMap();
1655
+ function markdownImageGraphicId(segment, identity) {
1656
+ return `md-image:${stableTerminalGraphicNumericId(`${identity}:${segment.src}:${segment.displayWidth ?? 0}:${segment.displayHeight ?? 0}:${segment.base64 ?? ""}`)}`;
1657
+ }
1658
+ function rememberMarkdownImageGraphic(terminal, id, rect) {
1659
+ let active = activeMarkdownImageGraphics.get(terminal);
1660
+ if (!active) {
1661
+ active = new Map();
1662
+ activeMarkdownImageGraphics.set(terminal, active);
1663
+ }
1664
+ active.set(id, rect);
1665
+ }
1666
+ function clearTrackedMarkdownImageGraphic(terminal, id) {
1667
+ activeMarkdownImageGraphics.get(terminal)?.delete(id);
1668
+ try {
1669
+ getTerminalGraphicsOutput(terminal)?.clear?.(id);
1670
+ } catch {}
1671
+ }
1672
+ function clearStaleMarkdownImageGraphicsForRow(terminal, y, keepIds, keepVisibleIds) {
1673
+ const active = activeMarkdownImageGraphics.get(terminal);
1674
+ if (!active?.size) return;
1675
+ for (const [id, rect] of active) {
1676
+ const intersectsRow = keepVisibleIds ? y >= rect.y && y < rect.y + rect.h : rect.y === y;
1677
+ if (!intersectsRow || keepIds.has(id) || keepVisibleIds?.has(id)) continue;
1678
+ clearTrackedMarkdownImageGraphic(terminal, id);
1679
+ }
1680
+ }
1681
+ function canAttemptMarkdownImageGraphic(terminal, segment) {
1682
+ if (segment.kind !== "image" || !segment.base64) return false;
1683
+ const output = getTerminalGraphicsOutput(terminal);
1684
+ const protocol = output?.capabilities.preferredProtocol;
1685
+ return Boolean(output?.capabilities.supported && isTerminalGraphicsProtocol(protocol));
1686
+ }
1687
+ function clearMarkdownImageGraphics(terminal, rect) {
1688
+ const active = activeMarkdownImageGraphics.get(terminal);
1689
+ if (!active?.size) return;
1690
+ for (const [id, item] of active) {
1691
+ if (rect && (item.x >= rect.x + rect.w || item.x + item.w <= rect.x || item.y >= rect.y + rect.h || item.y + item.h <= rect.y)) continue;
1692
+ clearTrackedMarkdownImageGraphic(terminal, id);
1693
+ }
1694
+ }
1695
+ function queueMarkdownImageGraphic(terminal, segment, identity, rect) {
1696
+ if (segment.kind !== "image" || !segment.base64) return "unavailable";
1697
+ const output = getTerminalGraphicsOutput(terminal);
1698
+ const protocol = output?.capabilities.preferredProtocol;
1699
+ if (!output?.capabilities.supported || !isTerminalGraphicsProtocol(protocol)) return "unavailable";
1700
+ const width = Math.max(1, Math.floor(segment.displayWidth ?? rect.w));
1701
+ const height = Math.max(1, Math.floor(segment.displayHeight ?? 1));
1702
+ const id = markdownImageGraphicId(segment, identity);
1703
+ const imageId = stableTerminalGraphicNumericId(`image:${id}`);
1704
+ const placementId = stableTerminalGraphicNumericId(`placement:${id}`);
1705
+ const sequence = protocol === "kitty" ? createKittyGraphicsSequence(segment.base64, {
1706
+ imageId,
1707
+ placementId,
1708
+ columns: width,
1709
+ rows: height,
1710
+ zIndex: -1
1711
+ }) : protocol === "iterm2" ? createIterm2InlineImageSequence(segment.base64, {
1712
+ width,
1713
+ height,
1714
+ preserveAspectRatio: true,
1715
+ doNotMoveCursor: true
1716
+ }) : "";
1717
+ if (!sequence) return "unavailable";
1718
+ const clearSequence = protocol === "kitty" ? createKittyDeleteGraphicsSequence({
1719
+ imageId,
1720
+ placementId
1721
+ }) : void 0;
1722
+ const resizeSequence = protocol === "kitty" ? createKittyPlacementSequence({
1723
+ imageId,
1724
+ placementId,
1725
+ columns: width,
1726
+ rows: height
1727
+ }) : void 0;
1728
+ const accepted = output.queue({
1729
+ id,
1730
+ x: rect.x,
1731
+ y: rect.y,
1732
+ w: width,
1733
+ h: height,
1734
+ protocol,
1735
+ sequence,
1736
+ resizeSequence,
1737
+ clearSequence,
1738
+ fallbackText: segment.alt ?? "image"
1739
+ });
1740
+ 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
+ if (accepted) rememberMarkdownImageGraphic(terminal, id, {
1742
+ x: rect.x,
1743
+ y: rect.y,
1744
+ w: width,
1745
+ h: height
1746
+ });
1747
+ return accepted ? "queued" : "rejected";
1748
+ }
1749
+ function collectVisibleMarkdownImageGraphicIds(rows, options) {
1750
+ const keepIds = new Set();
1751
+ if (options.w <= 0 || options.h <= 0) return keepIds;
1752
+ const clipStart = Math.max(0, Math.floor(options.clipStart));
1753
+ const clipEnd = clipStart + options.w;
1754
+ const firstRow = Math.min(rows.length, Math.max(0, Math.floor(options.rowOffset)));
1755
+ const lastRow = Math.min(rows.length, firstRow + Math.max(0, Math.floor(options.h)));
1756
+ let scanStart = firstRow;
1757
+ while (scanStart > 0 && (rows[scanStart]?.segments.length ?? 0) === 0) scanStart--;
1758
+ for (let rowIndex = scanStart; rowIndex < lastRow; rowIndex++) {
1759
+ const row = rows[rowIndex];
1760
+ if (!row) continue;
1761
+ let logicalX = 0;
1762
+ for (const [segmentIndex, segment] of row.segments.entries()) {
1763
+ const segmentStart = logicalX;
1764
+ const segmentEnd = logicalX + segment.cells;
1765
+ logicalX = segmentEnd;
1766
+ if (!segment.graphic) continue;
1767
+ if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1768
+ if (Math.max(segmentStart, clipStart) !== segmentStart) continue;
1769
+ const height = Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1));
1770
+ if (rowIndex + height <= firstRow || rowIndex >= lastRow) continue;
1771
+ const rect = {
1772
+ x: options.x + segmentStart - clipStart,
1773
+ y: options.y + rowIndex - firstRow,
1774
+ w: Math.max(1, Math.floor(segment.graphic.displayWidth ?? segment.cells)),
1775
+ h: height
1776
+ };
1777
+ if (options.isGraphicCovered?.(rect)) continue;
1778
+ keepIds.add(markdownImageGraphicId(segment.graphic, `${row.key}:${segmentIndex}`));
1779
+ }
1780
+ }
1781
+ return keepIds;
1782
+ }
988
1783
  function paintMarkdownVisualRow(terminal, row, options) {
989
1784
  const clipStart = Math.max(0, Math.floor(options.clipStart ?? 0));
990
1785
  const clipEnd = clipStart + options.w;
991
1786
  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
- });
1787
+ if (options.clear !== false) {
1788
+ terminal.write(spaces(options.w), {
1789
+ x: options.x,
1790
+ y: options.y,
1791
+ style: options.baseStyle
1792
+ });
1793
+ clearStaleMarkdownImageGraphicsForRow(terminal, options.y, new Set(), options.keepGraphicIds);
1794
+ }
997
1795
  return;
998
1796
  }
999
1797
  let logicalX = 0;
1000
1798
  let used = 0;
1001
- for (const segment of row.segments) {
1799
+ const queuedGraphics = new Set();
1800
+ for (const [segmentIndex, segment] of row.segments.entries()) {
1002
1801
  if (used >= options.w || !segment.text) {
1003
1802
  logicalX += segment.cells;
1004
1803
  continue;
@@ -1008,6 +1807,46 @@ function paintMarkdownVisualRow(terminal, row, options) {
1008
1807
  logicalX += segment.cells;
1009
1808
  if (segmentEnd <= clipStart || segmentStart >= clipEnd) continue;
1010
1809
  const segmentStyle = mergeStyle(options.baseStyle, segment.style);
1810
+ if (segment.graphic) {
1811
+ const visibleStart = Math.max(segmentStart, clipStart);
1812
+ const visibleEnd = Math.min(segmentEnd, clipEnd);
1813
+ const visibleCells = visibleEnd - visibleStart;
1814
+ if (visibleCells <= 0) continue;
1815
+ const expectedUsed = visibleStart - clipStart;
1816
+ if (expectedUsed > used) {
1817
+ const pad = Math.min(expectedUsed - used, options.w - used);
1818
+ terminal.write(spaces(pad), {
1819
+ x: options.x + used,
1820
+ y: options.y,
1821
+ style: options.baseStyle
1822
+ });
1823
+ used += pad;
1824
+ }
1825
+ const cellX = options.x + used;
1826
+ const graphicRect = {
1827
+ x: cellX,
1828
+ y: options.y,
1829
+ w: Math.max(1, Math.floor(segment.graphic.displayWidth ?? visibleCells)),
1830
+ h: Math.max(1, Math.floor(segment.graphic.displayHeight ?? 1))
1831
+ };
1832
+ const graphicCovered = options.isGraphicCovered?.(graphicRect) === true;
1833
+ const queueResult = !graphicCovered && visibleStart === segmentStart && visibleCells > 0 && queueMarkdownImageGraphic(terminal, segment.graphic, `${row.key}:${segmentIndex}`, {
1834
+ x: cellX,
1835
+ y: options.y,
1836
+ w: visibleCells
1837
+ });
1838
+ const graphicAvailable = queueResult || canAttemptMarkdownImageGraphic(terminal, segment.graphic);
1839
+ const queuedGraphic = queueResult === "queued";
1840
+ const suppressFallback = queuedGraphic || queueResult === "rejected" || graphicAvailable === true;
1841
+ 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), {
1843
+ x: cellX,
1844
+ y: options.y,
1845
+ style: suppressFallback ? options.baseStyle : segmentStyle
1846
+ });
1847
+ used += visibleCells;
1848
+ continue;
1849
+ }
1011
1850
  let pieceStart = segmentStart;
1012
1851
  forEachTextCellSegment(segment.text, (piece) => {
1013
1852
  if (used >= options.w) return false;
@@ -1037,7 +1876,8 @@ function paintMarkdownVisualRow(terminal, row, options) {
1037
1876
  }
1038
1877
  const pad = Math.min(visibleEnd - visibleStart, options.w - used);
1039
1878
  if (pad > 0) {
1040
- terminal.write(spaces(pad), {
1879
+ const fallback = sliceByCellsRange(piece.text, visibleStart - (pieceStart - piece.cells), visibleEnd - (pieceStart - piece.cells));
1880
+ terminal.write(fallback || spaces(pad), {
1041
1881
  x: options.x + used,
1042
1882
  y: options.y,
1043
1883
  style: segmentStyle
@@ -1046,6 +1886,7 @@ function paintMarkdownVisualRow(terminal, row, options) {
1046
1886
  }
1047
1887
  });
1048
1888
  }
1889
+ clearStaleMarkdownImageGraphicsForRow(terminal, options.y, queuedGraphics, options.keepGraphicIds);
1049
1890
  if (used < options.w && options.clear !== false) terminal.write(spaces(options.w - used), {
1050
1891
  x: options.x + used,
1051
1892
  y: options.y,
@@ -1076,7 +1917,7 @@ function markdownRowSignature(row) {
1076
1917
  return [
1077
1918
  row.key,
1078
1919
  row.plainText,
1079
- row.segments.map((segment) => `${segment.text}\u0001${segment.cells}\u0001${markdownStyleSignature(segment.style)}`).join("")
1920
+ row.segments.map((segment) => `${segment.text}\u0001${segment.cells}\u0001${markdownStyleSignature(segment.style)}\u0001${segment.graphic?.src ?? ""}\u0001${segment.graphic?.base64 ?? ""}\u0001${segment.fallbackText ?? ""}`).join("")
1080
1921
  ].join("");
1081
1922
  }
1082
1923
  function getWheelScrollInput(e) {
@@ -1099,6 +1940,10 @@ function getWheelScrollInput(e) {
1099
1940
  mode: "auto"
1100
1941
  };
1101
1942
  }
1943
+ function rowGraphicSignature(row) {
1944
+ if (!row) return "";
1945
+ return row.segments.map((segment) => segment.graphic ? `${segment.graphic.kind}\u0001${segment.graphic.src}\u0001${segment.graphic.base64 ?? ""}` : "").join("");
1946
+ }
1102
1947
  const TVirtualMarkdown = defineComponent({
1103
1948
  name: "TVirtualMarkdown",
1104
1949
  props: {
@@ -1161,15 +2006,58 @@ const TVirtualMarkdown = defineComponent({
1161
2006
  theme: {
1162
2007
  type: Object,
1163
2008
  default: void 0
2009
+ },
2010
+ imageRenderer: {
2011
+ type: Function,
2012
+ default: void 0
2013
+ },
2014
+ imageMinWidth: {
2015
+ type: Number,
2016
+ default: void 0
2017
+ },
2018
+ imageMaxWidth: {
2019
+ type: Number,
2020
+ default: void 0
2021
+ },
2022
+ imageMinHeight: {
2023
+ type: Number,
2024
+ default: void 0
2025
+ },
2026
+ imageMaxHeight: {
2027
+ type: Number,
2028
+ default: void 0
2029
+ },
2030
+ imagePreserveAspectRatio: {
2031
+ type: Boolean,
2032
+ default: true
2033
+ },
2034
+ imageActions: {
2035
+ type: Boolean,
2036
+ default: false
2037
+ },
2038
+ mathActions: {
2039
+ type: Boolean,
2040
+ default: false
2041
+ },
2042
+ linkActions: {
2043
+ type: Boolean,
2044
+ default: false
2045
+ },
2046
+ imageOcclusionRects: {
2047
+ type: Array,
2048
+ default: void 0
1164
2049
  }
1165
2050
  },
1166
- emits: [
1167
- "update:scrollTop",
1168
- "scroll",
1169
- "focus",
1170
- "blur",
1171
- "keydown"
1172
- ],
2051
+ emits: {
2052
+ "update:scrollTop": (_value) => true,
2053
+ scroll: (_value) => true,
2054
+ focus: () => true,
2055
+ blur: () => true,
2056
+ keydown: (_event) => true,
2057
+ imageAction: (_payload) => true,
2058
+ mathAction: (_payload) => true,
2059
+ linkAction: (_payload) => true
2060
+ },
1173
2061
  setup(props, { emit }) {
1174
2062
  const instance = getCurrentInstance();
1175
2063
  const { terminal, defaultStyle, events, scheduler, selection, widthProvider } = useTerminal();
@@ -1190,6 +2078,10 @@ const TVirtualMarkdown = defineComponent({
1190
2078
  streaming: props.streaming,
1191
2079
  customHtmlTags: props.customHtmlTags
1192
2080
  })));
2081
+ const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
2082
+ const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
2083
+ graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
2084
+ });
1193
2085
  watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
1194
2086
  parser.value = markRaw(createTuiMarkdownParser({
1195
2087
  streaming: props.streaming,
@@ -1199,10 +2091,19 @@ const TVirtualMarkdown = defineComponent({
1199
2091
  function rebuildRows() {
1200
2092
  const prevScrollTop = internalScrollTop.value;
1201
2093
  const prevVisibleRows = visibleRowSignatures(rows.value, prevScrollTop);
2094
+ const prevVisibleGraphics = visibleRowGraphics(rows.value, prevScrollTop);
1202
2095
  const nextLayout = withTextWidthProvider(widthProvider, () => {
1203
2096
  const blocks = props.blocks ?? buildMarkdownBlocks(props.content, parser.value, {
1204
2097
  final: props.final,
1205
- theme: props.theme
2098
+ theme: props.theme,
2099
+ imageResolver: props.imageRenderer,
2100
+ imageSize: {
2101
+ minWidth: props.imageMinWidth,
2102
+ maxWidth: props.imageMaxWidth,
2103
+ minHeight: props.imageMinHeight,
2104
+ maxHeight: props.imageMaxHeight,
2105
+ preserveAspectRatio: props.imagePreserveAspectRatio
2106
+ }
1206
2107
  }).blocks;
1207
2108
  return layoutMarkdownBlocksCached(blocks, props.w, layoutCache);
1208
2109
  });
@@ -1212,7 +2113,8 @@ const TVirtualMarkdown = defineComponent({
1212
2113
  reconcileScrollTop();
1213
2114
  const nextScrollTop = internalScrollTop.value;
1214
2115
  const nextVisibleRows = visibleRowSignatures(nextRows, nextScrollTop);
1215
- const visibleChanged = prevVisibleRows.length !== nextVisibleRows.length || prevVisibleRows.some((row, index) => row !== nextVisibleRows[index]);
2116
+ const nextVisibleGraphics = visibleRowGraphics(nextRows, nextScrollTop);
2117
+ const visibleChanged = prevVisibleRows.length !== nextVisibleRows.length || prevVisibleRows.some((row, index) => row !== nextVisibleRows[index]) || prevVisibleGraphics.some((row, index) => row !== nextVisibleGraphics[index]);
1216
2118
  if (!builtOnce || prevScrollTop !== nextScrollTop || visibleChanged) {
1217
2119
  documentVersion.value++;
1218
2120
  selection.refresh();
@@ -1281,6 +2183,15 @@ const TVirtualMarkdown = defineComponent({
1281
2183
  for (let index = start; index < end; index++) out.push(markdownRowSignature(sourceRows[index]));
1282
2184
  return out;
1283
2185
  }
2186
+ function visibleRowGraphics(sourceRows, scrollTop) {
2187
+ const clip = normalizedRect();
2188
+ const { y: clipY } = clipOffsets();
2189
+ const start = Math.max(0, scrollTop + clipY);
2190
+ const end = Math.max(start, start + clip.h);
2191
+ const out = [];
2192
+ for (let index = start; index < end; index++) out.push(rowGraphicSignature(sourceRows[index]));
2193
+ return out;
2194
+ }
1284
2195
  function hasControlledScrollTop() {
1285
2196
  return Object.prototype.hasOwnProperty.call(instance?.vnode.props ?? {}, "scrollTop");
1286
2197
  }
@@ -1329,7 +2240,13 @@ const TVirtualMarkdown = defineComponent({
1329
2240
  () => props.w,
1330
2241
  () => parser.value,
1331
2242
  () => props.final,
1332
- () => markdownThemeSignature(props.theme)
2243
+ () => props.imageRenderer,
2244
+ () => markdownThemeSignature(props.theme),
2245
+ () => props.imageMinWidth,
2246
+ () => props.imageMaxWidth,
2247
+ () => props.imageMinHeight,
2248
+ () => props.imageMaxHeight,
2249
+ () => props.imagePreserveAspectRatio
1333
2250
  ], () => {
1334
2251
  scheduleRebuild();
1335
2252
  }, { immediate: true });
@@ -1462,7 +2379,45 @@ const TVirtualMarkdown = defineComponent({
1462
2379
  selectable: props.selectable,
1463
2380
  selectionScrollBy: scrollSelectionBy,
1464
2381
  handlers: {
1465
- click: (_event) => {},
2382
+ click: (event) => {
2383
+ const r = normalizedRect();
2384
+ const { x: clipX, y: clipY } = clipOffsets();
2385
+ const hitOptions = {
2386
+ screenRect: r,
2387
+ rowOffset: internalScrollTop.value + clipY,
2388
+ clipStart: clipX
2389
+ };
2390
+ if (props.imageActions) {
2391
+ const hit = findMarkdownImageActionAt(rows.value, {
2392
+ cellX: event.cellX,
2393
+ cellY: event.cellY
2394
+ }, hitOptions);
2395
+ if (hit) {
2396
+ event.preventDefault();
2397
+ emit("imageAction", hit);
2398
+ return;
2399
+ }
2400
+ }
2401
+ if (props.mathActions) {
2402
+ const math = findMarkdownMathActionAt(rows.value, {
2403
+ cellX: event.cellX,
2404
+ cellY: event.cellY
2405
+ }, hitOptions);
2406
+ if (math) {
2407
+ event.preventDefault();
2408
+ emit("mathAction", math);
2409
+ return;
2410
+ }
2411
+ }
2412
+ if (!props.linkActions) return;
2413
+ const link = findMarkdownLinkActionAt(rows.value, {
2414
+ cellX: event.cellX,
2415
+ cellY: event.cellY
2416
+ }, hitOptions);
2417
+ if (!link) return;
2418
+ event.preventDefault();
2419
+ emit("linkAction", link);
2420
+ },
1466
2421
  wheel: (event) => {
1467
2422
  const { deltaY, mode } = getWheelScrollInput(event);
1468
2423
  if (!deltaY) return;
@@ -1493,6 +2448,8 @@ const TVirtualMarkdown = defineComponent({
1493
2448
  onBeforeUnmount(() => {
1494
2449
  alive = false;
1495
2450
  rebuildVersion++;
2451
+ unsubscribeGraphicsOutput();
2452
+ clearMarkdownImageGraphics(terminal, fullRect.value);
1496
2453
  });
1497
2454
  useRenderNode(() => ({
1498
2455
  zIndex: props.zIndex,
@@ -1509,7 +2466,9 @@ const TVirtualMarkdown = defineComponent({
1509
2466
  internalScrollTop.value,
1510
2467
  props.style,
1511
2468
  defaultStyle.value,
1512
- documentVersion.value
2469
+ documentVersion.value,
2470
+ graphicsOutputVersion.value,
2471
+ props.imageOcclusionRects
1513
2472
  ],
1514
2473
  paint: (dirtyRows) => {
1515
2474
  withTextWidthProvider(widthProvider, () => {
@@ -1518,6 +2477,18 @@ const TVirtualMarkdown = defineComponent({
1518
2477
  if (r.w <= 0 || r.h <= 0) return;
1519
2478
  const baseStyle = props.style ?? defaultStyle.value;
1520
2479
  const { x: clipX, y: clipY } = clipOffsets();
2480
+ const isGraphicCovered = (rect) => {
2481
+ return props.imageOcclusionRects?.some((item) => intersectRect(rect, item)) === true;
2482
+ };
2483
+ const keepGraphicIds = collectVisibleMarkdownImageGraphicIds(rows.value, {
2484
+ x: r.x,
2485
+ y: r.y,
2486
+ w: r.w,
2487
+ h: r.h,
2488
+ rowOffset: internalScrollTop.value + clipY,
2489
+ clipStart: clipX,
2490
+ isGraphicCovered
2491
+ });
1521
2492
  const paintRow = (y) => {
1522
2493
  if (y < r.y || y >= r.y + r.h) return;
1523
2494
  const visualIndex = internalScrollTop.value + clipY + (y - r.y);
@@ -1527,7 +2498,9 @@ const TVirtualMarkdown = defineComponent({
1527
2498
  w: r.w,
1528
2499
  clipStart: clipX,
1529
2500
  baseStyle,
1530
- clear: true
2501
+ clear: true,
2502
+ keepGraphicIds,
2503
+ isGraphicCovered
1531
2504
  });
1532
2505
  };
1533
2506
  if (dirtyRows?.length) {
@@ -1660,4 +2633,4 @@ function createMarkdownBlockSource(options) {
1660
2633
  }
1661
2634
 
1662
2635
  //#endregion
1663
- export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, createMarkdownBlockSource, createTuiMarkdownParser, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };
2636
+ export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow };