@simon_he/vue-tui 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -28
- package/dist/{TMermaidText-CXm_L969.js → TMermaidText-frVr8Ysb.js} +1 -1
- package/dist/{TSelect-CiyMvwl-.js → TSelect-Dx34M1pF.js} +6 -168
- package/dist/{TTree-DKGiuIcM.js → TTree-DdAjCPHu.js} +12 -13
- package/dist/agent.cjs +3405 -13393
- package/dist/agent.js +15 -1197
- package/dist/{block-source-5CQp4tF8.js → block-source-Crro3mxi.js} +191 -724
- package/dist/cli/input.d.ts +8 -0
- package/dist/cli/video/ffmpeg-frame-source.d.ts +10 -0
- package/dist/cli/video/png-frame-parser.d.ts +4 -0
- package/dist/cli/video/raw-frame-parser.d.ts +1 -0
- package/dist/cli/video/yt-dlp-frame-source.d.ts +11 -0
- package/dist/cli.cjs +142 -33
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +167 -37
- package/dist/core.js +5 -6
- package/dist/create-terminal-app.d.ts +24 -1
- package/dist/{debug-logger-ChY_PXVk.js → debug-logger-CmsGI4of.js} +1 -1
- package/dist/{dom-renderer-DNzmg2As.js → dom-renderer-DXx8fWzD.js} +2 -3
- package/dist/experimental/3d/bun/contributor-avatar-atlas.d.ts +12 -0
- package/dist/experimental/3d/bun/png.d.ts +1 -0
- package/dist/experimental/3d/bun/terminal-badge-scene.d.ts +11 -0
- package/dist/experimental/3d/bun/terminal-badge.wgsl.d.ts +1 -0
- package/dist/experimental/3d/bun/webgpu-renderer.d.ts +26 -0
- package/dist/experimental/3d/bun.cjs +7271 -0
- package/dist/experimental/3d/bun.d.cts +4 -0
- package/dist/experimental/3d/bun.d.ts +4 -0
- package/dist/experimental/3d/bun.js +7390 -0
- package/dist/experimental/video/node.cjs +497 -0
- package/dist/experimental/video/node.d.cts +4 -0
- package/dist/experimental/video/node.d.ts +4 -0
- package/dist/experimental/video/node.js +610 -0
- package/dist/experimental.cjs +3064 -567
- package/dist/experimental.d.cts +5 -0
- package/dist/experimental.d.ts +5 -0
- package/dist/experimental.js +1524 -9
- package/dist/{create-terminal-DXuZ2fci.js → hyperlink-_1FAj7Wq.js} +403 -2
- package/dist/index.js +15 -16
- package/dist/{linkify-CuuquMQT.js → linkify-BjI4zVfY.js} +164 -3
- package/dist/markdown.cjs +2929 -12918
- package/dist/markdown.d.cts +1 -0
- package/dist/markdown.d.ts +1 -0
- package/dist/markdown.js +13 -9
- package/dist/{mermaid-CWto52Wk.js → mermaid-BNhM1jAZ.js} +1 -1
- package/dist/mermaid.js +3 -3
- package/dist/observability.js +2 -2
- package/dist/{path-suggest-fPzRsgPv.js → path-suggest-Dndu4dTi.js} +1 -1
- package/dist/renderer/terminal-graphics.d.ts +1 -0
- package/dist/renderer-dom.js +3 -4
- package/dist/runtime.js +2 -2
- package/dist/terminal-graphics-Bbe83wSr.js +597 -0
- package/dist/{tlog-theme-CopRt9SC.js → tlog-theme-i6S_Oftd.js} +1206 -12
- package/dist/{trace-CYHDKM1m.js → trace-Bh8WnoMo.js} +1 -1
- package/dist/vue/components/T3DViewport.d.ts +473 -0
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +13 -0
- package/dist/vue/components/TVideo.d.ts +291 -0
- package/dist/vue/markdown/math.d.ts +2 -0
- package/dist/vue/video/types.d.ts +39 -0
- package/dist/vue.js +17 -18
- package/package.json +44 -3
- package/dist/hyperlink-B35-O-cW.js +0 -403
- /package/dist/{ansi-palette-CNXqFciC.js → ansi-palette-CH6jc2Z7.js} +0 -0
- /package/dist/{env-DNCzvX8b.js → env-Cl8qmNFN.js} +0 -0
- /package/dist/{frame-perf-BBhE-iuS.js → frame-perf-CHpWhbC4.js} +0 -0
- /package/dist/{path-BI9W15Zt.js → path-mnSgEpCm.js} +0 -0
- /package/dist/{rect-Ng-3eKzF.js → rect-DUpeDfBg.js} +0 -0
- /package/dist/{runtime-D77C03RM.js → runtime-Byf8XXOT.js} +0 -0
- /package/dist/{terminal-selection-CQYr--26.js → terminal-selection-4W1SJj4c.js} +0 -0
- /package/dist/{wheel-scroll-CQ22Hcyj.js → wheel-scroll-DASkEZr-.js} +0 -0
|
@@ -1,607 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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";
|
|
7
7
|
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, ref, shallowRef, watch, watchEffect } from "vue";
|
|
8
|
-
import katex from "katex";
|
|
9
8
|
import { getMarkdown, parseMarkdownToStructure } from "stream-markdown-parser";
|
|
10
9
|
|
|
11
|
-
//#region src/renderer/terminal-graphics.ts
|
|
12
|
-
const TERMINAL_GRAPHICS_PROTOCOL_PRIORITY = [
|
|
13
|
-
"kitty",
|
|
14
|
-
"iterm2",
|
|
15
|
-
"sixel"
|
|
16
|
-
];
|
|
17
|
-
const ESC = "\x1B";
|
|
18
|
-
const BEL = "\x07";
|
|
19
|
-
const ST = `${ESC}\\`;
|
|
20
|
-
const ESC_RE = "\\u001B";
|
|
21
|
-
const BEL_RE = "\\u0007";
|
|
22
|
-
const CSI_RE = new RegExp(`${ESC_RE}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
23
|
-
const OSC_RE = new RegExp(`${ESC_RE}\\][\\s\\S]*?(?:${BEL_RE}|${ESC_RE}\\\\)`, "g");
|
|
24
|
-
const DCS_RE = new RegExp(`${ESC_RE}P[\\s\\S]*?${ESC_RE}\\\\`, "g");
|
|
25
|
-
const APC_RE = new RegExp(`${ESC_RE}_[\\s\\S]*?${ESC_RE}\\\\`, "g");
|
|
26
|
-
const C1_CSI_RE = new RegExp("\\u009B[0-?]*[ -/]*[@-~]", "g");
|
|
27
|
-
const C1_OSC_RE = new RegExp(`\\u009D[\\s\\S]*?(?:${BEL_RE}|\\u009C)`, "g");
|
|
28
|
-
const C1_DCS_RE = new RegExp("\\u0090[\\s\\S]*?\\u009C", "g");
|
|
29
|
-
const C1_APC_RE = new RegExp("\\u009F[\\s\\S]*?\\u009C", "g");
|
|
30
|
-
const MAX_TERMINAL_GRAPHICS_SEQUENCE_CHARS = 16 * 1024 * 1024;
|
|
31
|
-
const MAX_TERMINAL_GRAPHICS_FALLBACK_CHARS = 16384;
|
|
32
|
-
const MAX_TERMINAL_GRAPHIC_CELLS = 1e4;
|
|
33
|
-
const TERMINAL_GRAPHICS_REGISTRY = Symbol.for("@simon_he/vue-tui:v1:terminal-graphics");
|
|
34
|
-
const terminalGraphicsGlobal = globalThis;
|
|
35
|
-
const terminalGraphicsRegistry = terminalGraphicsGlobal[TERMINAL_GRAPHICS_REGISTRY] ?? {
|
|
36
|
-
outputs: new WeakMap(),
|
|
37
|
-
outputVersions: new WeakMap(),
|
|
38
|
-
outputListeners: new WeakMap()
|
|
39
|
-
};
|
|
40
|
-
terminalGraphicsGlobal[TERMINAL_GRAPHICS_REGISTRY] = terminalGraphicsRegistry;
|
|
41
|
-
const terminalGraphicsOutputs = terminalGraphicsRegistry.outputs;
|
|
42
|
-
const terminalGraphicsOutputVersions = terminalGraphicsRegistry.outputVersions;
|
|
43
|
-
const terminalGraphicsOutputListeners = terminalGraphicsRegistry.outputListeners;
|
|
44
|
-
function getTerminalGraphicsOutput(terminal) {
|
|
45
|
-
return terminalGraphicsOutputs.get(getRootTerminal(terminal)) ?? null;
|
|
46
|
-
}
|
|
47
|
-
function getTerminalGraphicsOutputVersion(terminal) {
|
|
48
|
-
return terminalGraphicsOutputVersions.get(getRootTerminal(terminal)) ?? 0;
|
|
49
|
-
}
|
|
50
|
-
function subscribeTerminalGraphicsOutput(terminal, listener) {
|
|
51
|
-
const rootTerminal = getRootTerminal(terminal);
|
|
52
|
-
let listeners = terminalGraphicsOutputListeners.get(rootTerminal);
|
|
53
|
-
if (!listeners) {
|
|
54
|
-
listeners = new Set();
|
|
55
|
-
terminalGraphicsOutputListeners.set(rootTerminal, listeners);
|
|
56
|
-
}
|
|
57
|
-
listeners.add(listener);
|
|
58
|
-
return () => {
|
|
59
|
-
listeners.delete(listener);
|
|
60
|
-
if (listeners.size === 0) terminalGraphicsOutputListeners.delete(rootTerminal);
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function envString(env, key) {
|
|
64
|
-
const value = env[key];
|
|
65
|
-
return value == null ? void 0 : String(value);
|
|
66
|
-
}
|
|
67
|
-
function truthy(value) {
|
|
68
|
-
if (value == null) return false;
|
|
69
|
-
return /^(1|true|yes|on)$/i.test(String(value));
|
|
70
|
-
}
|
|
71
|
-
function readStdoutIsTTY() {
|
|
72
|
-
const maybeProcess = globalThis.process;
|
|
73
|
-
return Boolean(maybeProcess?.stdout?.isTTY);
|
|
74
|
-
}
|
|
75
|
-
function readProcessEnv() {
|
|
76
|
-
const maybeProcess = globalThis.process;
|
|
77
|
-
return maybeProcess?.env ?? {};
|
|
78
|
-
}
|
|
79
|
-
function normalizeMode(value) {
|
|
80
|
-
const raw = String(value ?? "auto").trim().toLowerCase();
|
|
81
|
-
if (raw === "auto" || raw === "off" || raw === "none" || raw === "kitty" || raw === "iterm2" || raw === "sixel" || raw === "unicode") return raw === "off" ? "none" : raw;
|
|
82
|
-
return "auto";
|
|
83
|
-
}
|
|
84
|
-
function isTerminalGraphicsProtocol(value) {
|
|
85
|
-
return value === "kitty" || value === "iterm2" || value === "sixel";
|
|
86
|
-
}
|
|
87
|
-
function uniqueProtocols(protocols) {
|
|
88
|
-
return [...new Set(protocols)];
|
|
89
|
-
}
|
|
90
|
-
function pickBestTerminalGraphicsProtocol(protocols) {
|
|
91
|
-
const available = new Set(protocols);
|
|
92
|
-
for (const protocol of TERMINAL_GRAPHICS_PROTOCOL_PRIORITY) if (available.has(protocol)) return protocol;
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
function detectCandidates(env) {
|
|
96
|
-
const candidates = [];
|
|
97
|
-
const term = String(envString(env, "TERM") ?? "").toLowerCase();
|
|
98
|
-
const termProgram = String(envString(env, "TERM_PROGRAM") ?? "").toLowerCase();
|
|
99
|
-
const termProgramVersion = String(envString(env, "TERM_PROGRAM_VERSION") ?? "").toLowerCase();
|
|
100
|
-
if (envString(env, "KITTY_WINDOW_ID") || envString(env, "GHOSTTY_RESOURCES_DIR") || /(?:^|-)kitty(?:-|$)/i.test(term) || term.includes("ghostty") || termProgram.includes("ghostty")) candidates.push("kitty");
|
|
101
|
-
if (termProgram.includes("iterm") || termProgram.includes("wezterm") || envString(env, "WEZTERM_PANE") || envString(env, "WEZTERM_EXECUTABLE")) candidates.push("iterm2");
|
|
102
|
-
if (truthy(envString(env, "VUE_TUI_SIXEL")) || truthy(envString(env, "VUE_TUI_GRAPHICS_SIXEL")) || String(envString(env, "TERMINAL_GRAPHICS") ?? "").toLowerCase().includes("sixel") || /\bsixel\b/i.test(`${term} ${termProgram} ${termProgramVersion}`)) candidates.push("sixel");
|
|
103
|
-
return uniqueProtocols(candidates);
|
|
104
|
-
}
|
|
105
|
-
function capabilitiesFor(protocol, input) {
|
|
106
|
-
const supported = protocol === "kitty" || protocol === "iterm2" || protocol === "sixel";
|
|
107
|
-
const candidates = uniqueProtocols([...input.candidates ?? (supported ? [protocol] : [])]);
|
|
108
|
-
return {
|
|
109
|
-
supported,
|
|
110
|
-
kitty: protocol === "kitty" || candidates.includes("kitty"),
|
|
111
|
-
iterm2: protocol === "iterm2" || candidates.includes("iterm2"),
|
|
112
|
-
sixel: protocol === "sixel" || candidates.includes("sixel"),
|
|
113
|
-
preferredProtocol: supported ? protocol : null,
|
|
114
|
-
protocol,
|
|
115
|
-
candidates,
|
|
116
|
-
stdoutIsTTY: input.stdoutIsTTY,
|
|
117
|
-
insideTmux: input.insideTmux,
|
|
118
|
-
insideScreen: input.insideScreen,
|
|
119
|
-
insideZellij: input.insideZellij,
|
|
120
|
-
multiplexer: input.multiplexer,
|
|
121
|
-
passthrough: input.passthrough,
|
|
122
|
-
forced: input.forced,
|
|
123
|
-
reason: input.reason
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function detectTerminalGraphicsCapabilities(options = {}) {
|
|
127
|
-
const env = options.env ?? readProcessEnv();
|
|
128
|
-
const stdoutIsTTY = options.stdoutIsTTY ?? options.isTTY ?? readStdoutIsTTY();
|
|
129
|
-
const term = String(envString(env, "TERM") ?? "").toLowerCase();
|
|
130
|
-
const insideTmux = Boolean(envString(env, "TMUX")) || term.includes("tmux");
|
|
131
|
-
const insideScreen = Boolean(envString(env, "STY")) || term.startsWith("screen");
|
|
132
|
-
const insideZellij = Boolean(envString(env, "ZELLIJ") || envString(env, "ZELLIJ_SESSION_NAME")) || term.includes("zellij");
|
|
133
|
-
const multiplexer = insideTmux ? "tmux" : insideScreen ? "screen" : insideZellij ? "zellij" : null;
|
|
134
|
-
const passthroughRequested = options.passthrough ?? (truthy(envString(env, "VUE_TUI_TERMINAL_GRAPHICS_PASSTHROUGH")) || truthy(envString(env, "VUE_TUI_GRAPHICS_TMUX_PASSTHROUGH")));
|
|
135
|
-
const passthroughSupported = !multiplexer || multiplexer === "tmux";
|
|
136
|
-
const passthrough = passthroughRequested && passthroughSupported;
|
|
137
|
-
const protocolOptionProvided = options.protocol != null;
|
|
138
|
-
const mode = normalizeMode(options.protocol ?? envString(env, "VUE_TUI_TERMINAL_GRAPHICS") ?? envString(env, "VUE_TUI_GRAPHICS_PROTOCOL"));
|
|
139
|
-
const forceFromOption = options.force === true;
|
|
140
|
-
const force = options.force ?? truthy(envString(env, "VUE_TUI_GRAPHICS_FORCE"));
|
|
141
|
-
const base = {
|
|
142
|
-
stdoutIsTTY,
|
|
143
|
-
insideTmux,
|
|
144
|
-
insideScreen,
|
|
145
|
-
insideZellij,
|
|
146
|
-
multiplexer,
|
|
147
|
-
passthrough,
|
|
148
|
-
forced: force
|
|
149
|
-
};
|
|
150
|
-
if (mode === "none") return capabilitiesFor("none", {
|
|
151
|
-
...base,
|
|
152
|
-
reason: protocolOptionProvided ? "disabled-by-option" : "disabled-by-env"
|
|
153
|
-
});
|
|
154
|
-
if (!stdoutIsTTY && !force) return capabilitiesFor("unicode", {
|
|
155
|
-
...base,
|
|
156
|
-
reason: "stdout-is-not-tty"
|
|
157
|
-
});
|
|
158
|
-
if (truthy(envString(env, "CI")) && !force) return capabilitiesFor("unicode", {
|
|
159
|
-
...base,
|
|
160
|
-
reason: "ci"
|
|
161
|
-
});
|
|
162
|
-
if (multiplexer && passthroughRequested && !passthroughSupported && !force) return capabilitiesFor("unicode", {
|
|
163
|
-
...base,
|
|
164
|
-
reason: `${multiplexer}-passthrough-not-implemented`
|
|
165
|
-
});
|
|
166
|
-
if (multiplexer && !passthrough && !force) return capabilitiesFor("unicode", {
|
|
167
|
-
...base,
|
|
168
|
-
reason: `${multiplexer}-without-passthrough`
|
|
169
|
-
});
|
|
170
|
-
if (mode === "kitty" || mode === "iterm2" || mode === "sixel") return capabilitiesFor(mode, {
|
|
171
|
-
...base,
|
|
172
|
-
candidates: [mode],
|
|
173
|
-
reason: force ? forceFromOption ? "forced-by-option" : "forced-by-env" : protocolOptionProvided ? "selected-by-option" : "selected-by-env"
|
|
174
|
-
});
|
|
175
|
-
if (mode === "unicode") return capabilitiesFor("unicode", {
|
|
176
|
-
...base,
|
|
177
|
-
reason: protocolOptionProvided ? "unicode-forced-by-option" : "unicode-forced-by-env"
|
|
178
|
-
});
|
|
179
|
-
const candidates = detectCandidates(env);
|
|
180
|
-
const preferredProtocol = pickBestTerminalGraphicsProtocol(candidates);
|
|
181
|
-
const protocol = preferredProtocol ?? "unicode";
|
|
182
|
-
return capabilitiesFor(protocol, {
|
|
183
|
-
...base,
|
|
184
|
-
candidates,
|
|
185
|
-
reason: protocol === "unicode" ? "no-supported-graphics-protocol-detected" : "auto-detected"
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
function hashTerminalGraphicsString(input) {
|
|
189
|
-
let hash = 2166136261;
|
|
190
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
191
|
-
hash ^= input.charCodeAt(index);
|
|
192
|
-
hash = Math.imul(hash, 16777619);
|
|
193
|
-
}
|
|
194
|
-
return (hash >>> 0).toString(36);
|
|
195
|
-
}
|
|
196
|
-
function stableTerminalGraphicNumericId(input, options = {}) {
|
|
197
|
-
const min = Math.floor(options.min ?? 1);
|
|
198
|
-
const max = Math.floor(options.max ?? 2147483647);
|
|
199
|
-
const span = Math.max(1, max - min + 1);
|
|
200
|
-
let hash = 2166136261;
|
|
201
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
202
|
-
hash ^= input.charCodeAt(index);
|
|
203
|
-
hash = Math.imul(hash, 16777619);
|
|
204
|
-
}
|
|
205
|
-
return min + (hash >>> 0) % span;
|
|
206
|
-
}
|
|
207
|
-
function positiveGraphicInt(value, max) {
|
|
208
|
-
const n = Math.trunc(Number(value));
|
|
209
|
-
if (!Number.isFinite(n) || n <= 0) return null;
|
|
210
|
-
return Math.min(max, n);
|
|
211
|
-
}
|
|
212
|
-
function normalizeTerminalGraphicSize(width, height) {
|
|
213
|
-
const w = positiveGraphicInt(width, MAX_TERMINAL_GRAPHIC_CELLS);
|
|
214
|
-
const h$1 = positiveGraphicInt(height, MAX_TERMINAL_GRAPHIC_CELLS);
|
|
215
|
-
if (w == null || h$1 == null) return null;
|
|
216
|
-
if (w * h$1 > MAX_TERMINAL_GRAPHIC_CELLS) return null;
|
|
217
|
-
return {
|
|
218
|
-
width: w,
|
|
219
|
-
height: h$1
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function canDrawTerminalGraphicRect(rect, size) {
|
|
223
|
-
const x = Math.floor(rect.x);
|
|
224
|
-
const y = Math.floor(rect.y);
|
|
225
|
-
const graphicSize = normalizeTerminalGraphicSize(rect.w, rect.h);
|
|
226
|
-
const cols = Math.max(0, Math.floor(size.cols));
|
|
227
|
-
const rows = Math.max(0, Math.floor(size.rows));
|
|
228
|
-
return graphicSize != null && cols > 0 && rows > 0 && Number.isFinite(x) && Number.isFinite(y) && x >= 0 && y >= 0 && x < cols && y < rows;
|
|
229
|
-
}
|
|
230
|
-
function withoutNestedEscapes(body) {
|
|
231
|
-
return !body.includes(ESC) && !body.includes(BEL);
|
|
232
|
-
}
|
|
233
|
-
function isBase64ish(value) {
|
|
234
|
-
return /^[A-Za-z0-9+/=]*$/.test(value);
|
|
235
|
-
}
|
|
236
|
-
function parseKittyControls(raw) {
|
|
237
|
-
const out = new Map();
|
|
238
|
-
if (!/^[A-Za-z0-9_,=+.\-:]*$/.test(raw)) return null;
|
|
239
|
-
for (const part of raw.split(",")) {
|
|
240
|
-
if (!part) continue;
|
|
241
|
-
const eq = part.indexOf("=");
|
|
242
|
-
if (eq <= 0) return null;
|
|
243
|
-
const key = part.slice(0, eq);
|
|
244
|
-
const value = part.slice(eq + 1);
|
|
245
|
-
if (!/^[A-Za-z]$/.test(key)) return null;
|
|
246
|
-
if (!/^[A-Za-z0-9+.\-:]*$/.test(value)) return null;
|
|
247
|
-
out.set(key, value);
|
|
248
|
-
}
|
|
249
|
-
return out;
|
|
250
|
-
}
|
|
251
|
-
function parseKittyPackets(sequence) {
|
|
252
|
-
const packets = [];
|
|
253
|
-
let index = 0;
|
|
254
|
-
while (index < sequence.length) {
|
|
255
|
-
if (!sequence.startsWith(`${ESC}_G`, index)) return null;
|
|
256
|
-
const end = sequence.indexOf(ST, index + 3);
|
|
257
|
-
if (end < 0) return null;
|
|
258
|
-
const body = sequence.slice(index + 3, end);
|
|
259
|
-
if (!withoutNestedEscapes(body)) return null;
|
|
260
|
-
const semicolon = body.indexOf(";");
|
|
261
|
-
const rawControls = semicolon >= 0 ? body.slice(0, semicolon) : body;
|
|
262
|
-
const payload = semicolon >= 0 ? body.slice(semicolon + 1) : "";
|
|
263
|
-
const controls = parseKittyControls(rawControls);
|
|
264
|
-
if (!controls) return null;
|
|
265
|
-
if (payload && !isBase64ish(payload)) return null;
|
|
266
|
-
packets.push({
|
|
267
|
-
controls,
|
|
268
|
-
payload
|
|
269
|
-
});
|
|
270
|
-
index = end + ST.length;
|
|
271
|
-
}
|
|
272
|
-
return packets.length ? packets : null;
|
|
273
|
-
}
|
|
274
|
-
const KITTY_DRAW_FIRST_CONTROL_KEYS = new Set([
|
|
275
|
-
"a",
|
|
276
|
-
"f",
|
|
277
|
-
"q",
|
|
278
|
-
"C",
|
|
279
|
-
"i",
|
|
280
|
-
"I",
|
|
281
|
-
"p",
|
|
282
|
-
"c",
|
|
283
|
-
"r",
|
|
284
|
-
"x",
|
|
285
|
-
"y",
|
|
286
|
-
"w",
|
|
287
|
-
"h",
|
|
288
|
-
"z",
|
|
289
|
-
"m",
|
|
290
|
-
"t"
|
|
291
|
-
]);
|
|
292
|
-
const KITTY_DRAW_CONTINUATION_CONTROL_KEYS = new Set([
|
|
293
|
-
"a",
|
|
294
|
-
"q",
|
|
295
|
-
"m"
|
|
296
|
-
]);
|
|
297
|
-
const KITTY_CLEAR_CONTROL_KEYS = new Set([
|
|
298
|
-
"a",
|
|
299
|
-
"d",
|
|
300
|
-
"i",
|
|
301
|
-
"I",
|
|
302
|
-
"p",
|
|
303
|
-
"q"
|
|
304
|
-
]);
|
|
305
|
-
function kittyControlsUseOnly(controls, allowed) {
|
|
306
|
-
for (const key of controls.keys()) if (!allowed.has(key)) return false;
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
const KITTY_UINT32_MAX = 4294967295;
|
|
310
|
-
const KITTY_INT32_MIN = -2147483648;
|
|
311
|
-
const KITTY_INT32_MAX = 2147483647;
|
|
312
|
-
function kittyControlIsUint32(controls, key, min = 0) {
|
|
313
|
-
const value = controls.get(key);
|
|
314
|
-
if (value == null) return true;
|
|
315
|
-
if (!/^\d+$/.test(value)) return false;
|
|
316
|
-
const n = Number(value);
|
|
317
|
-
return Number.isSafeInteger(n) && n >= min && n <= KITTY_UINT32_MAX;
|
|
318
|
-
}
|
|
319
|
-
function kittyControlIsInt32(controls, key) {
|
|
320
|
-
const value = controls.get(key);
|
|
321
|
-
if (value == null) return true;
|
|
322
|
-
if (!/^-?\d+$/.test(value)) return false;
|
|
323
|
-
const n = Number(value);
|
|
324
|
-
return Number.isSafeInteger(n) && n >= KITTY_INT32_MIN && n <= KITTY_INT32_MAX;
|
|
325
|
-
}
|
|
326
|
-
function kittyControlPositiveInt(controls, key, fallback) {
|
|
327
|
-
const value = controls.get(key);
|
|
328
|
-
if (value == null) return fallback;
|
|
329
|
-
if (!/^\d+$/.test(value)) return null;
|
|
330
|
-
const n = Number(value);
|
|
331
|
-
return Number.isSafeInteger(n) && n >= 1 ? n : null;
|
|
332
|
-
}
|
|
333
|
-
function kittyControlIsTerminalGraphicCellsBounded(controls) {
|
|
334
|
-
const columns = kittyControlPositiveInt(controls, "c", 1);
|
|
335
|
-
const rows = kittyControlPositiveInt(controls, "r", 1);
|
|
336
|
-
return columns != null && rows != null && columns * rows <= MAX_TERMINAL_GRAPHIC_CELLS;
|
|
337
|
-
}
|
|
338
|
-
function validateKittySharedSafeControls(controls) {
|
|
339
|
-
const q = controls.get("q");
|
|
340
|
-
if (q != null && !/^[0-2]$/.test(q)) return false;
|
|
341
|
-
const cursorMove = controls.get("C");
|
|
342
|
-
if (cursorMove != null && !/^[01]$/.test(cursorMove)) return false;
|
|
343
|
-
for (const key of [
|
|
344
|
-
"i",
|
|
345
|
-
"I",
|
|
346
|
-
"p"
|
|
347
|
-
]) if (!kittyControlIsUint32(controls, key)) return false;
|
|
348
|
-
for (const key of ["c", "r"]) if (!kittyControlIsUint32(controls, key, 1)) return false;
|
|
349
|
-
if (!kittyControlIsTerminalGraphicCellsBounded(controls)) return false;
|
|
350
|
-
for (const key of ["x", "y"]) if (!kittyControlIsUint32(controls, key)) return false;
|
|
351
|
-
for (const key of ["w", "h"]) if (!kittyControlIsUint32(controls, key, 1)) return false;
|
|
352
|
-
if (!kittyControlIsInt32(controls, "z")) return false;
|
|
353
|
-
const transmissionMedium = controls.get("t");
|
|
354
|
-
if (transmissionMedium != null && transmissionMedium !== "d") return false;
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
function validateKittySequence(sequence, op = "draw") {
|
|
358
|
-
const packets = parseKittyPackets(sequence);
|
|
359
|
-
if (!packets) return false;
|
|
360
|
-
if (op === "clear") {
|
|
361
|
-
if (packets.length !== 1) return false;
|
|
362
|
-
const packet = packets[0];
|
|
363
|
-
if (!kittyControlsUseOnly(packet.controls, KITTY_CLEAR_CONTROL_KEYS)) return false;
|
|
364
|
-
if (!validateKittySharedSafeControls(packet.controls)) return false;
|
|
365
|
-
if (packet.payload) return false;
|
|
366
|
-
if (packet.controls.get("a") !== "d") return false;
|
|
367
|
-
const d = packet.controls.get("d");
|
|
368
|
-
return d == null || /^[aAcCiI]$/.test(d);
|
|
369
|
-
}
|
|
370
|
-
const first = packets[0];
|
|
371
|
-
if (!kittyControlsUseOnly(first.controls, KITTY_DRAW_FIRST_CONTROL_KEYS)) return false;
|
|
372
|
-
if (!validateKittySharedSafeControls(first.controls)) return false;
|
|
373
|
-
if (first.controls.get("a") === "p") {
|
|
374
|
-
if (packets.length !== 1) return false;
|
|
375
|
-
if (first.payload) return false;
|
|
376
|
-
if (!first.controls.has("i") && !first.controls.has("I")) return false;
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
if (first.controls.get("a") !== "T") return false;
|
|
380
|
-
if (!first.payload) return false;
|
|
381
|
-
const f = first.controls.get("f");
|
|
382
|
-
if (f != null && f !== "24" && f !== "32" && f !== "100") return false;
|
|
383
|
-
const firstMore = first.controls.get("m");
|
|
384
|
-
if (firstMore != null && firstMore !== "0" && firstMore !== "1") return false;
|
|
385
|
-
if (packets.length > 1 && firstMore !== "1") return false;
|
|
386
|
-
for (let i = 1; i < packets.length; i++) {
|
|
387
|
-
const packet = packets[i];
|
|
388
|
-
if (!kittyControlsUseOnly(packet.controls, KITTY_DRAW_CONTINUATION_CONTROL_KEYS)) return false;
|
|
389
|
-
if (!validateKittySharedSafeControls(packet.controls)) return false;
|
|
390
|
-
const action = packet.controls.get("a");
|
|
391
|
-
if (action != null && action !== "T") return false;
|
|
392
|
-
const m = packet.controls.get("m");
|
|
393
|
-
if (m !== "0" && m !== "1") return false;
|
|
394
|
-
if (!packet.payload && !(i === packets.length - 1 && m === "0")) return false;
|
|
395
|
-
}
|
|
396
|
-
if (packets.length > 1 && packets[packets.length - 1].controls.get("m") === "1") return false;
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
function validateIterm2Dimension(value) {
|
|
400
|
-
return /^auto$/i.test(value) || /^(?:[1-9]\d{0,4})(?:px|%)?$/.test(value);
|
|
401
|
-
}
|
|
402
|
-
function validateIterm2Params(params) {
|
|
403
|
-
const seen = new Set();
|
|
404
|
-
let inline = false;
|
|
405
|
-
for (const part of params.split(";").filter(Boolean)) {
|
|
406
|
-
const eq = part.indexOf("=");
|
|
407
|
-
if (eq <= 0) return false;
|
|
408
|
-
const key = part.slice(0, eq);
|
|
409
|
-
const value = part.slice(eq + 1);
|
|
410
|
-
if (seen.has(key)) return false;
|
|
411
|
-
seen.add(key);
|
|
412
|
-
switch (key) {
|
|
413
|
-
case "inline":
|
|
414
|
-
if (value !== "1") return false;
|
|
415
|
-
inline = true;
|
|
416
|
-
break;
|
|
417
|
-
case "width":
|
|
418
|
-
case "height":
|
|
419
|
-
if (!validateIterm2Dimension(value)) return false;
|
|
420
|
-
break;
|
|
421
|
-
case "preserveAspectRatio":
|
|
422
|
-
case "doNotMoveCursor":
|
|
423
|
-
if (!/^[01]$/.test(value)) return false;
|
|
424
|
-
break;
|
|
425
|
-
default: return false;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return inline;
|
|
429
|
-
}
|
|
430
|
-
function validateIterm2Sequence(sequence, op = "draw") {
|
|
431
|
-
if (op === "clear") return false;
|
|
432
|
-
let index = 0;
|
|
433
|
-
while (index < sequence.length) {
|
|
434
|
-
const prefix = `${ESC}]1337;File=`;
|
|
435
|
-
if (!sequence.startsWith(prefix, index)) return false;
|
|
436
|
-
const belEnd = sequence.indexOf(BEL, index + prefix.length);
|
|
437
|
-
const stEnd = sequence.indexOf(ST, index + prefix.length);
|
|
438
|
-
const end = belEnd >= 0 && stEnd >= 0 ? Math.min(belEnd, stEnd) : belEnd >= 0 ? belEnd : stEnd;
|
|
439
|
-
if (end < 0) return false;
|
|
440
|
-
const body = sequence.slice(index + `${ESC}]1337;`.length, end);
|
|
441
|
-
if (!body.startsWith("File=")) return false;
|
|
442
|
-
if (!withoutNestedEscapes(body)) return false;
|
|
443
|
-
const colon = body.indexOf(":");
|
|
444
|
-
if (colon < 0) return false;
|
|
445
|
-
const params = body.slice(5, colon);
|
|
446
|
-
const payload = body.slice(colon + 1);
|
|
447
|
-
if (!/^[A-Za-z0-9_=;,.%+\-:]*$/.test(params)) return false;
|
|
448
|
-
if (!payload || !isBase64ish(payload)) return false;
|
|
449
|
-
if (!validateIterm2Params(params)) return false;
|
|
450
|
-
index = end + (end === stEnd ? ST.length : BEL.length);
|
|
451
|
-
}
|
|
452
|
-
return sequence.length > 0;
|
|
453
|
-
}
|
|
454
|
-
function validateSixelSequence(sequence, op = "draw") {
|
|
455
|
-
if (op === "clear") return false;
|
|
456
|
-
let index = 0;
|
|
457
|
-
while (index < sequence.length) {
|
|
458
|
-
if (!sequence.startsWith(`${ESC}P`, index)) return false;
|
|
459
|
-
const end = sequence.indexOf(ST, index + 2);
|
|
460
|
-
if (end < 0) return false;
|
|
461
|
-
const body = sequence.slice(index + 2, end);
|
|
462
|
-
if (!withoutNestedEscapes(body)) return false;
|
|
463
|
-
for (let i = 0; i < body.length; i++) {
|
|
464
|
-
const code = body.charCodeAt(i);
|
|
465
|
-
if (code < 32 || code > 126) return false;
|
|
466
|
-
}
|
|
467
|
-
const q = body.indexOf("q");
|
|
468
|
-
if (q < 0 || q > 32) return false;
|
|
469
|
-
const introducer = body.slice(0, q);
|
|
470
|
-
if (!/^[0-9;?]*$/.test(introducer)) return false;
|
|
471
|
-
const data = body.slice(q + 1);
|
|
472
|
-
if (!data.length) return false;
|
|
473
|
-
index = end + ST.length;
|
|
474
|
-
}
|
|
475
|
-
return sequence.length > 0;
|
|
476
|
-
}
|
|
477
|
-
function stripTerminalControlChars(value) {
|
|
478
|
-
let out = "";
|
|
479
|
-
for (let index = 0; index < value.length; index++) {
|
|
480
|
-
const code = value.charCodeAt(index);
|
|
481
|
-
if (code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code === 127 || code >= 128 && code <= 159) continue;
|
|
482
|
-
out += value[index];
|
|
483
|
-
}
|
|
484
|
-
return out;
|
|
485
|
-
}
|
|
486
|
-
function sanitizeTerminalFallbackText(text) {
|
|
487
|
-
return stripTerminalControlChars(String(text ?? "").replace(OSC_RE, "").replace(CSI_RE, "").replace(DCS_RE, "").replace(APC_RE, "").replace(C1_OSC_RE, "").replace(C1_CSI_RE, "").replace(C1_DCS_RE, "").replace(C1_APC_RE, "")).slice(0, MAX_TERMINAL_GRAPHICS_FALLBACK_CHARS);
|
|
488
|
-
}
|
|
489
|
-
function isSafeTerminalGraphicsSequence(sequence, protocol, op = "draw") {
|
|
490
|
-
if (!isTerminalGraphicsProtocol(protocol)) return false;
|
|
491
|
-
if (!sequence || sequence.length > MAX_TERMINAL_GRAPHICS_SEQUENCE_CHARS) return false;
|
|
492
|
-
return protocol === "kitty" ? validateKittySequence(sequence, op) : protocol === "iterm2" ? validateIterm2Sequence(sequence, op) : validateSixelSequence(sequence, op);
|
|
493
|
-
}
|
|
494
|
-
function uint32Control(key, value, min = 0) {
|
|
495
|
-
if (value == null) return "";
|
|
496
|
-
const n = Math.floor(value);
|
|
497
|
-
return Number.isSafeInteger(n) && n >= min && n <= KITTY_UINT32_MAX ? `${key}=${n}` : "";
|
|
498
|
-
}
|
|
499
|
-
function int32Control(key, value) {
|
|
500
|
-
if (value == null) return "";
|
|
501
|
-
const n = Math.floor(value);
|
|
502
|
-
return Number.isSafeInteger(n) && n >= KITTY_INT32_MIN && n <= KITTY_INT32_MAX ? `${key}=${n}` : "";
|
|
503
|
-
}
|
|
504
|
-
function sanitizeBase64(value) {
|
|
505
|
-
const data = String(value ?? "").replace(/\s+/g, "");
|
|
506
|
-
return isBase64ish(data) ? data : "";
|
|
507
|
-
}
|
|
508
|
-
function kittyGraphicCellControls(columns, rows) {
|
|
509
|
-
if (columns == null && rows == null) return [];
|
|
510
|
-
const size = normalizeTerminalGraphicSize(columns ?? 1, rows ?? 1);
|
|
511
|
-
if (!size) return [];
|
|
512
|
-
return [columns == null ? "" : `c=${size.width}`, rows == null ? "" : `r=${size.height}`];
|
|
513
|
-
}
|
|
514
|
-
function kittyGraphicSourceControls(sourceX, sourceY, sourceWidth, sourceHeight) {
|
|
515
|
-
return [
|
|
516
|
-
uint32Control("x", sourceX),
|
|
517
|
-
uint32Control("y", sourceY),
|
|
518
|
-
uint32Control("w", sourceWidth, 1),
|
|
519
|
-
uint32Control("h", sourceHeight, 1)
|
|
520
|
-
];
|
|
521
|
-
}
|
|
522
|
-
function chunkBase64(value, chunkSize) {
|
|
523
|
-
const normalizedSize = Math.max(4, Math.floor(chunkSize / 4) * 4);
|
|
524
|
-
const chunks = [];
|
|
525
|
-
for (let index = 0; index < value.length; index += normalizedSize) chunks.push(value.slice(index, index + normalizedSize));
|
|
526
|
-
return chunks.length ? chunks : [""];
|
|
527
|
-
}
|
|
528
|
-
function createKittyPlacementSequence(options) {
|
|
529
|
-
const imageId = uint32Control("i", options.imageId);
|
|
530
|
-
const imageNumber = uint32Control("I", options.imageNumber);
|
|
531
|
-
if (!imageId && !imageNumber) return "";
|
|
532
|
-
const controls = [
|
|
533
|
-
"a=p",
|
|
534
|
-
options.quiet === false ? "" : "q=2",
|
|
535
|
-
options.noCursorMove === false ? "" : "C=1",
|
|
536
|
-
imageId,
|
|
537
|
-
imageNumber,
|
|
538
|
-
uint32Control("p", options.placementId),
|
|
539
|
-
...kittyGraphicCellControls(options.columns, options.rows),
|
|
540
|
-
...kittyGraphicSourceControls(options.sourceX, options.sourceY, options.sourceWidth, options.sourceHeight),
|
|
541
|
-
int32Control("z", options.zIndex)
|
|
542
|
-
].filter(Boolean);
|
|
543
|
-
return `${ESC}_G${controls.join(",")}${ST}`;
|
|
544
|
-
}
|
|
545
|
-
function createKittyGraphicsSequence(base64Png, options = {}) {
|
|
546
|
-
const data = sanitizeBase64(base64Png);
|
|
547
|
-
if (!data) return "";
|
|
548
|
-
const chunks = chunkBase64(data, options.chunkSize ?? 4096);
|
|
549
|
-
const baseControls = [
|
|
550
|
-
"a=T",
|
|
551
|
-
"f=100",
|
|
552
|
-
options.quiet === false ? "" : "q=2",
|
|
553
|
-
options.noCursorMove === false ? "" : "C=1",
|
|
554
|
-
uint32Control("i", options.imageId),
|
|
555
|
-
uint32Control("I", options.imageNumber),
|
|
556
|
-
uint32Control("p", options.placementId),
|
|
557
|
-
...kittyGraphicCellControls(options.columns, options.rows),
|
|
558
|
-
...kittyGraphicSourceControls(options.sourceX, options.sourceY, options.sourceWidth, options.sourceHeight),
|
|
559
|
-
int32Control("z", options.zIndex)
|
|
560
|
-
].filter(Boolean);
|
|
561
|
-
return chunks.map((chunk, index) => {
|
|
562
|
-
const last = index === chunks.length - 1;
|
|
563
|
-
const controls = index === 0 ? [...baseControls, chunks.length > 1 ? `m=${last ? 0 : 1}` : ""] : [options.quiet === false ? "" : "q=2", `m=${last ? 0 : 1}`].filter(Boolean);
|
|
564
|
-
return `${ESC}_G${controls.filter(Boolean).join(",")};${chunk}${ST}`;
|
|
565
|
-
}).join("");
|
|
566
|
-
}
|
|
567
|
-
function createKittyDeleteGraphicsSequence(options = {}) {
|
|
568
|
-
const controls = ["a=d"];
|
|
569
|
-
if (options.allVisible) controls.push(`d=${options.freeImageData ? "A" : "a"}`);
|
|
570
|
-
else if (options.imageId != null) {
|
|
571
|
-
const imageId = uint32Control("i", options.imageId);
|
|
572
|
-
const placementId = uint32Control("p", options.placementId);
|
|
573
|
-
if (!imageId || options.placementId != null && !placementId) return "";
|
|
574
|
-
controls.push(`d=${options.freeImageData ? "I" : "i"}`);
|
|
575
|
-
controls.push(imageId);
|
|
576
|
-
controls.push(placementId);
|
|
577
|
-
} else if (options.currentCell) controls.push(`d=${options.freeImageData ? "C" : "c"}`);
|
|
578
|
-
else return "";
|
|
579
|
-
if (options.quiet !== false) controls.push("q=2");
|
|
580
|
-
return `${ESC}_G${controls.filter(Boolean).join(",")}${ST}`;
|
|
581
|
-
}
|
|
582
|
-
function createIterm2InlineImageSequence(base64Data, options = {}) {
|
|
583
|
-
const data = sanitizeBase64(base64Data);
|
|
584
|
-
if (!data) return "";
|
|
585
|
-
const params = [
|
|
586
|
-
"inline=1",
|
|
587
|
-
options.width != null ? `width=${formatItermDimension(options.width)}` : "",
|
|
588
|
-
options.height != null ? `height=${formatItermDimension(options.height)}` : "",
|
|
589
|
-
`preserveAspectRatio=${options.preserveAspectRatio === false ? 0 : 1}`,
|
|
590
|
-
options.doNotMoveCursor === false ? "" : "doNotMoveCursor=1"
|
|
591
|
-
].filter(Boolean);
|
|
592
|
-
return `${ESC}]1337;File=${params.join(";")}:${data}${BEL}`;
|
|
593
|
-
}
|
|
594
|
-
function formatItermDimension(value) {
|
|
595
|
-
if (typeof value === "number") {
|
|
596
|
-
const n = Math.floor(value);
|
|
597
|
-
if (!Number.isFinite(n)) return "auto";
|
|
598
|
-
return String(Math.min(99999, Math.max(1, n)));
|
|
599
|
-
}
|
|
600
|
-
const raw = String(value ?? "").trim();
|
|
601
|
-
return validateIterm2Dimension(raw) ? raw : "auto";
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
//#endregion
|
|
605
10
|
//#region src/vue/markdown/image.ts
|
|
606
11
|
const DATA_IMAGE_RE = /^data:(image\/[A-Za-z0-9.+-]+);base64,([A-Za-z0-9+/=\s]+)$/i;
|
|
607
12
|
const SAFE_IMAGE_MIME_RE = /^image\/(?:png|jpeg|jpg|gif|webp)$/i;
|
|
@@ -710,6 +115,29 @@ function readMarkdownImageDimensions(base64) {
|
|
|
710
115
|
|
|
711
116
|
//#endregion
|
|
712
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
|
+
}
|
|
713
141
|
const KATEX_TEXT_OPTIONS = Object.freeze({
|
|
714
142
|
output: "mathml",
|
|
715
143
|
throwOnError: false,
|
|
@@ -838,7 +266,7 @@ function approximateTexToUnicode(tex) {
|
|
|
838
266
|
out = out.replace(/\s+/g, " ").trim();
|
|
839
267
|
return sanitizeInlineText(out.replace(/[<>]/g, ""));
|
|
840
268
|
}
|
|
841
|
-
function canRenderTerminalMath(tex) {
|
|
269
|
+
function canRenderTerminalMath(tex, katex) {
|
|
842
270
|
const source = String(tex ?? "").trim();
|
|
843
271
|
if (!source || source.length > 160) return false;
|
|
844
272
|
if (/\\(?:begin|end|left|right|matrix|cases|array|align|color|href|html|includegraphics|class|style|tag|text|operatorname)\b/.test(source)) return false;
|
|
@@ -857,7 +285,15 @@ function renderMarkdownInlineMathSegment(tex) {
|
|
|
857
285
|
text: "",
|
|
858
286
|
supported: false
|
|
859
287
|
};
|
|
860
|
-
|
|
288
|
+
const katex = loadedKatex;
|
|
289
|
+
if (!katex) {
|
|
290
|
+
loadMarkdownMathRenderer();
|
|
291
|
+
return {
|
|
292
|
+
text: "",
|
|
293
|
+
supported: false
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (!canRenderTerminalMath(source, katex)) return {
|
|
861
297
|
text: "",
|
|
862
298
|
supported: false
|
|
863
299
|
};
|
|
@@ -964,6 +400,15 @@ function stringProp(node, key) {
|
|
|
964
400
|
const value = node[key];
|
|
965
401
|
return typeof value === "string" ? value : "";
|
|
966
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Temporary fallback: stream-markdown-parser only recovers ``
|
|
405
|
+
* destinations (Windows drive letters, fixed in 1.1.8). Unix-style `file:///...`
|
|
406
|
+
* destinations at inline start are still emitted as `text("!") + link`. Remove this
|
|
407
|
+
* once the parser covers all `file://` destinations.
|
|
408
|
+
*/
|
|
409
|
+
function isUnixFileUrlNeedingImageFallback(href) {
|
|
410
|
+
return /^file:\/\//i.test(href) && !/^file:\/\/\/[a-z]:\//i.test(href);
|
|
411
|
+
}
|
|
967
412
|
function imageSourceFromRaw(raw) {
|
|
968
413
|
if (!raw.startsWith(";
|
|
@@ -1060,132 +505,150 @@ function codeBlockLanguage(node) {
|
|
|
1060
505
|
}
|
|
1061
506
|
function inlineNodeSegments(nodes, theme, inheritedStyle, options = {}) {
|
|
1062
507
|
const out = [];
|
|
1063
|
-
for (
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
break;
|
|
1080
|
-
case "strikethrough":
|
|
1081
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
|
|
1082
|
-
break;
|
|
1083
|
-
case "highlight":
|
|
1084
|
-
case "insert":
|
|
1085
|
-
case "subscript":
|
|
1086
|
-
case "superscript":
|
|
1087
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
1088
|
-
break;
|
|
1089
|
-
case "inline_code":
|
|
1090
|
-
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
1091
|
-
break;
|
|
1092
|
-
case "hardbreak":
|
|
1093
|
-
out.push(HARD_BREAK_SEGMENT);
|
|
1094
|
-
break;
|
|
1095
|
-
case "link": {
|
|
1096
|
-
const href = stringProp(node, "href");
|
|
1097
|
-
const safeHref = href ? sanitizeMarkdownLink(href) : null;
|
|
1098
|
-
const linkStyle = safeHref ? mergeStyle$1(inheritedStyle, {
|
|
1099
|
-
...theme.link,
|
|
1100
|
-
href: safeHref
|
|
1101
|
-
}) : inheritedStyle;
|
|
1102
|
-
const children = nodeChildren(node);
|
|
1103
|
-
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
|
|
1104
|
-
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
1105
|
-
break;
|
|
508
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
509
|
+
let node = nodes[index];
|
|
510
|
+
const next = nodes[index + 1];
|
|
511
|
+
const text = node.type === "text" ? stringProp(node, "content") : "";
|
|
512
|
+
if (node.type === "text" && text.endsWith("!") && stringProp(node, "raw").endsWith("!") && next?.type === "link" && isUnixFileUrlNeedingImageFallback(stringProp(next, "href"))) {
|
|
513
|
+
const src = stringProp(next, "href");
|
|
514
|
+
if (sanitizeMarkdownImageSource(src)) {
|
|
515
|
+
pushTextSegments(out, text.slice(0, -1), inheritedStyle);
|
|
516
|
+
node = {
|
|
517
|
+
type: "image",
|
|
518
|
+
src,
|
|
519
|
+
alt: stringProp(next, "text"),
|
|
520
|
+
raw: `!${stringProp(next, "raw")}`
|
|
521
|
+
};
|
|
522
|
+
index++;
|
|
523
|
+
}
|
|
1106
524
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
525
|
+
switch (node.type) {
|
|
526
|
+
case "text":
|
|
527
|
+
pushTextSegments(out, stringProp(node, "content"), inheritedStyle);
|
|
528
|
+
break;
|
|
529
|
+
case "inline":
|
|
530
|
+
case "paragraph":
|
|
531
|
+
case "heading":
|
|
532
|
+
case "list_item":
|
|
533
|
+
case "blockquote":
|
|
534
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
535
|
+
break;
|
|
536
|
+
case "strong":
|
|
537
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strong), options));
|
|
538
|
+
break;
|
|
539
|
+
case "emphasis":
|
|
540
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.emphasis), options));
|
|
541
|
+
break;
|
|
542
|
+
case "strikethrough":
|
|
543
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, mergeStyle$1(inheritedStyle, theme.strikethrough), options));
|
|
544
|
+
break;
|
|
545
|
+
case "highlight":
|
|
546
|
+
case "insert":
|
|
547
|
+
case "subscript":
|
|
548
|
+
case "superscript":
|
|
549
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
550
|
+
break;
|
|
551
|
+
case "inline_code":
|
|
552
|
+
pushTextSegments(out, stringProp(node, "code"), mergeStyle$1(inheritedStyle, theme.inlineCode));
|
|
553
|
+
break;
|
|
554
|
+
case "hardbreak":
|
|
555
|
+
out.push(HARD_BREAK_SEGMENT);
|
|
556
|
+
break;
|
|
557
|
+
case "link": {
|
|
558
|
+
const href = stringProp(node, "href");
|
|
559
|
+
const safeHref = href ? sanitizeMarkdownLink(href) : null;
|
|
560
|
+
const linkStyle = safeHref ? mergeStyle$1(inheritedStyle, {
|
|
1115
561
|
...theme.link,
|
|
1116
|
-
href:
|
|
1117
|
-
});
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
...
|
|
1140
|
-
...
|
|
1141
|
-
...
|
|
1142
|
-
naturalWidth:
|
|
1143
|
-
naturalHeight:
|
|
562
|
+
href: safeHref
|
|
563
|
+
}) : inheritedStyle;
|
|
564
|
+
const children = nodeChildren(node);
|
|
565
|
+
if (children.length) out.push(...inlineNodeSegments(children, theme, linkStyle, options));
|
|
566
|
+
else pushTextSegments(out, stringProp(node, "text"), linkStyle);
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "image": {
|
|
570
|
+
const raw = stringProp(node, "raw");
|
|
571
|
+
const rawSource = stringProp(node, "src") || imageSourceFromRaw(raw);
|
|
572
|
+
const source = sanitizeMarkdownImageSource(rawSource);
|
|
573
|
+
const alt = stringProp(node, "alt") || imageFallbackText(raw, rawSource, source?.src);
|
|
574
|
+
if (source) {
|
|
575
|
+
const size = options.imageSize;
|
|
576
|
+
const imageLinkStyle = mergeStyle$1(inheritedStyle, {
|
|
577
|
+
...theme.link,
|
|
578
|
+
href: source.src
|
|
579
|
+
});
|
|
580
|
+
const sourceDimensions = readMarkdownImageDimensions(source.base64);
|
|
581
|
+
const graphicBase = {
|
|
582
|
+
kind: "image",
|
|
583
|
+
src: source.src,
|
|
584
|
+
alt,
|
|
585
|
+
...source.mime ? { mime: source.mime } : {},
|
|
586
|
+
...source.base64 ? { base64: source.base64 } : {},
|
|
587
|
+
...sourceDimensions ? {
|
|
588
|
+
naturalWidth: sourceDimensions.width,
|
|
589
|
+
naturalHeight: sourceDimensions.height
|
|
1144
590
|
} : {},
|
|
1145
|
-
...resolveImageDisplaySize(alt, size,
|
|
591
|
+
...resolveImageDisplaySize(alt, size, sourceDimensions?.width, sourceDimensions?.height)
|
|
1146
592
|
};
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
raw,
|
|
1167
|
-
rendered: rendered.supported
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
break;
|
|
1171
|
-
case "reference":
|
|
1172
|
-
case "footnote_reference":
|
|
1173
|
-
case "footnote_anchor":
|
|
1174
|
-
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
1175
|
-
break;
|
|
1176
|
-
case "html_inline":
|
|
1177
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
1178
|
-
break;
|
|
1179
|
-
case "html_block":
|
|
1180
|
-
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
1181
|
-
break;
|
|
1182
|
-
default:
|
|
1183
|
-
if (nodeChildren(node).length) {
|
|
1184
|
-
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
593
|
+
const resolved = normalizeImageResolverResult(options.imageResolver?.(graphicBase));
|
|
594
|
+
const base64 = resolved.base64 || source.base64;
|
|
595
|
+
if (base64) {
|
|
596
|
+
const dimensions = readMarkdownImageDimensions(base64) ?? sourceDimensions;
|
|
597
|
+
const graphic = {
|
|
598
|
+
...graphicBase,
|
|
599
|
+
base64,
|
|
600
|
+
...resolved.originalBase64 ? { originalBase64: resolved.originalBase64 } : {},
|
|
601
|
+
...resolved.mime ? { mime: resolved.mime } : {},
|
|
602
|
+
...resolved.originalMime ? { originalMime: resolved.originalMime } : {},
|
|
603
|
+
...dimensions ? {
|
|
604
|
+
naturalWidth: dimensions.width,
|
|
605
|
+
naturalHeight: dimensions.height
|
|
606
|
+
} : {},
|
|
607
|
+
...resolveImageDisplaySize(alt, size, dimensions?.width, dimensions?.height)
|
|
608
|
+
};
|
|
609
|
+
pushTextSegments(out, alt, imageLinkStyle, graphic);
|
|
610
|
+
} else pushTextSegments(out, alt, imageLinkStyle);
|
|
611
|
+
} else pushTextSegments(out, alt, mergeStyle$1(inheritedStyle, theme.link));
|
|
1185
612
|
break;
|
|
1186
613
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
614
|
+
case "checkbox":
|
|
615
|
+
case "checkbox_input":
|
|
616
|
+
pushTextSegments(out, booleanProp(node, "checked") ? "[x]" : "[ ]", inheritedStyle);
|
|
617
|
+
break;
|
|
618
|
+
case "emoji":
|
|
619
|
+
pushTextSegments(out, stringProp(node, "markup") || stringProp(node, "name"), inheritedStyle);
|
|
620
|
+
break;
|
|
621
|
+
case "math_inline":
|
|
622
|
+
{
|
|
623
|
+
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, {
|
|
627
|
+
source,
|
|
628
|
+
raw,
|
|
629
|
+
rendered: rendered.supported
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
case "reference":
|
|
634
|
+
case "footnote_reference":
|
|
635
|
+
case "footnote_anchor":
|
|
636
|
+
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
637
|
+
break;
|
|
638
|
+
case "html_inline":
|
|
639
|
+
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
640
|
+
break;
|
|
641
|
+
case "html_block":
|
|
642
|
+
pushTextSegments(out, stringProp(node, "content"), mergeStyle$1(inheritedStyle, theme.html));
|
|
643
|
+
break;
|
|
644
|
+
default:
|
|
645
|
+
if (nodeChildren(node).length) {
|
|
646
|
+
out.push(...inlineNodeSegments(nodeChildren(node), theme, inheritedStyle, options));
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
pushTextSegments(out, stringProp(node, "raw"), inheritedStyle);
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
1189
652
|
}
|
|
1190
653
|
return out;
|
|
1191
654
|
}
|
|
@@ -2676,6 +2139,9 @@ const TVirtualMarkdown = defineComponent({
|
|
|
2676
2139
|
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
2677
2140
|
graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
|
|
2678
2141
|
});
|
|
2142
|
+
const unsubscribeMathRenderer = subscribeMarkdownMathRenderer(() => {
|
|
2143
|
+
scheduleRebuild();
|
|
2144
|
+
});
|
|
2679
2145
|
watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
|
|
2680
2146
|
parser.value = markRaw(createTuiMarkdownParser({
|
|
2681
2147
|
streaming: props.streaming,
|
|
@@ -3043,6 +2509,7 @@ const TVirtualMarkdown = defineComponent({
|
|
|
3043
2509
|
alive = false;
|
|
3044
2510
|
rebuildVersion++;
|
|
3045
2511
|
unsubscribeGraphicsOutput();
|
|
2512
|
+
unsubscribeMathRenderer();
|
|
3046
2513
|
clearMarkdownImageGraphics(terminal, fullRect.value);
|
|
3047
2514
|
});
|
|
3048
2515
|
useRenderNode(() => ({
|
|
@@ -3227,4 +2694,4 @@ function createMarkdownBlockSource(options) {
|
|
|
3227
2694
|
}
|
|
3228
2695
|
|
|
3229
2696
|
//#endregion
|
|
3230
|
-
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows,
|
|
2697
|
+
export { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathRenderer, markdownThemeSignature, paintMarkdownVisualRow, subscribeMarkdownMathRenderer };
|