pi2dsh 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -19
- package/README.zh.md +44 -19
- package/dist/build-DsGYAgiT.mjs +4416 -0
- package/dist/build-DsGYAgiT.mjs.map +1 -0
- package/dist/cli.mjs +2 -2
- package/dist/compat/pi-ai.d.mts +195 -2435
- package/dist/compat/pi-ai.d.mts.map +1 -1
- package/dist/compat/pi-ai.mjs +3 -4668
- package/dist/compat/pi-coding-agent.d.mts +333 -14
- package/dist/compat/pi-coding-agent.d.mts.map +1 -1
- package/dist/compat/pi-coding-agent.mjs +2 -2
- package/dist/compat/pi-tui.mjs +1 -3620
- package/dist/credentials-oauth.d.mts +27 -0
- package/dist/credentials-oauth.d.mts.map +1 -0
- package/dist/credentials-oauth.mjs +91 -0
- package/dist/credentials-oauth.mjs.map +1 -0
- package/dist/host.mjs +4 -2
- package/dist/host.mjs.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{mcp-config-jL9w70It.mjs → mcp-config-gqg7GJEE.mjs} +115 -51
- package/dist/mcp-config-gqg7GJEE.mjs.map +1 -0
- package/dist/oauth-bridge-BpPrppjR.mjs +359 -0
- package/dist/oauth-bridge-BpPrppjR.mjs.map +1 -0
- package/dist/pi-ai-CWFlgigJ.mjs +2947 -0
- package/dist/pi-ai-CWFlgigJ.mjs.map +1 -0
- package/dist/pi-coding-agent-Z1hTs61i.mjs +5203 -0
- package/dist/pi-coding-agent-Z1hTs61i.mjs.map +1 -0
- package/dist/pi-tui-5CYLcj-_.mjs +4512 -0
- package/dist/pi-tui-5CYLcj-_.mjs.map +1 -0
- package/dist/pi-types-BLt46jaf.d.mts +2494 -0
- package/dist/pi-types-BLt46jaf.d.mts.map +1 -0
- package/dist/{rolldown-runtime-C2Q2p085.mjs → rolldown-runtime-D-uZhY3_.mjs} +3 -2
- package/dist/{runtime-D84Hv_3m.mjs → runtime-6DefpI6h.mjs} +344 -23
- package/dist/runtime-6DefpI6h.mjs.map +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{source-D7Ir-rPT.mjs → source-0sA5z08z.mjs} +6 -2
- package/dist/source-0sA5z08z.mjs.map +1 -0
- package/package.json +14 -1
- package/dist/compat/pi-ai.mjs.map +0 -1
- package/dist/compat/pi-tui.mjs.map +0 -1
- package/dist/mcp-config-jL9w70It.mjs.map +0 -1
- package/dist/pi-coding-agent-Dsg6_0ua.mjs +0 -2060
- package/dist/pi-coding-agent-Dsg6_0ua.mjs.map +0 -1
- package/dist/pi-tui-utils-CcaVtm-3.mjs +0 -895
- package/dist/pi-tui-utils-CcaVtm-3.mjs.map +0 -1
- package/dist/pi-types-KazmR2O5.d.mts +0 -62
- package/dist/pi-types-KazmR2O5.d.mts.map +0 -1
- package/dist/pi-uuid-Db8ShZsK.mjs +0 -47
- package/dist/pi-uuid-Db8ShZsK.mjs.map +0 -1
- package/dist/runtime-D84Hv_3m.mjs.map +0 -1
- package/dist/source-D7Ir-rPT.mjs.map +0 -1
|
@@ -0,0 +1,4512 @@
|
|
|
1
|
+
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { readdirSync, statSync } from "fs";
|
|
6
|
+
import { basename as basename$1, dirname as dirname$1, join as join$1 } from "path";
|
|
7
|
+
import { eastAsianWidth } from "get-east-asian-width";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
import { homedir as homedir$1 } from "os";
|
|
11
|
+
import { Marked } from "marked";
|
|
12
|
+
//#region src/compat/vendor/pi-tui-utils.ts
|
|
13
|
+
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
14
|
+
const wordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
15
|
+
/**
|
|
16
|
+
* Get the shared grapheme segmenter instance.
|
|
17
|
+
*/
|
|
18
|
+
function getGraphemeSegmenter() {
|
|
19
|
+
return graphemeSegmenter;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get the shared word segmenter instance.
|
|
23
|
+
*/
|
|
24
|
+
function getWordSegmenter() {
|
|
25
|
+
return wordSegmenter;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if a grapheme cluster (after segmentation) could possibly be an RGI emoji.
|
|
29
|
+
* This is a fast heuristic to avoid the expensive rgiEmojiRegex test.
|
|
30
|
+
* The tested Unicode blocks are deliberately broad to account for future
|
|
31
|
+
* Unicode additions.
|
|
32
|
+
*/
|
|
33
|
+
function couldBeEmoji(segment) {
|
|
34
|
+
const cp = segment.codePointAt(0);
|
|
35
|
+
return cp >= 126976 && cp <= 130047 || cp >= 8960 && cp <= 9215 || cp >= 9728 && cp <= 10175 || cp >= 11088 && cp <= 11093 || segment.includes("️") || segment.length > 2;
|
|
36
|
+
}
|
|
37
|
+
const zeroWidthRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Mark}|\p{Surrogate})+$/v;
|
|
38
|
+
const leadingNonPrintingRegex = /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v;
|
|
39
|
+
const nonPrintingCharRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Format}|\p{Mark}|\p{Surrogate})$/v;
|
|
40
|
+
const markCharRegex = /^\p{Mark}$/v;
|
|
41
|
+
const terminalSpacingMarkRegex = /^(?:[\p{Spacing_Mark}--[\u1734\u302E\u302F]]|[\u065F\u0F7F\u102B\u102C\u1031\u1033-\u1035\u1038\u103A-\u103E])+$/v;
|
|
42
|
+
const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
|
|
43
|
+
const WIDTH_CACHE_SIZE = 512;
|
|
44
|
+
const widthCache = /* @__PURE__ */ new Map();
|
|
45
|
+
const cjkBreakRegex = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
|
|
46
|
+
function isPrintableAscii(str) {
|
|
47
|
+
for (let i = 0; i < str.length; i++) {
|
|
48
|
+
const code = str.charCodeAt(i);
|
|
49
|
+
if (code < 32 || code > 126) return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function truncateFragmentToWidth(text, maxWidth) {
|
|
54
|
+
if (maxWidth <= 0 || text.length === 0) return {
|
|
55
|
+
text: "",
|
|
56
|
+
width: 0
|
|
57
|
+
};
|
|
58
|
+
if (isPrintableAscii(text)) {
|
|
59
|
+
const clipped = text.slice(0, maxWidth);
|
|
60
|
+
return {
|
|
61
|
+
text: clipped,
|
|
62
|
+
width: clipped.length
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const hasAnsi = text.includes("\x1B");
|
|
66
|
+
const hasTabs = text.includes(" ");
|
|
67
|
+
if (!hasAnsi && !hasTabs) {
|
|
68
|
+
let result = "";
|
|
69
|
+
let width = 0;
|
|
70
|
+
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
71
|
+
const w = graphemeWidth(segment);
|
|
72
|
+
if (width + w > maxWidth) break;
|
|
73
|
+
result += segment;
|
|
74
|
+
width += w;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
text: result,
|
|
78
|
+
width
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
let result = "";
|
|
82
|
+
let width = 0;
|
|
83
|
+
let i = 0;
|
|
84
|
+
let pendingAnsi = "";
|
|
85
|
+
while (i < text.length) {
|
|
86
|
+
const ansi = extractAnsiCode(text, i);
|
|
87
|
+
if (ansi) {
|
|
88
|
+
pendingAnsi += ansi.code;
|
|
89
|
+
i += ansi.length;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (text[i] === " ") {
|
|
93
|
+
if (width + 3 > maxWidth) break;
|
|
94
|
+
if (pendingAnsi) {
|
|
95
|
+
result += pendingAnsi;
|
|
96
|
+
pendingAnsi = "";
|
|
97
|
+
}
|
|
98
|
+
result += " ";
|
|
99
|
+
width += 3;
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
let end = i;
|
|
104
|
+
while (end < text.length && text[end] !== " ") {
|
|
105
|
+
if (extractAnsiCode(text, end)) break;
|
|
106
|
+
end++;
|
|
107
|
+
}
|
|
108
|
+
for (const { segment } of graphemeSegmenter.segment(text.slice(i, end))) {
|
|
109
|
+
const w = graphemeWidth(segment);
|
|
110
|
+
if (width + w > maxWidth) return {
|
|
111
|
+
text: result,
|
|
112
|
+
width
|
|
113
|
+
};
|
|
114
|
+
if (pendingAnsi) {
|
|
115
|
+
result += pendingAnsi;
|
|
116
|
+
pendingAnsi = "";
|
|
117
|
+
}
|
|
118
|
+
result += segment;
|
|
119
|
+
width += w;
|
|
120
|
+
}
|
|
121
|
+
i = end;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
text: result,
|
|
125
|
+
width
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function finalizeTruncatedResult(prefix, prefixWidth, ellipsis, ellipsisWidth, maxWidth, pad) {
|
|
129
|
+
const reset = "\x1B[0m";
|
|
130
|
+
const hyperlinkClose = getActiveOsc8Close(prefix);
|
|
131
|
+
const visibleWidth = prefixWidth + ellipsisWidth;
|
|
132
|
+
let result;
|
|
133
|
+
if (ellipsis.length > 0) result = `${prefix}${hyperlinkClose}${reset}${ellipsis}${reset}`;
|
|
134
|
+
else result = `${prefix}${hyperlinkClose}${reset}`;
|
|
135
|
+
return pad ? result + " ".repeat(Math.max(0, maxWidth - visibleWidth)) : result;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Calculate the terminal width of a single grapheme cluster.
|
|
139
|
+
* Based on code from the string-width library, but includes a possible-emoji
|
|
140
|
+
* check to avoid running the RGI_Emoji regex unnecessarily.
|
|
141
|
+
*/
|
|
142
|
+
function graphemeWidth(segment) {
|
|
143
|
+
if (segment === " ") return 3;
|
|
144
|
+
if (terminalSpacingMarkRegex.test(segment)) return [...segment].length;
|
|
145
|
+
if (zeroWidthRegex.test(segment)) return 0;
|
|
146
|
+
if (couldBeEmoji(segment) && rgiEmojiRegex.test(segment)) return 2;
|
|
147
|
+
const base = segment.replace(leadingNonPrintingRegex, "");
|
|
148
|
+
const cp = base.codePointAt(0);
|
|
149
|
+
if (cp === void 0) return 0;
|
|
150
|
+
if (cp >= 127462 && cp <= 127487) return 2;
|
|
151
|
+
let width = eastAsianWidth(cp);
|
|
152
|
+
let followsMark = false;
|
|
153
|
+
const chars = [...base];
|
|
154
|
+
for (const char of chars.slice(1)) if (terminalSpacingMarkRegex.test(char)) {
|
|
155
|
+
width += 1;
|
|
156
|
+
followsMark = false;
|
|
157
|
+
} else if (markCharRegex.test(char)) followsMark = true;
|
|
158
|
+
else if (!nonPrintingCharRegex.test(char)) {
|
|
159
|
+
const c = char.codePointAt(0);
|
|
160
|
+
if (followsMark || c >= 65280 && c <= 65519) width += eastAsianWidth(c);
|
|
161
|
+
else if (c === 3635 || c === 3763) width += 1;
|
|
162
|
+
followsMark = false;
|
|
163
|
+
}
|
|
164
|
+
return width;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Calculate the visible width of a string in terminal columns.
|
|
168
|
+
*/
|
|
169
|
+
function visibleWidth(str) {
|
|
170
|
+
if (str.length === 0) return 0;
|
|
171
|
+
if (isPrintableAscii(str)) return str.length;
|
|
172
|
+
const cached = widthCache.get(str);
|
|
173
|
+
if (cached !== void 0) return cached;
|
|
174
|
+
let clean = str;
|
|
175
|
+
if (str.includes(" ")) clean = clean.replace(/\t/g, " ");
|
|
176
|
+
if (clean.includes("\x1B")) {
|
|
177
|
+
let stripped = "";
|
|
178
|
+
let i = 0;
|
|
179
|
+
while (i < clean.length) {
|
|
180
|
+
const ansi = extractAnsiCode(clean, i);
|
|
181
|
+
if (ansi) {
|
|
182
|
+
i += ansi.length;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
stripped += clean[i];
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
clean = stripped;
|
|
189
|
+
}
|
|
190
|
+
let width = 0;
|
|
191
|
+
for (const { segment } of graphemeSegmenter.segment(clean)) width += graphemeWidth(segment);
|
|
192
|
+
if (widthCache.size >= WIDTH_CACHE_SIZE) {
|
|
193
|
+
const firstKey = widthCache.keys().next().value;
|
|
194
|
+
if (firstKey !== void 0) widthCache.delete(firstKey);
|
|
195
|
+
}
|
|
196
|
+
widthCache.set(str, width);
|
|
197
|
+
return width;
|
|
198
|
+
}
|
|
199
|
+
/** Remove ANSI, OSC, and APC control sequences while preserving visible text. */
|
|
200
|
+
function stripTerminalSequences(str) {
|
|
201
|
+
if (!str.includes("\x1B")) return str;
|
|
202
|
+
let result = "";
|
|
203
|
+
let i = 0;
|
|
204
|
+
while (i < str.length) {
|
|
205
|
+
const ansi = extractAnsiCode(str, i);
|
|
206
|
+
if (ansi) {
|
|
207
|
+
i += ansi.length;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
result += str[i];
|
|
211
|
+
i++;
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
/** Return the terminal-cell range occupied by the grapheme at a visible column. */
|
|
216
|
+
function getGraphemeCellRange(line, column) {
|
|
217
|
+
let currentCol = 0;
|
|
218
|
+
let i = 0;
|
|
219
|
+
while (i < line.length) {
|
|
220
|
+
const ansi = extractAnsiCode(line, i);
|
|
221
|
+
if (ansi) {
|
|
222
|
+
i += ansi.length;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
let textEnd = i;
|
|
226
|
+
while (textEnd < line.length && !extractAnsiCode(line, textEnd)) textEnd++;
|
|
227
|
+
for (const { segment } of graphemeSegmenter.segment(line.slice(i, textEnd))) {
|
|
228
|
+
const width = graphemeWidth(segment);
|
|
229
|
+
if (width > 0 && column >= currentCol && column < currentCol + width) return {
|
|
230
|
+
start: currentCol,
|
|
231
|
+
end: currentCol + width
|
|
232
|
+
};
|
|
233
|
+
currentCol += width;
|
|
234
|
+
}
|
|
235
|
+
i = textEnd;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/** Return the OSC 8 hyperlink covering a visible terminal column. */
|
|
239
|
+
function getOsc8LinkAtColumn(line, column) {
|
|
240
|
+
let activeUrl;
|
|
241
|
+
let currentCol = 0;
|
|
242
|
+
let i = 0;
|
|
243
|
+
while (i < line.length) {
|
|
244
|
+
const ansi = extractAnsiCode(line, i);
|
|
245
|
+
if (ansi) {
|
|
246
|
+
const hyperlink = /^\x1b\]8;[^;]*;([^\x07\x1b]*)(?:\x07|\x1b\\)$/.exec(ansi.code);
|
|
247
|
+
if (hyperlink) activeUrl = hyperlink[1] || void 0;
|
|
248
|
+
i += ansi.length;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
let textEnd = i;
|
|
252
|
+
while (textEnd < line.length && !extractAnsiCode(line, textEnd)) textEnd++;
|
|
253
|
+
for (const { segment } of graphemeSegmenter.segment(line.slice(i, textEnd))) {
|
|
254
|
+
const width = segment === " " ? 3 : graphemeWidth(segment);
|
|
255
|
+
if (column >= currentCol && column < currentCol + width) return activeUrl;
|
|
256
|
+
currentCol += width;
|
|
257
|
+
}
|
|
258
|
+
i = textEnd;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Normalize text for terminal output without changing logical editor content.
|
|
263
|
+
* Some terminals render precomposed Thai/Lao AM vowels inconsistently during
|
|
264
|
+
* differential repaint. Their compatibility decompositions have the same cell
|
|
265
|
+
* width but avoid stale-cell artifacts in terminal renderers. Visible tabs are
|
|
266
|
+
* expanded to the fixed width used by layout so terminal tab stops cannot wrap
|
|
267
|
+
* a logical line, while tabs inside terminal string sequences stay untouched.
|
|
268
|
+
*/
|
|
269
|
+
const THAI_LAO_AM_REGEX = /[\u0e33\u0eb3]/;
|
|
270
|
+
const THAI_LAO_AM_GLOBAL_REGEX = /[\u0e33\u0eb3]/g;
|
|
271
|
+
function normalizeTerminalOutput(str) {
|
|
272
|
+
let normalized = str;
|
|
273
|
+
if (THAI_LAO_AM_REGEX.test(normalized)) normalized = normalized.replace(THAI_LAO_AM_GLOBAL_REGEX, (char) => char === "ำ" ? "ํา" : "ໍາ");
|
|
274
|
+
if (!normalized.includes(" ")) return normalized;
|
|
275
|
+
let result = "";
|
|
276
|
+
let i = 0;
|
|
277
|
+
while (i < normalized.length) {
|
|
278
|
+
const ansi = extractAnsiCode(normalized, i);
|
|
279
|
+
if (ansi) {
|
|
280
|
+
result += ansi.code;
|
|
281
|
+
i += ansi.length;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
result += normalized[i] === " " ? " " : normalized[i];
|
|
285
|
+
i++;
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Extract ANSI escape sequences from a string at the given position.
|
|
291
|
+
*/
|
|
292
|
+
function extractAnsiCode(str, pos) {
|
|
293
|
+
if (pos >= str.length || str[pos] !== "\x1B") return null;
|
|
294
|
+
const next = str[pos + 1];
|
|
295
|
+
if (next === "[") {
|
|
296
|
+
let j = pos + 2;
|
|
297
|
+
while (j < str.length && !/[mGKHJ]/.test(str[j])) j++;
|
|
298
|
+
if (j < str.length) return {
|
|
299
|
+
code: str.substring(pos, j + 1),
|
|
300
|
+
length: j + 1 - pos
|
|
301
|
+
};
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
if (next === "]") {
|
|
305
|
+
let j = pos + 2;
|
|
306
|
+
while (j < str.length) {
|
|
307
|
+
if (str[j] === "\x07") return {
|
|
308
|
+
code: str.substring(pos, j + 1),
|
|
309
|
+
length: j + 1 - pos
|
|
310
|
+
};
|
|
311
|
+
if (str[j] === "\x1B" && str[j + 1] === "\\") return {
|
|
312
|
+
code: str.substring(pos, j + 2),
|
|
313
|
+
length: j + 2 - pos
|
|
314
|
+
};
|
|
315
|
+
j++;
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
if (next === "_") {
|
|
320
|
+
let j = pos + 2;
|
|
321
|
+
while (j < str.length) {
|
|
322
|
+
if (str[j] === "\x07") return {
|
|
323
|
+
code: str.substring(pos, j + 1),
|
|
324
|
+
length: j + 1 - pos
|
|
325
|
+
};
|
|
326
|
+
if (str[j] === "\x1B" && str[j + 1] === "\\") return {
|
|
327
|
+
code: str.substring(pos, j + 2),
|
|
328
|
+
length: j + 2 - pos
|
|
329
|
+
};
|
|
330
|
+
j++;
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function parseOsc8Hyperlink(ansiCode) {
|
|
337
|
+
if (!ansiCode.startsWith("\x1B]8;")) return;
|
|
338
|
+
const terminator = ansiCode.endsWith("\x07") ? "\x07" : "\x1B\\";
|
|
339
|
+
const body = ansiCode.slice(4, terminator === "\x07" ? -1 : -2);
|
|
340
|
+
const separatorIndex = body.indexOf(";");
|
|
341
|
+
if (separatorIndex === -1) return;
|
|
342
|
+
const params = body.slice(0, separatorIndex);
|
|
343
|
+
const url = body.slice(separatorIndex + 1);
|
|
344
|
+
if (!url) return null;
|
|
345
|
+
return {
|
|
346
|
+
params,
|
|
347
|
+
url,
|
|
348
|
+
terminator
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function formatOsc8Hyperlink(hyperlink) {
|
|
352
|
+
return `\x1b]8;${hyperlink.params};${hyperlink.url}${hyperlink.terminator}`;
|
|
353
|
+
}
|
|
354
|
+
function formatOsc8Close(terminator) {
|
|
355
|
+
return `\x1b]8;;${terminator}`;
|
|
356
|
+
}
|
|
357
|
+
function getActiveOsc8Close(prefix) {
|
|
358
|
+
if (!prefix.includes("\x1B]8;")) return "";
|
|
359
|
+
let activeHyperlink = null;
|
|
360
|
+
let i = 0;
|
|
361
|
+
while (i < prefix.length) {
|
|
362
|
+
const ansi = extractAnsiCode(prefix, i);
|
|
363
|
+
if (ansi) {
|
|
364
|
+
const hyperlink = parseOsc8Hyperlink(ansi.code);
|
|
365
|
+
if (hyperlink !== void 0) activeHyperlink = hyperlink;
|
|
366
|
+
i += ansi.length;
|
|
367
|
+
} else i++;
|
|
368
|
+
}
|
|
369
|
+
return activeHyperlink ? formatOsc8Close(activeHyperlink.terminator) : "";
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Track active ANSI SGR codes to preserve styling across line breaks.
|
|
373
|
+
*/
|
|
374
|
+
var AnsiCodeTracker = class {
|
|
375
|
+
bold = false;
|
|
376
|
+
dim = false;
|
|
377
|
+
italic = false;
|
|
378
|
+
underline = false;
|
|
379
|
+
blink = false;
|
|
380
|
+
inverse = false;
|
|
381
|
+
hidden = false;
|
|
382
|
+
strikethrough = false;
|
|
383
|
+
fgColor = null;
|
|
384
|
+
bgColor = null;
|
|
385
|
+
activeHyperlink = null;
|
|
386
|
+
process(ansiCode) {
|
|
387
|
+
const hyperlink = parseOsc8Hyperlink(ansiCode);
|
|
388
|
+
if (hyperlink !== void 0) {
|
|
389
|
+
this.activeHyperlink = hyperlink;
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!ansiCode.endsWith("m")) return;
|
|
393
|
+
const match = ansiCode.match(/\x1b\[([\d;]*)m/);
|
|
394
|
+
if (!match) return;
|
|
395
|
+
const params = match[1];
|
|
396
|
+
if (params === "" || params === "0") {
|
|
397
|
+
this.reset();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const parts = params.split(";");
|
|
401
|
+
let i = 0;
|
|
402
|
+
while (i < parts.length) {
|
|
403
|
+
const code = Number.parseInt(parts[i], 10);
|
|
404
|
+
if (code === 38 || code === 48) {
|
|
405
|
+
if (parts[i + 1] === "5" && parts[i + 2] !== void 0) {
|
|
406
|
+
const colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]}`;
|
|
407
|
+
if (code === 38) this.fgColor = colorCode;
|
|
408
|
+
else this.bgColor = colorCode;
|
|
409
|
+
i += 3;
|
|
410
|
+
continue;
|
|
411
|
+
} else if (parts[i + 1] === "2" && parts[i + 4] !== void 0) {
|
|
412
|
+
const colorCode = `${parts[i]};${parts[i + 1]};${parts[i + 2]};${parts[i + 3]};${parts[i + 4]}`;
|
|
413
|
+
if (code === 38) this.fgColor = colorCode;
|
|
414
|
+
else this.bgColor = colorCode;
|
|
415
|
+
i += 5;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
switch (code) {
|
|
420
|
+
case 0:
|
|
421
|
+
this.reset();
|
|
422
|
+
break;
|
|
423
|
+
case 1:
|
|
424
|
+
this.bold = true;
|
|
425
|
+
break;
|
|
426
|
+
case 2:
|
|
427
|
+
this.dim = true;
|
|
428
|
+
break;
|
|
429
|
+
case 3:
|
|
430
|
+
this.italic = true;
|
|
431
|
+
break;
|
|
432
|
+
case 4:
|
|
433
|
+
this.underline = true;
|
|
434
|
+
break;
|
|
435
|
+
case 5:
|
|
436
|
+
this.blink = true;
|
|
437
|
+
break;
|
|
438
|
+
case 7:
|
|
439
|
+
this.inverse = true;
|
|
440
|
+
break;
|
|
441
|
+
case 8:
|
|
442
|
+
this.hidden = true;
|
|
443
|
+
break;
|
|
444
|
+
case 9:
|
|
445
|
+
this.strikethrough = true;
|
|
446
|
+
break;
|
|
447
|
+
case 21:
|
|
448
|
+
this.bold = false;
|
|
449
|
+
break;
|
|
450
|
+
case 22:
|
|
451
|
+
this.bold = false;
|
|
452
|
+
this.dim = false;
|
|
453
|
+
break;
|
|
454
|
+
case 23:
|
|
455
|
+
this.italic = false;
|
|
456
|
+
break;
|
|
457
|
+
case 24:
|
|
458
|
+
this.underline = false;
|
|
459
|
+
break;
|
|
460
|
+
case 25:
|
|
461
|
+
this.blink = false;
|
|
462
|
+
break;
|
|
463
|
+
case 27:
|
|
464
|
+
this.inverse = false;
|
|
465
|
+
break;
|
|
466
|
+
case 28:
|
|
467
|
+
this.hidden = false;
|
|
468
|
+
break;
|
|
469
|
+
case 29:
|
|
470
|
+
this.strikethrough = false;
|
|
471
|
+
break;
|
|
472
|
+
case 39:
|
|
473
|
+
this.fgColor = null;
|
|
474
|
+
break;
|
|
475
|
+
case 49:
|
|
476
|
+
this.bgColor = null;
|
|
477
|
+
break;
|
|
478
|
+
default: if (code >= 30 && code <= 37 || code >= 90 && code <= 97) this.fgColor = String(code);
|
|
479
|
+
else if (code >= 40 && code <= 47 || code >= 100 && code <= 107) this.bgColor = String(code);
|
|
480
|
+
}
|
|
481
|
+
i++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
reset() {
|
|
485
|
+
this.bold = false;
|
|
486
|
+
this.dim = false;
|
|
487
|
+
this.italic = false;
|
|
488
|
+
this.underline = false;
|
|
489
|
+
this.blink = false;
|
|
490
|
+
this.inverse = false;
|
|
491
|
+
this.hidden = false;
|
|
492
|
+
this.strikethrough = false;
|
|
493
|
+
this.fgColor = null;
|
|
494
|
+
this.bgColor = null;
|
|
495
|
+
}
|
|
496
|
+
/** Clear all state for reuse. */
|
|
497
|
+
clear() {
|
|
498
|
+
this.reset();
|
|
499
|
+
this.activeHyperlink = null;
|
|
500
|
+
}
|
|
501
|
+
getActiveCodes() {
|
|
502
|
+
const codes = [];
|
|
503
|
+
if (this.bold) codes.push("1");
|
|
504
|
+
if (this.dim) codes.push("2");
|
|
505
|
+
if (this.italic) codes.push("3");
|
|
506
|
+
if (this.underline) codes.push("4");
|
|
507
|
+
if (this.blink) codes.push("5");
|
|
508
|
+
if (this.inverse) codes.push("7");
|
|
509
|
+
if (this.hidden) codes.push("8");
|
|
510
|
+
if (this.strikethrough) codes.push("9");
|
|
511
|
+
if (this.fgColor) codes.push(this.fgColor);
|
|
512
|
+
if (this.bgColor) codes.push(this.bgColor);
|
|
513
|
+
let result = codes.length > 0 ? `\x1b[${codes.join(";")}m` : "";
|
|
514
|
+
if (this.activeHyperlink) result += formatOsc8Hyperlink(this.activeHyperlink);
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
hasActiveCodes() {
|
|
518
|
+
return this.bold || this.dim || this.italic || this.underline || this.blink || this.inverse || this.hidden || this.strikethrough || this.fgColor !== null || this.bgColor !== null || this.activeHyperlink !== null;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Get reset codes for attributes that need to be turned off at line end.
|
|
522
|
+
* Underline must be closed to prevent bleeding into padding.
|
|
523
|
+
* Active OSC 8 hyperlinks must be closed and re-opened on the next line.
|
|
524
|
+
* Returns empty string if no attributes need closing.
|
|
525
|
+
*/
|
|
526
|
+
getLineEndReset() {
|
|
527
|
+
let result = "";
|
|
528
|
+
if (this.underline) result += "\x1B[24m";
|
|
529
|
+
if (this.activeHyperlink) result += formatOsc8Close(this.activeHyperlink.terminator);
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
function updateTrackerFromText(text, tracker) {
|
|
534
|
+
let i = 0;
|
|
535
|
+
while (i < text.length) {
|
|
536
|
+
const ansiResult = extractAnsiCode(text, i);
|
|
537
|
+
if (ansiResult) {
|
|
538
|
+
tracker.process(ansiResult.code);
|
|
539
|
+
i += ansiResult.length;
|
|
540
|
+
} else i++;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Split text into words while keeping ANSI codes attached.
|
|
545
|
+
*/
|
|
546
|
+
function splitIntoTokensWithAnsi(text) {
|
|
547
|
+
const tokens = [];
|
|
548
|
+
let current = "";
|
|
549
|
+
let pendingAnsi = "";
|
|
550
|
+
let currentKind = null;
|
|
551
|
+
let i = 0;
|
|
552
|
+
const flushCurrent = () => {
|
|
553
|
+
if (!current) return;
|
|
554
|
+
tokens.push(current);
|
|
555
|
+
current = "";
|
|
556
|
+
currentKind = null;
|
|
557
|
+
};
|
|
558
|
+
while (i < text.length) {
|
|
559
|
+
const ansiResult = extractAnsiCode(text, i);
|
|
560
|
+
if (ansiResult) {
|
|
561
|
+
pendingAnsi += ansiResult.code;
|
|
562
|
+
i += ansiResult.length;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
let end = i;
|
|
566
|
+
while (end < text.length && !extractAnsiCode(text, end)) end++;
|
|
567
|
+
for (const { segment } of graphemeSegmenter.segment(text.slice(i, end))) {
|
|
568
|
+
const segmentIsSpace = segment === " ";
|
|
569
|
+
if (!segmentIsSpace && cjkBreakRegex.test(segment)) {
|
|
570
|
+
flushCurrent();
|
|
571
|
+
const token = pendingAnsi + segment;
|
|
572
|
+
pendingAnsi = "";
|
|
573
|
+
tokens.push(token);
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const segmentKind = segmentIsSpace ? "space" : "word";
|
|
577
|
+
if (current && currentKind !== segmentKind) flushCurrent();
|
|
578
|
+
if (pendingAnsi) {
|
|
579
|
+
current += pendingAnsi;
|
|
580
|
+
pendingAnsi = "";
|
|
581
|
+
}
|
|
582
|
+
currentKind = segmentKind;
|
|
583
|
+
current += segment;
|
|
584
|
+
}
|
|
585
|
+
i = end;
|
|
586
|
+
}
|
|
587
|
+
if (pendingAnsi) {
|
|
588
|
+
if (current) current += pendingAnsi;
|
|
589
|
+
else if (tokens.length > 0) tokens[tokens.length - 1] += pendingAnsi;
|
|
590
|
+
else current = pendingAnsi;
|
|
591
|
+
}
|
|
592
|
+
if (current) tokens.push(current);
|
|
593
|
+
return tokens;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Wrap text with ANSI codes preserved.
|
|
597
|
+
*
|
|
598
|
+
* ONLY does word wrapping - NO padding, NO background colors.
|
|
599
|
+
* Returns lines where each line is <= width visible chars.
|
|
600
|
+
* Active ANSI codes are preserved across line breaks.
|
|
601
|
+
*
|
|
602
|
+
* @param text - Text to wrap (may contain ANSI codes and newlines)
|
|
603
|
+
* @param width - Maximum visible width per line
|
|
604
|
+
* @returns Array of wrapped lines (NOT padded to width)
|
|
605
|
+
*/
|
|
606
|
+
function wrapTextWithAnsi(text, width) {
|
|
607
|
+
if (!text) return [""];
|
|
608
|
+
const inputLines = text.split(/\r\n|\r|\n/);
|
|
609
|
+
const result = [];
|
|
610
|
+
const tracker = new AnsiCodeTracker();
|
|
611
|
+
for (const inputLine of inputLines) {
|
|
612
|
+
const wrappedLines = wrapSingleLine((result.length > 0 ? tracker.getActiveCodes() : "") + inputLine, width);
|
|
613
|
+
for (const wrappedLine of wrappedLines) result.push(wrappedLine);
|
|
614
|
+
updateTrackerFromText(inputLine, tracker);
|
|
615
|
+
}
|
|
616
|
+
return result.length > 0 ? result : [""];
|
|
617
|
+
}
|
|
618
|
+
function wrapSingleLine(line, width) {
|
|
619
|
+
if (!line) return [""];
|
|
620
|
+
if (visibleWidth(line) <= width) return [line];
|
|
621
|
+
const wrapped = [];
|
|
622
|
+
const tracker = new AnsiCodeTracker();
|
|
623
|
+
const tokens = splitIntoTokensWithAnsi(line);
|
|
624
|
+
let currentLine = "";
|
|
625
|
+
let currentVisibleLength = 0;
|
|
626
|
+
for (const token of tokens) {
|
|
627
|
+
const tokenVisibleLength = visibleWidth(token);
|
|
628
|
+
const isWhitespace = token.trim() === "";
|
|
629
|
+
if (tokenVisibleLength > width && !isWhitespace) {
|
|
630
|
+
if (currentLine) {
|
|
631
|
+
const lineEndReset = tracker.getLineEndReset();
|
|
632
|
+
if (lineEndReset) currentLine += lineEndReset;
|
|
633
|
+
wrapped.push(currentLine);
|
|
634
|
+
currentLine = "";
|
|
635
|
+
currentVisibleLength = 0;
|
|
636
|
+
}
|
|
637
|
+
const broken = breakLongWord(token, width, tracker);
|
|
638
|
+
for (let i = 0; i < broken.length - 1; i++) wrapped.push(broken[i]);
|
|
639
|
+
currentLine = broken[broken.length - 1];
|
|
640
|
+
currentVisibleLength = visibleWidth(currentLine);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (currentVisibleLength + tokenVisibleLength > width && currentVisibleLength > 0) {
|
|
644
|
+
let lineToWrap = currentLine.trimEnd();
|
|
645
|
+
const lineEndReset = tracker.getLineEndReset();
|
|
646
|
+
if (lineEndReset) lineToWrap += lineEndReset;
|
|
647
|
+
wrapped.push(lineToWrap);
|
|
648
|
+
if (isWhitespace) {
|
|
649
|
+
currentLine = tracker.getActiveCodes();
|
|
650
|
+
currentVisibleLength = 0;
|
|
651
|
+
} else {
|
|
652
|
+
currentLine = tracker.getActiveCodes() + token;
|
|
653
|
+
currentVisibleLength = tokenVisibleLength;
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
currentLine += token;
|
|
657
|
+
currentVisibleLength += tokenVisibleLength;
|
|
658
|
+
}
|
|
659
|
+
updateTrackerFromText(token, tracker);
|
|
660
|
+
}
|
|
661
|
+
if (currentLine) wrapped.push(currentLine);
|
|
662
|
+
return wrapped.length > 0 ? wrapped.map((line) => line.trimEnd()) : [""];
|
|
663
|
+
}
|
|
664
|
+
const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
|
|
665
|
+
/**
|
|
666
|
+
* Check if a character is whitespace.
|
|
667
|
+
*/
|
|
668
|
+
function isWhitespaceChar(char) {
|
|
669
|
+
return /\s/.test(char);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Check if a character is punctuation.
|
|
673
|
+
*/
|
|
674
|
+
function isPunctuationChar(char) {
|
|
675
|
+
return PUNCTUATION_REGEX.test(char);
|
|
676
|
+
}
|
|
677
|
+
function breakLongWord(word, width, tracker) {
|
|
678
|
+
const lines = [];
|
|
679
|
+
let currentLine = tracker.getActiveCodes();
|
|
680
|
+
let currentWidth = 0;
|
|
681
|
+
let i = 0;
|
|
682
|
+
const segments = [];
|
|
683
|
+
while (i < word.length) {
|
|
684
|
+
const ansiResult = extractAnsiCode(word, i);
|
|
685
|
+
if (ansiResult) {
|
|
686
|
+
segments.push({
|
|
687
|
+
type: "ansi",
|
|
688
|
+
value: ansiResult.code
|
|
689
|
+
});
|
|
690
|
+
i += ansiResult.length;
|
|
691
|
+
} else {
|
|
692
|
+
let end = i;
|
|
693
|
+
while (end < word.length) {
|
|
694
|
+
if (extractAnsiCode(word, end)) break;
|
|
695
|
+
end++;
|
|
696
|
+
}
|
|
697
|
+
const textPortion = word.slice(i, end);
|
|
698
|
+
for (const seg of graphemeSegmenter.segment(textPortion)) segments.push({
|
|
699
|
+
type: "grapheme",
|
|
700
|
+
value: seg.segment
|
|
701
|
+
});
|
|
702
|
+
i = end;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
for (const seg of segments) {
|
|
706
|
+
if (seg.type === "ansi") {
|
|
707
|
+
currentLine += seg.value;
|
|
708
|
+
tracker.process(seg.value);
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const grapheme = seg.value;
|
|
712
|
+
if (!grapheme) continue;
|
|
713
|
+
const graphemeWidth = visibleWidth(grapheme);
|
|
714
|
+
if (currentWidth + graphemeWidth > width) {
|
|
715
|
+
const lineEndReset = tracker.getLineEndReset();
|
|
716
|
+
if (lineEndReset) currentLine += lineEndReset;
|
|
717
|
+
lines.push(currentLine);
|
|
718
|
+
currentLine = tracker.getActiveCodes();
|
|
719
|
+
currentWidth = 0;
|
|
720
|
+
}
|
|
721
|
+
currentLine += grapheme;
|
|
722
|
+
currentWidth += graphemeWidth;
|
|
723
|
+
}
|
|
724
|
+
if (currentLine) lines.push(currentLine);
|
|
725
|
+
return lines.length > 0 ? lines : [""];
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Apply background color to a line, padding to full width.
|
|
729
|
+
*
|
|
730
|
+
* @param line - Line of text (may contain ANSI codes)
|
|
731
|
+
* @param width - Total width to pad to
|
|
732
|
+
* @param bgFn - Background color function
|
|
733
|
+
* @returns Line with background applied and padded to width
|
|
734
|
+
*/
|
|
735
|
+
function applyBackgroundToLine(line, width, bgFn) {
|
|
736
|
+
const visibleLen = visibleWidth(line);
|
|
737
|
+
const paddingNeeded = Math.max(0, width - visibleLen);
|
|
738
|
+
return bgFn(line + " ".repeat(paddingNeeded));
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Truncate text to fit within a maximum visible width, adding ellipsis if needed.
|
|
742
|
+
* Optionally pad with spaces to reach exactly maxWidth.
|
|
743
|
+
* Properly handles ANSI escape codes (they don't count toward width).
|
|
744
|
+
*
|
|
745
|
+
* @param text - Text to truncate (may contain ANSI codes)
|
|
746
|
+
* @param maxWidth - Maximum visible width
|
|
747
|
+
* @param ellipsis - Ellipsis string to append when truncating (default: "...")
|
|
748
|
+
* @param pad - If true, pad result with spaces to exactly maxWidth (default: false)
|
|
749
|
+
* @returns Truncated text, optionally padded to exactly maxWidth
|
|
750
|
+
*/
|
|
751
|
+
function truncateToWidth(text, maxWidth, ellipsis = "...", pad = false) {
|
|
752
|
+
if (maxWidth <= 0) return "";
|
|
753
|
+
if (text.length === 0) return pad ? " ".repeat(maxWidth) : "";
|
|
754
|
+
const ellipsisWidth = visibleWidth(ellipsis);
|
|
755
|
+
if (ellipsisWidth >= maxWidth) {
|
|
756
|
+
const textWidth = visibleWidth(text);
|
|
757
|
+
if (textWidth <= maxWidth) return pad ? text + " ".repeat(maxWidth - textWidth) : text;
|
|
758
|
+
const clippedEllipsis = truncateFragmentToWidth(ellipsis, maxWidth);
|
|
759
|
+
if (clippedEllipsis.width === 0) return pad ? " ".repeat(maxWidth) : "";
|
|
760
|
+
return finalizeTruncatedResult("", 0, clippedEllipsis.text, clippedEllipsis.width, maxWidth, pad);
|
|
761
|
+
}
|
|
762
|
+
if (isPrintableAscii(text)) {
|
|
763
|
+
if (text.length <= maxWidth) return pad ? text + " ".repeat(maxWidth - text.length) : text;
|
|
764
|
+
const targetWidth = maxWidth - ellipsisWidth;
|
|
765
|
+
return finalizeTruncatedResult(text.slice(0, targetWidth), targetWidth, ellipsis, ellipsisWidth, maxWidth, pad);
|
|
766
|
+
}
|
|
767
|
+
const targetWidth = maxWidth - ellipsisWidth;
|
|
768
|
+
let result = "";
|
|
769
|
+
let pendingAnsi = "";
|
|
770
|
+
let visibleSoFar = 0;
|
|
771
|
+
let keptWidth = 0;
|
|
772
|
+
let keepContiguousPrefix = true;
|
|
773
|
+
let overflowed = false;
|
|
774
|
+
let exhaustedInput = false;
|
|
775
|
+
const hasAnsi = text.includes("\x1B");
|
|
776
|
+
const hasTabs = text.includes(" ");
|
|
777
|
+
if (!hasAnsi && !hasTabs) {
|
|
778
|
+
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
779
|
+
const width = graphemeWidth(segment);
|
|
780
|
+
if (keepContiguousPrefix && keptWidth + width <= targetWidth) {
|
|
781
|
+
result += segment;
|
|
782
|
+
keptWidth += width;
|
|
783
|
+
} else keepContiguousPrefix = false;
|
|
784
|
+
visibleSoFar += width;
|
|
785
|
+
if (visibleSoFar > maxWidth) {
|
|
786
|
+
overflowed = true;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
exhaustedInput = !overflowed;
|
|
791
|
+
} else {
|
|
792
|
+
let i = 0;
|
|
793
|
+
while (i < text.length) {
|
|
794
|
+
const ansi = extractAnsiCode(text, i);
|
|
795
|
+
if (ansi) {
|
|
796
|
+
pendingAnsi += ansi.code;
|
|
797
|
+
i += ansi.length;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (text[i] === " ") {
|
|
801
|
+
if (keepContiguousPrefix && keptWidth + 3 <= targetWidth) {
|
|
802
|
+
if (pendingAnsi) {
|
|
803
|
+
result += pendingAnsi;
|
|
804
|
+
pendingAnsi = "";
|
|
805
|
+
}
|
|
806
|
+
result += " ";
|
|
807
|
+
keptWidth += 3;
|
|
808
|
+
} else {
|
|
809
|
+
keepContiguousPrefix = false;
|
|
810
|
+
pendingAnsi = "";
|
|
811
|
+
}
|
|
812
|
+
visibleSoFar += 3;
|
|
813
|
+
if (visibleSoFar > maxWidth) {
|
|
814
|
+
overflowed = true;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
i++;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
let end = i;
|
|
821
|
+
while (end < text.length && text[end] !== " ") {
|
|
822
|
+
if (extractAnsiCode(text, end)) break;
|
|
823
|
+
end++;
|
|
824
|
+
}
|
|
825
|
+
for (const { segment } of graphemeSegmenter.segment(text.slice(i, end))) {
|
|
826
|
+
const width = graphemeWidth(segment);
|
|
827
|
+
if (keepContiguousPrefix && keptWidth + width <= targetWidth) {
|
|
828
|
+
if (pendingAnsi) {
|
|
829
|
+
result += pendingAnsi;
|
|
830
|
+
pendingAnsi = "";
|
|
831
|
+
}
|
|
832
|
+
result += segment;
|
|
833
|
+
keptWidth += width;
|
|
834
|
+
} else {
|
|
835
|
+
keepContiguousPrefix = false;
|
|
836
|
+
pendingAnsi = "";
|
|
837
|
+
}
|
|
838
|
+
visibleSoFar += width;
|
|
839
|
+
if (visibleSoFar > maxWidth) {
|
|
840
|
+
overflowed = true;
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (overflowed) break;
|
|
845
|
+
i = end;
|
|
846
|
+
}
|
|
847
|
+
exhaustedInput = i >= text.length;
|
|
848
|
+
}
|
|
849
|
+
if (!overflowed && exhaustedInput) return pad ? text + " ".repeat(Math.max(0, maxWidth - visibleSoFar)) : text;
|
|
850
|
+
return finalizeTruncatedResult(result, keptWidth, ellipsis, ellipsisWidth, maxWidth, pad);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Extract a range of visible columns from a line. Handles ANSI codes and wide chars.
|
|
854
|
+
* @param strict - If true, exclude wide chars at boundary that would extend past the range
|
|
855
|
+
*/
|
|
856
|
+
function sliceByColumn(line, startCol, length, strict = false) {
|
|
857
|
+
return sliceWithWidth(line, startCol, length, strict).text;
|
|
858
|
+
}
|
|
859
|
+
/** Like sliceByColumn but also returns the actual visible width of the result. */
|
|
860
|
+
function sliceWithWidth(line, startCol, length, strict = false) {
|
|
861
|
+
if (length <= 0) return {
|
|
862
|
+
text: "",
|
|
863
|
+
width: 0
|
|
864
|
+
};
|
|
865
|
+
const endCol = startCol + length;
|
|
866
|
+
let result = "", resultWidth = 0, currentCol = 0, i = 0, pendingAnsi = "";
|
|
867
|
+
while (i < line.length) {
|
|
868
|
+
const ansi = extractAnsiCode(line, i);
|
|
869
|
+
if (ansi) {
|
|
870
|
+
if (currentCol >= startCol && currentCol < endCol) result += ansi.code;
|
|
871
|
+
else if (currentCol < startCol) pendingAnsi += ansi.code;
|
|
872
|
+
i += ansi.length;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
let textEnd = i;
|
|
876
|
+
while (textEnd < line.length && !extractAnsiCode(line, textEnd)) textEnd++;
|
|
877
|
+
for (const { segment } of graphemeSegmenter.segment(line.slice(i, textEnd))) {
|
|
878
|
+
const w = graphemeWidth(segment);
|
|
879
|
+
const inRange = currentCol >= startCol && currentCol < endCol;
|
|
880
|
+
const fits = !strict || currentCol + w <= endCol;
|
|
881
|
+
if (inRange && fits) {
|
|
882
|
+
if (pendingAnsi) {
|
|
883
|
+
result += pendingAnsi;
|
|
884
|
+
pendingAnsi = "";
|
|
885
|
+
}
|
|
886
|
+
result += segment;
|
|
887
|
+
resultWidth += w;
|
|
888
|
+
}
|
|
889
|
+
currentCol += w;
|
|
890
|
+
if (currentCol >= endCol) break;
|
|
891
|
+
}
|
|
892
|
+
i = textEnd;
|
|
893
|
+
if (currentCol >= endCol) break;
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
text: result,
|
|
897
|
+
width: resultWidth
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
new AnsiCodeTracker();
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region src/compat/vendor/pi-tui-fuzzy.ts
|
|
903
|
+
function fuzzyMatch(query, text) {
|
|
904
|
+
const queryLower = query.toLowerCase();
|
|
905
|
+
const textLower = text.toLowerCase();
|
|
906
|
+
const matchQuery = (normalizedQuery) => {
|
|
907
|
+
if (normalizedQuery.length === 0) return {
|
|
908
|
+
matches: true,
|
|
909
|
+
score: 0
|
|
910
|
+
};
|
|
911
|
+
if (normalizedQuery.length > textLower.length) return {
|
|
912
|
+
matches: false,
|
|
913
|
+
score: 0
|
|
914
|
+
};
|
|
915
|
+
let queryIndex = 0;
|
|
916
|
+
let score = 0;
|
|
917
|
+
let lastMatchIndex = -1;
|
|
918
|
+
let consecutiveMatches = 0;
|
|
919
|
+
for (let i = 0; i < textLower.length && queryIndex < normalizedQuery.length; i++) if (textLower[i] === normalizedQuery[queryIndex]) {
|
|
920
|
+
const isWordBoundary = i === 0 || /[\s\-_./:]/.test(textLower[i - 1]);
|
|
921
|
+
if (lastMatchIndex === i - 1) {
|
|
922
|
+
consecutiveMatches++;
|
|
923
|
+
score -= consecutiveMatches * 5;
|
|
924
|
+
} else {
|
|
925
|
+
consecutiveMatches = 0;
|
|
926
|
+
if (lastMatchIndex >= 0) score += (i - lastMatchIndex - 1) * 2;
|
|
927
|
+
}
|
|
928
|
+
if (isWordBoundary) score -= 10;
|
|
929
|
+
score += i * .1;
|
|
930
|
+
lastMatchIndex = i;
|
|
931
|
+
queryIndex++;
|
|
932
|
+
}
|
|
933
|
+
if (queryIndex < normalizedQuery.length) return {
|
|
934
|
+
matches: false,
|
|
935
|
+
score: 0
|
|
936
|
+
};
|
|
937
|
+
if (normalizedQuery === textLower) score -= 100;
|
|
938
|
+
return {
|
|
939
|
+
matches: true,
|
|
940
|
+
score
|
|
941
|
+
};
|
|
942
|
+
};
|
|
943
|
+
const primaryMatch = matchQuery(queryLower);
|
|
944
|
+
if (primaryMatch.matches) return primaryMatch;
|
|
945
|
+
const alphaNumericMatch = queryLower.match(/^(?<letters>[a-z]+)(?<digits>[0-9]+)$/);
|
|
946
|
+
const numericAlphaMatch = queryLower.match(/^(?<digits>[0-9]+)(?<letters>[a-z]+)$/);
|
|
947
|
+
const swappedQuery = alphaNumericMatch ? `${alphaNumericMatch.groups?.digits ?? ""}${alphaNumericMatch.groups?.letters ?? ""}` : numericAlphaMatch ? `${numericAlphaMatch.groups?.letters ?? ""}${numericAlphaMatch.groups?.digits ?? ""}` : "";
|
|
948
|
+
if (!swappedQuery) return primaryMatch;
|
|
949
|
+
const swappedMatch = matchQuery(swappedQuery);
|
|
950
|
+
if (!swappedMatch.matches) return primaryMatch;
|
|
951
|
+
return {
|
|
952
|
+
matches: true,
|
|
953
|
+
score: swappedMatch.score + 5
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Filter and sort items by fuzzy match quality (best matches first).
|
|
958
|
+
* Supports whitespace- and slash-separated tokens: all tokens must match.
|
|
959
|
+
*/
|
|
960
|
+
function fuzzyFilter(items, query, getText) {
|
|
961
|
+
if (!query.trim()) return items;
|
|
962
|
+
const tokens = query.trim().split(/[\s/]+/).filter((t) => t.length > 0);
|
|
963
|
+
if (tokens.length === 0) return items;
|
|
964
|
+
const results = [];
|
|
965
|
+
for (const item of items) {
|
|
966
|
+
const text = getText(item);
|
|
967
|
+
let totalScore = 0;
|
|
968
|
+
let allMatch = true;
|
|
969
|
+
for (const token of tokens) {
|
|
970
|
+
const match = fuzzyMatch(token, text);
|
|
971
|
+
if (match.matches) totalScore += match.score;
|
|
972
|
+
else {
|
|
973
|
+
allMatch = false;
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (allMatch) results.push({
|
|
978
|
+
item,
|
|
979
|
+
totalScore
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
results.sort((a, b) => a.totalScore - b.totalScore);
|
|
983
|
+
return results.map((r) => r.item);
|
|
984
|
+
}
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/compat/vendor/pi-tui-keys.ts
|
|
987
|
+
/**
|
|
988
|
+
* Keyboard input handling for terminal applications.
|
|
989
|
+
*
|
|
990
|
+
* Supports both legacy terminal sequences and Kitty keyboard protocol.
|
|
991
|
+
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
992
|
+
* Reference: https://github.com/sst/opentui/blob/7da92b4088aebfe27b9f691c04163a48821e49fd/packages/core/src/lib/parse.keypress.ts
|
|
993
|
+
*
|
|
994
|
+
* Symbol keys are also supported, however some ctrl+symbol combos
|
|
995
|
+
* overlap with ASCII codes, e.g. ctrl+[ = ESC.
|
|
996
|
+
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys
|
|
997
|
+
* Those can still be * used for ctrl+shift combos
|
|
998
|
+
*
|
|
999
|
+
* API:
|
|
1000
|
+
* - matchesKey(data, keyId) - Check if input matches a key identifier
|
|
1001
|
+
* - parseKey(data) - Parse input and return the key identifier
|
|
1002
|
+
* - Key - Helper object for creating typed key identifiers
|
|
1003
|
+
* - setKittyProtocolActive(active) - Set global Kitty protocol state
|
|
1004
|
+
* - isKittyProtocolActive() - Query global Kitty protocol state
|
|
1005
|
+
*/
|
|
1006
|
+
let _kittyProtocolActive = false;
|
|
1007
|
+
/**
|
|
1008
|
+
* Set the global Kitty keyboard protocol state.
|
|
1009
|
+
* Called by ProcessTerminal after detecting protocol support.
|
|
1010
|
+
*/
|
|
1011
|
+
function setKittyProtocolActive(active) {
|
|
1012
|
+
_kittyProtocolActive = active;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Query whether Kitty keyboard protocol is currently active.
|
|
1016
|
+
*/
|
|
1017
|
+
function isKittyProtocolActive() {
|
|
1018
|
+
return _kittyProtocolActive;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Helper object for creating typed key identifiers with autocomplete.
|
|
1022
|
+
*
|
|
1023
|
+
* Usage:
|
|
1024
|
+
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
|
1025
|
+
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
|
1026
|
+
* - Key.ctrl("c"), Key.alt("x"), Key.super("k") for single modifiers
|
|
1027
|
+
* - Key.ctrlShift("p"), Key.ctrlAlt("x"), Key.ctrlSuper("k") for combined modifiers
|
|
1028
|
+
*/
|
|
1029
|
+
const Key = {
|
|
1030
|
+
escape: "escape",
|
|
1031
|
+
esc: "esc",
|
|
1032
|
+
enter: "enter",
|
|
1033
|
+
return: "return",
|
|
1034
|
+
tab: "tab",
|
|
1035
|
+
space: "space",
|
|
1036
|
+
backspace: "backspace",
|
|
1037
|
+
delete: "delete",
|
|
1038
|
+
insert: "insert",
|
|
1039
|
+
clear: "clear",
|
|
1040
|
+
home: "home",
|
|
1041
|
+
end: "end",
|
|
1042
|
+
pageUp: "pageUp",
|
|
1043
|
+
pageDown: "pageDown",
|
|
1044
|
+
up: "up",
|
|
1045
|
+
down: "down",
|
|
1046
|
+
left: "left",
|
|
1047
|
+
right: "right",
|
|
1048
|
+
f1: "f1",
|
|
1049
|
+
f2: "f2",
|
|
1050
|
+
f3: "f3",
|
|
1051
|
+
f4: "f4",
|
|
1052
|
+
f5: "f5",
|
|
1053
|
+
f6: "f6",
|
|
1054
|
+
f7: "f7",
|
|
1055
|
+
f8: "f8",
|
|
1056
|
+
f9: "f9",
|
|
1057
|
+
f10: "f10",
|
|
1058
|
+
f11: "f11",
|
|
1059
|
+
f12: "f12",
|
|
1060
|
+
backtick: "`",
|
|
1061
|
+
hyphen: "-",
|
|
1062
|
+
equals: "=",
|
|
1063
|
+
leftbracket: "[",
|
|
1064
|
+
rightbracket: "]",
|
|
1065
|
+
backslash: "\\",
|
|
1066
|
+
semicolon: ";",
|
|
1067
|
+
quote: "'",
|
|
1068
|
+
comma: ",",
|
|
1069
|
+
period: ".",
|
|
1070
|
+
slash: "/",
|
|
1071
|
+
exclamation: "!",
|
|
1072
|
+
at: "@",
|
|
1073
|
+
hash: "#",
|
|
1074
|
+
dollar: "$",
|
|
1075
|
+
percent: "%",
|
|
1076
|
+
caret: "^",
|
|
1077
|
+
ampersand: "&",
|
|
1078
|
+
asterisk: "*",
|
|
1079
|
+
leftparen: "(",
|
|
1080
|
+
rightparen: ")",
|
|
1081
|
+
underscore: "_",
|
|
1082
|
+
plus: "+",
|
|
1083
|
+
pipe: "|",
|
|
1084
|
+
tilde: "~",
|
|
1085
|
+
leftbrace: "{",
|
|
1086
|
+
rightbrace: "}",
|
|
1087
|
+
colon: ":",
|
|
1088
|
+
lessthan: "<",
|
|
1089
|
+
greaterthan: ">",
|
|
1090
|
+
question: "?",
|
|
1091
|
+
ctrl: (key) => `ctrl+${key}`,
|
|
1092
|
+
shift: (key) => `shift+${key}`,
|
|
1093
|
+
alt: (key) => `alt+${key}`,
|
|
1094
|
+
super: (key) => `super+${key}`,
|
|
1095
|
+
ctrlShift: (key) => `ctrl+shift+${key}`,
|
|
1096
|
+
shiftCtrl: (key) => `shift+ctrl+${key}`,
|
|
1097
|
+
ctrlAlt: (key) => `ctrl+alt+${key}`,
|
|
1098
|
+
altCtrl: (key) => `alt+ctrl+${key}`,
|
|
1099
|
+
shiftAlt: (key) => `shift+alt+${key}`,
|
|
1100
|
+
altShift: (key) => `alt+shift+${key}`,
|
|
1101
|
+
ctrlSuper: (key) => `ctrl+super+${key}`,
|
|
1102
|
+
superCtrl: (key) => `super+ctrl+${key}`,
|
|
1103
|
+
shiftSuper: (key) => `shift+super+${key}`,
|
|
1104
|
+
superShift: (key) => `super+shift+${key}`,
|
|
1105
|
+
altSuper: (key) => `alt+super+${key}`,
|
|
1106
|
+
superAlt: (key) => `super+alt+${key}`,
|
|
1107
|
+
ctrlShiftAlt: (key) => `ctrl+shift+alt+${key}`,
|
|
1108
|
+
ctrlShiftSuper: (key) => `ctrl+shift+super+${key}`
|
|
1109
|
+
};
|
|
1110
|
+
const SYMBOL_KEYS = /* @__PURE__ */ new Set([
|
|
1111
|
+
"`",
|
|
1112
|
+
"-",
|
|
1113
|
+
"=",
|
|
1114
|
+
"[",
|
|
1115
|
+
"]",
|
|
1116
|
+
"\\",
|
|
1117
|
+
";",
|
|
1118
|
+
"'",
|
|
1119
|
+
",",
|
|
1120
|
+
".",
|
|
1121
|
+
"/",
|
|
1122
|
+
"!",
|
|
1123
|
+
"@",
|
|
1124
|
+
"#",
|
|
1125
|
+
"$",
|
|
1126
|
+
"%",
|
|
1127
|
+
"^",
|
|
1128
|
+
"&",
|
|
1129
|
+
"*",
|
|
1130
|
+
"(",
|
|
1131
|
+
")",
|
|
1132
|
+
"_",
|
|
1133
|
+
"+",
|
|
1134
|
+
"|",
|
|
1135
|
+
"~",
|
|
1136
|
+
"{",
|
|
1137
|
+
"}",
|
|
1138
|
+
":",
|
|
1139
|
+
"<",
|
|
1140
|
+
">",
|
|
1141
|
+
"?"
|
|
1142
|
+
]);
|
|
1143
|
+
const MODIFIERS = {
|
|
1144
|
+
shift: 1,
|
|
1145
|
+
alt: 2,
|
|
1146
|
+
ctrl: 4,
|
|
1147
|
+
super: 8
|
|
1148
|
+
};
|
|
1149
|
+
const LOCK_MASK = 192;
|
|
1150
|
+
const CODEPOINTS = {
|
|
1151
|
+
escape: 27,
|
|
1152
|
+
tab: 9,
|
|
1153
|
+
enter: 13,
|
|
1154
|
+
space: 32,
|
|
1155
|
+
backspace: 127,
|
|
1156
|
+
kpEnter: 57414
|
|
1157
|
+
};
|
|
1158
|
+
const ARROW_CODEPOINTS = {
|
|
1159
|
+
up: -1,
|
|
1160
|
+
down: -2,
|
|
1161
|
+
right: -3,
|
|
1162
|
+
left: -4
|
|
1163
|
+
};
|
|
1164
|
+
const FUNCTIONAL_CODEPOINTS = {
|
|
1165
|
+
delete: -10,
|
|
1166
|
+
insert: -11,
|
|
1167
|
+
pageUp: -12,
|
|
1168
|
+
pageDown: -13,
|
|
1169
|
+
home: -14,
|
|
1170
|
+
end: -15
|
|
1171
|
+
};
|
|
1172
|
+
const KITTY_FUNCTIONAL_KEY_EQUIVALENTS = /* @__PURE__ */ new Map([
|
|
1173
|
+
[57399, 48],
|
|
1174
|
+
[57400, 49],
|
|
1175
|
+
[57401, 50],
|
|
1176
|
+
[57402, 51],
|
|
1177
|
+
[57403, 52],
|
|
1178
|
+
[57404, 53],
|
|
1179
|
+
[57405, 54],
|
|
1180
|
+
[57406, 55],
|
|
1181
|
+
[57407, 56],
|
|
1182
|
+
[57408, 57],
|
|
1183
|
+
[57409, 46],
|
|
1184
|
+
[57410, 47],
|
|
1185
|
+
[57411, 42],
|
|
1186
|
+
[57412, 45],
|
|
1187
|
+
[57413, 43],
|
|
1188
|
+
[57415, 61],
|
|
1189
|
+
[57416, 44],
|
|
1190
|
+
[57417, ARROW_CODEPOINTS.left],
|
|
1191
|
+
[57418, ARROW_CODEPOINTS.right],
|
|
1192
|
+
[57419, ARROW_CODEPOINTS.up],
|
|
1193
|
+
[57420, ARROW_CODEPOINTS.down],
|
|
1194
|
+
[57421, FUNCTIONAL_CODEPOINTS.pageUp],
|
|
1195
|
+
[57422, FUNCTIONAL_CODEPOINTS.pageDown],
|
|
1196
|
+
[57423, FUNCTIONAL_CODEPOINTS.home],
|
|
1197
|
+
[57424, FUNCTIONAL_CODEPOINTS.end],
|
|
1198
|
+
[57425, FUNCTIONAL_CODEPOINTS.insert],
|
|
1199
|
+
[57426, FUNCTIONAL_CODEPOINTS.delete]
|
|
1200
|
+
]);
|
|
1201
|
+
function normalizeKittyFunctionalCodepoint(codepoint) {
|
|
1202
|
+
return KITTY_FUNCTIONAL_KEY_EQUIVALENTS.get(codepoint) ?? codepoint;
|
|
1203
|
+
}
|
|
1204
|
+
function normalizeShiftedLetterIdentityCodepoint(codepoint, modifier) {
|
|
1205
|
+
if ((modifier & -193 & MODIFIERS.shift) !== 0 && codepoint >= 65 && codepoint <= 90) return codepoint + 32;
|
|
1206
|
+
return codepoint;
|
|
1207
|
+
}
|
|
1208
|
+
const LEGACY_KEY_SEQUENCES = {
|
|
1209
|
+
up: ["\x1B[A", "\x1BOA"],
|
|
1210
|
+
down: ["\x1B[B", "\x1BOB"],
|
|
1211
|
+
right: ["\x1B[C", "\x1BOC"],
|
|
1212
|
+
left: ["\x1B[D", "\x1BOD"],
|
|
1213
|
+
home: [
|
|
1214
|
+
"\x1B[H",
|
|
1215
|
+
"\x1BOH",
|
|
1216
|
+
"\x1B[1~",
|
|
1217
|
+
"\x1B[7~"
|
|
1218
|
+
],
|
|
1219
|
+
end: [
|
|
1220
|
+
"\x1B[F",
|
|
1221
|
+
"\x1BOF",
|
|
1222
|
+
"\x1B[4~",
|
|
1223
|
+
"\x1B[8~"
|
|
1224
|
+
],
|
|
1225
|
+
insert: ["\x1B[2~"],
|
|
1226
|
+
delete: ["\x1B[3~"],
|
|
1227
|
+
pageUp: ["\x1B[5~", "\x1B[[5~"],
|
|
1228
|
+
pageDown: ["\x1B[6~", "\x1B[[6~"],
|
|
1229
|
+
clear: ["\x1B[E", "\x1BOE"],
|
|
1230
|
+
f1: [
|
|
1231
|
+
"\x1BOP",
|
|
1232
|
+
"\x1B[11~",
|
|
1233
|
+
"\x1B[[A"
|
|
1234
|
+
],
|
|
1235
|
+
f2: [
|
|
1236
|
+
"\x1BOQ",
|
|
1237
|
+
"\x1B[12~",
|
|
1238
|
+
"\x1B[[B"
|
|
1239
|
+
],
|
|
1240
|
+
f3: [
|
|
1241
|
+
"\x1BOR",
|
|
1242
|
+
"\x1B[13~",
|
|
1243
|
+
"\x1B[[C"
|
|
1244
|
+
],
|
|
1245
|
+
f4: [
|
|
1246
|
+
"\x1BOS",
|
|
1247
|
+
"\x1B[14~",
|
|
1248
|
+
"\x1B[[D"
|
|
1249
|
+
],
|
|
1250
|
+
f5: ["\x1B[15~", "\x1B[[E"],
|
|
1251
|
+
f6: ["\x1B[17~"],
|
|
1252
|
+
f7: ["\x1B[18~"],
|
|
1253
|
+
f8: ["\x1B[19~"],
|
|
1254
|
+
f9: ["\x1B[20~"],
|
|
1255
|
+
f10: ["\x1B[21~"],
|
|
1256
|
+
f11: ["\x1B[23~"],
|
|
1257
|
+
f12: ["\x1B[24~"]
|
|
1258
|
+
};
|
|
1259
|
+
const LEGACY_SHIFT_SEQUENCES = {
|
|
1260
|
+
up: ["\x1B[a"],
|
|
1261
|
+
down: ["\x1B[b"],
|
|
1262
|
+
right: ["\x1B[c"],
|
|
1263
|
+
left: ["\x1B[d"],
|
|
1264
|
+
clear: ["\x1B[e"],
|
|
1265
|
+
insert: ["\x1B[2$"],
|
|
1266
|
+
delete: ["\x1B[3$"],
|
|
1267
|
+
pageUp: ["\x1B[5$"],
|
|
1268
|
+
pageDown: ["\x1B[6$"],
|
|
1269
|
+
home: ["\x1B[7$"],
|
|
1270
|
+
end: ["\x1B[8$"]
|
|
1271
|
+
};
|
|
1272
|
+
const LEGACY_CTRL_SEQUENCES = {
|
|
1273
|
+
up: ["\x1BOa"],
|
|
1274
|
+
down: ["\x1BOb"],
|
|
1275
|
+
right: ["\x1BOc"],
|
|
1276
|
+
left: ["\x1BOd"],
|
|
1277
|
+
clear: ["\x1BOe"],
|
|
1278
|
+
insert: ["\x1B[2^"],
|
|
1279
|
+
delete: ["\x1B[3^"],
|
|
1280
|
+
pageUp: ["\x1B[5^"],
|
|
1281
|
+
pageDown: ["\x1B[6^"],
|
|
1282
|
+
home: ["\x1B[7^"],
|
|
1283
|
+
end: ["\x1B[8^"]
|
|
1284
|
+
};
|
|
1285
|
+
const LEGACY_SEQUENCE_KEY_IDS = {
|
|
1286
|
+
"\x1BOA": "up",
|
|
1287
|
+
"\x1BOB": "down",
|
|
1288
|
+
"\x1BOC": "right",
|
|
1289
|
+
"\x1BOD": "left",
|
|
1290
|
+
"\x1BOH": "home",
|
|
1291
|
+
"\x1BOF": "end",
|
|
1292
|
+
"\x1B[E": "clear",
|
|
1293
|
+
"\x1BOE": "clear",
|
|
1294
|
+
"\x1BOe": "ctrl+clear",
|
|
1295
|
+
"\x1B[e": "shift+clear",
|
|
1296
|
+
"\x1B[2~": "insert",
|
|
1297
|
+
"\x1B[2$": "shift+insert",
|
|
1298
|
+
"\x1B[2^": "ctrl+insert",
|
|
1299
|
+
"\x1B[3$": "shift+delete",
|
|
1300
|
+
"\x1B[3^": "ctrl+delete",
|
|
1301
|
+
"\x1B[[5~": "pageUp",
|
|
1302
|
+
"\x1B[[6~": "pageDown",
|
|
1303
|
+
"\x1B[a": "shift+up",
|
|
1304
|
+
"\x1B[b": "shift+down",
|
|
1305
|
+
"\x1B[c": "shift+right",
|
|
1306
|
+
"\x1B[d": "shift+left",
|
|
1307
|
+
"\x1BOa": "ctrl+up",
|
|
1308
|
+
"\x1BOb": "ctrl+down",
|
|
1309
|
+
"\x1BOc": "ctrl+right",
|
|
1310
|
+
"\x1BOd": "ctrl+left",
|
|
1311
|
+
"\x1B[5$": "shift+pageUp",
|
|
1312
|
+
"\x1B[6$": "shift+pageDown",
|
|
1313
|
+
"\x1B[7$": "shift+home",
|
|
1314
|
+
"\x1B[8$": "shift+end",
|
|
1315
|
+
"\x1B[5^": "ctrl+pageUp",
|
|
1316
|
+
"\x1B[6^": "ctrl+pageDown",
|
|
1317
|
+
"\x1B[7^": "ctrl+home",
|
|
1318
|
+
"\x1B[8^": "ctrl+end",
|
|
1319
|
+
"\x1BOP": "f1",
|
|
1320
|
+
"\x1BOQ": "f2",
|
|
1321
|
+
"\x1BOR": "f3",
|
|
1322
|
+
"\x1BOS": "f4",
|
|
1323
|
+
"\x1B[11~": "f1",
|
|
1324
|
+
"\x1B[12~": "f2",
|
|
1325
|
+
"\x1B[13~": "f3",
|
|
1326
|
+
"\x1B[14~": "f4",
|
|
1327
|
+
"\x1B[[A": "f1",
|
|
1328
|
+
"\x1B[[B": "f2",
|
|
1329
|
+
"\x1B[[C": "f3",
|
|
1330
|
+
"\x1B[[D": "f4",
|
|
1331
|
+
"\x1B[[E": "f5",
|
|
1332
|
+
"\x1B[15~": "f5",
|
|
1333
|
+
"\x1B[17~": "f6",
|
|
1334
|
+
"\x1B[18~": "f7",
|
|
1335
|
+
"\x1B[19~": "f8",
|
|
1336
|
+
"\x1B[20~": "f9",
|
|
1337
|
+
"\x1B[21~": "f10",
|
|
1338
|
+
"\x1B[23~": "f11",
|
|
1339
|
+
"\x1B[24~": "f12",
|
|
1340
|
+
"\x1Bb": "alt+left",
|
|
1341
|
+
"\x1Bf": "alt+right",
|
|
1342
|
+
"\x1Bp": "alt+up",
|
|
1343
|
+
"\x1Bn": "alt+down"
|
|
1344
|
+
};
|
|
1345
|
+
const matchesLegacySequence = (data, sequences) => sequences.includes(data);
|
|
1346
|
+
const matchesLegacyModifierSequence = (data, key, modifier) => {
|
|
1347
|
+
if (modifier === MODIFIERS.shift) return matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);
|
|
1348
|
+
if (modifier === MODIFIERS.ctrl) return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
|
|
1349
|
+
return false;
|
|
1350
|
+
};
|
|
1351
|
+
/**
|
|
1352
|
+
* Check if the last parsed key event was a key release.
|
|
1353
|
+
* Only meaningful when Kitty keyboard protocol with flag 2 is active.
|
|
1354
|
+
*/
|
|
1355
|
+
function isKeyRelease(data) {
|
|
1356
|
+
if (data.includes("\x1B[200~")) return false;
|
|
1357
|
+
if (data.includes(":3u") || data.includes(":3~") || data.includes(":3A") || data.includes(":3B") || data.includes(":3C") || data.includes(":3D") || data.includes(":3H") || data.includes(":3F")) return true;
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Check if the last parsed key event was a key repeat.
|
|
1362
|
+
* Only meaningful when Kitty keyboard protocol with flag 2 is active.
|
|
1363
|
+
*/
|
|
1364
|
+
function isKeyRepeat(data) {
|
|
1365
|
+
if (data.includes("\x1B[200~")) return false;
|
|
1366
|
+
if (data.includes(":2u") || data.includes(":2~") || data.includes(":2A") || data.includes(":2B") || data.includes(":2C") || data.includes(":2D") || data.includes(":2H") || data.includes(":2F")) return true;
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
function parseEventType(eventTypeStr) {
|
|
1370
|
+
if (!eventTypeStr) return "press";
|
|
1371
|
+
const eventType = parseInt(eventTypeStr, 10);
|
|
1372
|
+
if (eventType === 2) return "repeat";
|
|
1373
|
+
if (eventType === 3) return "release";
|
|
1374
|
+
return "press";
|
|
1375
|
+
}
|
|
1376
|
+
function parseKittySequence(data) {
|
|
1377
|
+
const csiUMatch = data.match(/^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/);
|
|
1378
|
+
if (csiUMatch) {
|
|
1379
|
+
const codepoint = parseInt(csiUMatch[1], 10);
|
|
1380
|
+
const shiftedKey = csiUMatch[2] && csiUMatch[2].length > 0 ? parseInt(csiUMatch[2], 10) : void 0;
|
|
1381
|
+
const baseLayoutKey = csiUMatch[3] ? parseInt(csiUMatch[3], 10) : void 0;
|
|
1382
|
+
const modValue = csiUMatch[4] ? parseInt(csiUMatch[4], 10) : 1;
|
|
1383
|
+
const eventType = parseEventType(csiUMatch[5]);
|
|
1384
|
+
return {
|
|
1385
|
+
codepoint,
|
|
1386
|
+
shiftedKey,
|
|
1387
|
+
baseLayoutKey,
|
|
1388
|
+
modifier: modValue - 1,
|
|
1389
|
+
eventType
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
const arrowMatch = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([ABCD])$/);
|
|
1393
|
+
if (arrowMatch) {
|
|
1394
|
+
const modValue = parseInt(arrowMatch[1], 10);
|
|
1395
|
+
const eventType = parseEventType(arrowMatch[2]);
|
|
1396
|
+
return {
|
|
1397
|
+
codepoint: {
|
|
1398
|
+
A: -1,
|
|
1399
|
+
B: -2,
|
|
1400
|
+
C: -3,
|
|
1401
|
+
D: -4
|
|
1402
|
+
}[arrowMatch[3]],
|
|
1403
|
+
modifier: modValue - 1,
|
|
1404
|
+
eventType
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
const funcMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?(?::(\d+))?~$/);
|
|
1408
|
+
if (funcMatch) {
|
|
1409
|
+
const keyNum = parseInt(funcMatch[1], 10);
|
|
1410
|
+
const modValue = funcMatch[2] ? parseInt(funcMatch[2], 10) : 1;
|
|
1411
|
+
const eventType = parseEventType(funcMatch[3]);
|
|
1412
|
+
const codepoint = {
|
|
1413
|
+
2: FUNCTIONAL_CODEPOINTS.insert,
|
|
1414
|
+
3: FUNCTIONAL_CODEPOINTS.delete,
|
|
1415
|
+
5: FUNCTIONAL_CODEPOINTS.pageUp,
|
|
1416
|
+
6: FUNCTIONAL_CODEPOINTS.pageDown,
|
|
1417
|
+
7: FUNCTIONAL_CODEPOINTS.home,
|
|
1418
|
+
8: FUNCTIONAL_CODEPOINTS.end
|
|
1419
|
+
}[keyNum];
|
|
1420
|
+
if (codepoint !== void 0) return {
|
|
1421
|
+
codepoint,
|
|
1422
|
+
modifier: modValue - 1,
|
|
1423
|
+
eventType
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
const homeEndMatch = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([HF])$/);
|
|
1427
|
+
if (homeEndMatch) {
|
|
1428
|
+
const modValue = parseInt(homeEndMatch[1], 10);
|
|
1429
|
+
const eventType = parseEventType(homeEndMatch[2]);
|
|
1430
|
+
return {
|
|
1431
|
+
codepoint: homeEndMatch[3] === "H" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end,
|
|
1432
|
+
modifier: modValue - 1,
|
|
1433
|
+
eventType
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
return null;
|
|
1437
|
+
}
|
|
1438
|
+
function matchesKittySequence(data, expectedCodepoint, expectedModifier) {
|
|
1439
|
+
const parsed = parseKittySequence(data);
|
|
1440
|
+
if (!parsed) return false;
|
|
1441
|
+
if ((parsed.modifier & -193) !== (expectedModifier & -193)) return false;
|
|
1442
|
+
const normalizedCodepoint = normalizeShiftedLetterIdentityCodepoint(normalizeKittyFunctionalCodepoint(parsed.codepoint), parsed.modifier);
|
|
1443
|
+
if (normalizedCodepoint === normalizeShiftedLetterIdentityCodepoint(normalizeKittyFunctionalCodepoint(expectedCodepoint), expectedModifier)) return true;
|
|
1444
|
+
if (parsed.baseLayoutKey !== void 0 && parsed.baseLayoutKey === expectedCodepoint) {
|
|
1445
|
+
const cp = normalizedCodepoint;
|
|
1446
|
+
const isLatinLetter = cp >= 97 && cp <= 122;
|
|
1447
|
+
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(cp));
|
|
1448
|
+
if (!isLatinLetter && !isKnownSymbol) return true;
|
|
1449
|
+
}
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
function parseModifyOtherKeysSequence(data) {
|
|
1453
|
+
const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
|
|
1454
|
+
if (!match) return null;
|
|
1455
|
+
const modValue = parseInt(match[1], 10);
|
|
1456
|
+
return {
|
|
1457
|
+
codepoint: parseInt(match[2], 10),
|
|
1458
|
+
modifier: modValue - 1
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
|
|
1463
|
+
* This is used by terminals when Kitty protocol is not enabled.
|
|
1464
|
+
* Modifier values are 1-indexed: 2=shift, 3=alt, 5=ctrl, etc.
|
|
1465
|
+
*/
|
|
1466
|
+
function matchesModifyOtherKeys(data, expectedKeycode, expectedModifier) {
|
|
1467
|
+
const parsed = parseModifyOtherKeysSequence(data);
|
|
1468
|
+
if (!parsed) return false;
|
|
1469
|
+
return parsed.codepoint === expectedKeycode && parsed.modifier === expectedModifier;
|
|
1470
|
+
}
|
|
1471
|
+
function isWindowsTerminalSession() {
|
|
1472
|
+
return Boolean(process.env.WT_SESSION) && !process.env.SSH_CONNECTION && !process.env.SSH_CLIENT && !process.env.SSH_TTY;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Raw 0x08 (BS) is ambiguous in legacy terminals.
|
|
1476
|
+
*
|
|
1477
|
+
* - Windows Terminal uses it for Ctrl+Backspace.
|
|
1478
|
+
* - Some legacy terminals and tmux setups send it for plain Backspace.
|
|
1479
|
+
*
|
|
1480
|
+
* Prefer explicit Kitty / CSI-u / modifyOtherKeys sequences whenever they are
|
|
1481
|
+
* available. Fall back to a Windows Terminal heuristic only for raw BS bytes.
|
|
1482
|
+
*/
|
|
1483
|
+
function matchesRawBackspace(data, expectedModifier) {
|
|
1484
|
+
if (data === "") return expectedModifier === 0;
|
|
1485
|
+
if (data !== "\b") return false;
|
|
1486
|
+
return isWindowsTerminalSession() ? expectedModifier === MODIFIERS.ctrl : expectedModifier === 0;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Get the control character for a key.
|
|
1490
|
+
* Uses the universal formula: code & 0x1f (mask to lower 5 bits)
|
|
1491
|
+
*
|
|
1492
|
+
* Works for:
|
|
1493
|
+
* - Letters a-z → 1-26
|
|
1494
|
+
* - Symbols [\]_ → 27, 28, 29, 31
|
|
1495
|
+
* - Also maps - to same as _ (same physical key on US keyboards)
|
|
1496
|
+
*/
|
|
1497
|
+
function rawCtrlChar(key) {
|
|
1498
|
+
const char = key.toLowerCase();
|
|
1499
|
+
const code = char.charCodeAt(0);
|
|
1500
|
+
if (code >= 97 && code <= 122 || char === "[" || char === "\\" || char === "]" || char === "_") return String.fromCharCode(code & 31);
|
|
1501
|
+
if (char === "-") return String.fromCharCode(31);
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
function isDigitKey(key) {
|
|
1505
|
+
return key >= "0" && key <= "9";
|
|
1506
|
+
}
|
|
1507
|
+
function matchesPrintableModifyOtherKeys(data, expectedKeycode, expectedModifier) {
|
|
1508
|
+
if (expectedModifier === 0) return false;
|
|
1509
|
+
const parsed = parseModifyOtherKeysSequence(data);
|
|
1510
|
+
if (!parsed || parsed.modifier !== expectedModifier) return false;
|
|
1511
|
+
return normalizeShiftedLetterIdentityCodepoint(parsed.codepoint, parsed.modifier) === normalizeShiftedLetterIdentityCodepoint(expectedKeycode, expectedModifier);
|
|
1512
|
+
}
|
|
1513
|
+
function formatKeyNameWithModifiers(keyName, modifier) {
|
|
1514
|
+
const mods = [];
|
|
1515
|
+
const effectiveMod = modifier & -193;
|
|
1516
|
+
if ((effectiveMod & ~(MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super)) !== 0) return void 0;
|
|
1517
|
+
if (effectiveMod & MODIFIERS.shift) mods.push("shift");
|
|
1518
|
+
if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
|
|
1519
|
+
if (effectiveMod & MODIFIERS.alt) mods.push("alt");
|
|
1520
|
+
if (effectiveMod & MODIFIERS.super) mods.push("super");
|
|
1521
|
+
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
1522
|
+
}
|
|
1523
|
+
function parseKeyId(keyId) {
|
|
1524
|
+
const parts = keyId.toLowerCase().split("+");
|
|
1525
|
+
const key = parts[parts.length - 1];
|
|
1526
|
+
if (!key) return null;
|
|
1527
|
+
return {
|
|
1528
|
+
key,
|
|
1529
|
+
ctrl: parts.includes("ctrl"),
|
|
1530
|
+
shift: parts.includes("shift"),
|
|
1531
|
+
alt: parts.includes("alt"),
|
|
1532
|
+
super: parts.includes("super")
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Match input data against a key identifier string.
|
|
1537
|
+
*
|
|
1538
|
+
* Supported key identifiers:
|
|
1539
|
+
* - Single keys: "escape", "tab", "enter", "backspace", "delete", "home", "end", "space"
|
|
1540
|
+
* - Arrow keys: "up", "down", "left", "right"
|
|
1541
|
+
* - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
|
|
1542
|
+
* - Shift combinations: "shift+tab", "shift+enter"
|
|
1543
|
+
* - Alt combinations: "alt+enter", "alt+backspace"
|
|
1544
|
+
* - Super combinations: "super+k", "super+enter"
|
|
1545
|
+
* - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x", "ctrl+super+k"
|
|
1546
|
+
*
|
|
1547
|
+
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p"), Key.super("k")
|
|
1548
|
+
*
|
|
1549
|
+
* @param data - Raw input data from terminal
|
|
1550
|
+
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
|
1551
|
+
*/
|
|
1552
|
+
function matchesKey(data, keyId) {
|
|
1553
|
+
const parsed = parseKeyId(keyId);
|
|
1554
|
+
if (!parsed) return false;
|
|
1555
|
+
const { key, ctrl, shift, alt, super: superModifier } = parsed;
|
|
1556
|
+
let modifier = 0;
|
|
1557
|
+
if (shift) modifier |= MODIFIERS.shift;
|
|
1558
|
+
if (alt) modifier |= MODIFIERS.alt;
|
|
1559
|
+
if (ctrl) modifier |= MODIFIERS.ctrl;
|
|
1560
|
+
if (superModifier) modifier |= MODIFIERS.super;
|
|
1561
|
+
switch (key) {
|
|
1562
|
+
case "escape":
|
|
1563
|
+
case "esc":
|
|
1564
|
+
if (modifier !== 0) return false;
|
|
1565
|
+
return data === "\x1B" || matchesKittySequence(data, CODEPOINTS.escape, 0) || matchesModifyOtherKeys(data, CODEPOINTS.escape, 0);
|
|
1566
|
+
case "space":
|
|
1567
|
+
if (!_kittyProtocolActive) {
|
|
1568
|
+
if (modifier === MODIFIERS.ctrl && data === "\0") return true;
|
|
1569
|
+
if (modifier === MODIFIERS.alt && data === "\x1B ") return true;
|
|
1570
|
+
}
|
|
1571
|
+
if (modifier === 0) return data === " " || matchesKittySequence(data, CODEPOINTS.space, 0) || matchesModifyOtherKeys(data, CODEPOINTS.space, 0);
|
|
1572
|
+
return matchesKittySequence(data, CODEPOINTS.space, modifier) || matchesModifyOtherKeys(data, CODEPOINTS.space, modifier);
|
|
1573
|
+
case "tab":
|
|
1574
|
+
if (modifier === MODIFIERS.shift) return data === "\x1B[Z" || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) || matchesModifyOtherKeys(data, CODEPOINTS.tab, MODIFIERS.shift);
|
|
1575
|
+
if (modifier === 0) return data === " " || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
1576
|
+
return matchesKittySequence(data, CODEPOINTS.tab, modifier) || matchesModifyOtherKeys(data, CODEPOINTS.tab, modifier);
|
|
1577
|
+
case "enter":
|
|
1578
|
+
case "return":
|
|
1579
|
+
if (modifier === MODIFIERS.shift) {
|
|
1580
|
+
if (matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) || matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.shift)) return true;
|
|
1581
|
+
if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.shift)) return true;
|
|
1582
|
+
if (_kittyProtocolActive) return data === "\x1B\r" || data === "\n";
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
if (modifier === MODIFIERS.alt) {
|
|
1586
|
+
if (matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) || matchesKittySequence(data, CODEPOINTS.kpEnter, MODIFIERS.alt)) return true;
|
|
1587
|
+
if (matchesModifyOtherKeys(data, CODEPOINTS.enter, MODIFIERS.alt)) return true;
|
|
1588
|
+
if (!_kittyProtocolActive) return data === "\x1B\r";
|
|
1589
|
+
return false;
|
|
1590
|
+
}
|
|
1591
|
+
if (modifier === 0) return data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM" || matchesKittySequence(data, CODEPOINTS.enter, 0) || matchesKittySequence(data, CODEPOINTS.kpEnter, 0);
|
|
1592
|
+
return matchesKittySequence(data, CODEPOINTS.enter, modifier) || matchesKittySequence(data, CODEPOINTS.kpEnter, modifier) || matchesModifyOtherKeys(data, CODEPOINTS.enter, modifier);
|
|
1593
|
+
case "backspace":
|
|
1594
|
+
if (modifier === MODIFIERS.alt) {
|
|
1595
|
+
if (data === "\x1B" || data === "\x1B\b") return true;
|
|
1596
|
+
return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt) || matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt);
|
|
1597
|
+
}
|
|
1598
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
1599
|
+
if (matchesRawBackspace(data, MODIFIERS.ctrl)) return true;
|
|
1600
|
+
return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.ctrl) || matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.ctrl);
|
|
1601
|
+
}
|
|
1602
|
+
if (modifier === 0) return matchesRawBackspace(data, 0) || matchesKittySequence(data, CODEPOINTS.backspace, 0) || matchesModifyOtherKeys(data, CODEPOINTS.backspace, 0);
|
|
1603
|
+
return matchesKittySequence(data, CODEPOINTS.backspace, modifier) || matchesModifyOtherKeys(data, CODEPOINTS.backspace, modifier);
|
|
1604
|
+
case "insert":
|
|
1605
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.insert) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, 0);
|
|
1606
|
+
if (matchesLegacyModifierSequence(data, "insert", modifier)) return true;
|
|
1607
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, modifier);
|
|
1608
|
+
case "delete":
|
|
1609
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.delete) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
|
|
1610
|
+
if (matchesLegacyModifierSequence(data, "delete", modifier)) return true;
|
|
1611
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, modifier);
|
|
1612
|
+
case "clear":
|
|
1613
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);
|
|
1614
|
+
return matchesLegacyModifierSequence(data, "clear", modifier);
|
|
1615
|
+
case "home":
|
|
1616
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.home) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0);
|
|
1617
|
+
if (matchesLegacyModifierSequence(data, "home", modifier)) return true;
|
|
1618
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, modifier);
|
|
1619
|
+
case "end":
|
|
1620
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.end) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0);
|
|
1621
|
+
if (matchesLegacyModifierSequence(data, "end", modifier)) return true;
|
|
1622
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, modifier);
|
|
1623
|
+
case "pageup":
|
|
1624
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageUp) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0);
|
|
1625
|
+
if (matchesLegacyModifierSequence(data, "pageUp", modifier)) return true;
|
|
1626
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, modifier);
|
|
1627
|
+
case "pagedown":
|
|
1628
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageDown) || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0);
|
|
1629
|
+
if (matchesLegacyModifierSequence(data, "pageDown", modifier)) return true;
|
|
1630
|
+
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
|
|
1631
|
+
case "up":
|
|
1632
|
+
if (modifier === MODIFIERS.alt) return data === "\x1Bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
|
|
1633
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.up) || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);
|
|
1634
|
+
if (matchesLegacyModifierSequence(data, "up", modifier)) return true;
|
|
1635
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
|
1636
|
+
case "down":
|
|
1637
|
+
if (modifier === MODIFIERS.alt) return data === "\x1Bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
|
|
1638
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.down) || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);
|
|
1639
|
+
if (matchesLegacyModifierSequence(data, "down", modifier)) return true;
|
|
1640
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
|
1641
|
+
case "left":
|
|
1642
|
+
if (modifier === MODIFIERS.alt) return data === "\x1B[1;3D" || !_kittyProtocolActive && data === "\x1BB" || data === "\x1Bb" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);
|
|
1643
|
+
if (modifier === MODIFIERS.ctrl) return data === "\x1B[1;5D" || matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);
|
|
1644
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.left) || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
|
|
1645
|
+
if (matchesLegacyModifierSequence(data, "left", modifier)) return true;
|
|
1646
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
|
1647
|
+
case "right":
|
|
1648
|
+
if (modifier === MODIFIERS.alt) return data === "\x1B[1;3C" || !_kittyProtocolActive && data === "\x1BF" || data === "\x1Bf" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);
|
|
1649
|
+
if (modifier === MODIFIERS.ctrl) return data === "\x1B[1;5C" || matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);
|
|
1650
|
+
if (modifier === 0) return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.right) || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
|
|
1651
|
+
if (matchesLegacyModifierSequence(data, "right", modifier)) return true;
|
|
1652
|
+
return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
|
|
1653
|
+
case "f1":
|
|
1654
|
+
case "f2":
|
|
1655
|
+
case "f3":
|
|
1656
|
+
case "f4":
|
|
1657
|
+
case "f5":
|
|
1658
|
+
case "f6":
|
|
1659
|
+
case "f7":
|
|
1660
|
+
case "f8":
|
|
1661
|
+
case "f9":
|
|
1662
|
+
case "f10":
|
|
1663
|
+
case "f11":
|
|
1664
|
+
case "f12":
|
|
1665
|
+
if (modifier !== 0) return false;
|
|
1666
|
+
return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[key]);
|
|
1667
|
+
}
|
|
1668
|
+
if (key.length === 1 && (key >= "a" && key <= "z" || isDigitKey(key) || SYMBOL_KEYS.has(key))) {
|
|
1669
|
+
const codepoint = key.charCodeAt(0);
|
|
1670
|
+
const rawCtrl = rawCtrlChar(key);
|
|
1671
|
+
const isLetter = key >= "a" && key <= "z";
|
|
1672
|
+
const isDigit = isDigitKey(key);
|
|
1673
|
+
if (modifier === MODIFIERS.ctrl + MODIFIERS.alt && !_kittyProtocolActive && rawCtrl) {
|
|
1674
|
+
if (data === `\x1b${rawCtrl}`) return true;
|
|
1675
|
+
}
|
|
1676
|
+
if (modifier === MODIFIERS.alt && !_kittyProtocolActive && (isLetter || isDigit || SYMBOL_KEYS.has(key))) {
|
|
1677
|
+
if (data === `\x1b${key}`) return true;
|
|
1678
|
+
}
|
|
1679
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
1680
|
+
if (rawCtrl && data === rawCtrl) return true;
|
|
1681
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.ctrl) || matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.ctrl);
|
|
1682
|
+
}
|
|
1683
|
+
if (modifier === MODIFIERS.shift + MODIFIERS.ctrl) return matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) || matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
1684
|
+
if (modifier === MODIFIERS.shift) {
|
|
1685
|
+
if (isLetter && data === key.toUpperCase()) return true;
|
|
1686
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.shift) || matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift);
|
|
1687
|
+
}
|
|
1688
|
+
if (modifier !== 0) return matchesKittySequence(data, codepoint, modifier) || matchesPrintableModifyOtherKeys(data, codepoint, modifier);
|
|
1689
|
+
return data === key || matchesKittySequence(data, codepoint, 0);
|
|
1690
|
+
}
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Parse input data and return the key identifier if recognized.
|
|
1695
|
+
*
|
|
1696
|
+
* @param data - Raw input data from terminal
|
|
1697
|
+
* @returns Key identifier string (e.g., "ctrl+c") or undefined
|
|
1698
|
+
*/
|
|
1699
|
+
function formatParsedKey(codepoint, modifier, baseLayoutKey) {
|
|
1700
|
+
const identityCodepoint = normalizeShiftedLetterIdentityCodepoint(normalizeKittyFunctionalCodepoint(codepoint), modifier);
|
|
1701
|
+
const isLatinLetter = identityCodepoint >= 97 && identityCodepoint <= 122;
|
|
1702
|
+
const isDigit = identityCodepoint >= 48 && identityCodepoint <= 57;
|
|
1703
|
+
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(identityCodepoint));
|
|
1704
|
+
const effectiveCodepoint = isLatinLetter || isDigit || isKnownSymbol ? identityCodepoint : baseLayoutKey ?? identityCodepoint;
|
|
1705
|
+
let keyName;
|
|
1706
|
+
if (effectiveCodepoint === CODEPOINTS.escape) keyName = "escape";
|
|
1707
|
+
else if (effectiveCodepoint === CODEPOINTS.tab) keyName = "tab";
|
|
1708
|
+
else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter) keyName = "enter";
|
|
1709
|
+
else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
|
|
1710
|
+
else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
|
|
1711
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
|
|
1712
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
|
|
1713
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
|
|
1714
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
|
|
1715
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
|
|
1716
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = "pageDown";
|
|
1717
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = "up";
|
|
1718
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = "down";
|
|
1719
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
|
1720
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
|
1721
|
+
else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57) keyName = String.fromCharCode(effectiveCodepoint);
|
|
1722
|
+
else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122) keyName = String.fromCharCode(effectiveCodepoint);
|
|
1723
|
+
else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint))) keyName = String.fromCharCode(effectiveCodepoint);
|
|
1724
|
+
if (!keyName) return void 0;
|
|
1725
|
+
return formatKeyNameWithModifiers(keyName, modifier);
|
|
1726
|
+
}
|
|
1727
|
+
function parseKey(data) {
|
|
1728
|
+
const kitty = parseKittySequence(data);
|
|
1729
|
+
if (kitty) return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
|
|
1730
|
+
const modifyOtherKeys = parseModifyOtherKeysSequence(data);
|
|
1731
|
+
if (modifyOtherKeys) return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
|
|
1732
|
+
if (_kittyProtocolActive) {
|
|
1733
|
+
if (data === "\x1B\r" || data === "\n") return "shift+enter";
|
|
1734
|
+
}
|
|
1735
|
+
const legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];
|
|
1736
|
+
if (legacySequenceKeyId) return legacySequenceKeyId;
|
|
1737
|
+
if (data === "\x1B") return "escape";
|
|
1738
|
+
if (data === "") return "ctrl+\\";
|
|
1739
|
+
if (data === "") return "ctrl+]";
|
|
1740
|
+
if (data === "") return "ctrl+-";
|
|
1741
|
+
if (data === "\x1B\x1B") return "ctrl+alt+[";
|
|
1742
|
+
if (data === "\x1B") return "ctrl+alt+\\";
|
|
1743
|
+
if (data === "\x1B") return "ctrl+alt+]";
|
|
1744
|
+
if (data === "\x1B") return "ctrl+alt+-";
|
|
1745
|
+
if (data === " ") return "tab";
|
|
1746
|
+
if (data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM") return "enter";
|
|
1747
|
+
if (data === "\0") return "ctrl+space";
|
|
1748
|
+
if (data === " ") return "space";
|
|
1749
|
+
if (data === "") return "backspace";
|
|
1750
|
+
if (data === "\b") return isWindowsTerminalSession() ? "ctrl+backspace" : "backspace";
|
|
1751
|
+
if (data === "\x1B[Z") return "shift+tab";
|
|
1752
|
+
if (!_kittyProtocolActive && data === "\x1B\r") return "alt+enter";
|
|
1753
|
+
if (!_kittyProtocolActive && data === "\x1B ") return "alt+space";
|
|
1754
|
+
if (data === "\x1B" || data === "\x1B\b") return "alt+backspace";
|
|
1755
|
+
if (!_kittyProtocolActive && data === "\x1BB") return "alt+left";
|
|
1756
|
+
if (!_kittyProtocolActive && data === "\x1BF") return "alt+right";
|
|
1757
|
+
if (!_kittyProtocolActive && data.length === 2 && data[0] === "\x1B") {
|
|
1758
|
+
const code = data.charCodeAt(1);
|
|
1759
|
+
if (code >= 1 && code <= 26) return `ctrl+alt+${String.fromCharCode(code + 96)}`;
|
|
1760
|
+
const key = String.fromCharCode(code);
|
|
1761
|
+
if (code >= 97 && code <= 122 || code >= 48 && code <= 57 || SYMBOL_KEYS.has(key)) return `alt+${key}`;
|
|
1762
|
+
}
|
|
1763
|
+
if (data === "\x1B[A") return "up";
|
|
1764
|
+
if (data === "\x1B[B") return "down";
|
|
1765
|
+
if (data === "\x1B[C") return "right";
|
|
1766
|
+
if (data === "\x1B[D") return "left";
|
|
1767
|
+
if (data === "\x1B[H" || data === "\x1BOH") return "home";
|
|
1768
|
+
if (data === "\x1B[F" || data === "\x1BOF") return "end";
|
|
1769
|
+
if (data === "\x1B[3~") return "delete";
|
|
1770
|
+
if (data === "\x1B[5~") return "pageUp";
|
|
1771
|
+
if (data === "\x1B[6~") return "pageDown";
|
|
1772
|
+
if (data.length === 1) {
|
|
1773
|
+
const code = data.charCodeAt(0);
|
|
1774
|
+
if (code >= 1 && code <= 26) return `ctrl+${String.fromCharCode(code + 96)}`;
|
|
1775
|
+
if (code >= 32 && code <= 126) return data;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
|
|
1779
|
+
const KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
|
|
1780
|
+
/**
|
|
1781
|
+
* Decode a Kitty CSI-u sequence into a printable character, if applicable.
|
|
1782
|
+
*
|
|
1783
|
+
* When Kitty keyboard protocol flag 1 (disambiguate) is active, terminals send
|
|
1784
|
+
* CSI-u sequences for all keys, including plain printable characters. This
|
|
1785
|
+
* function extracts the printable character from such sequences.
|
|
1786
|
+
*
|
|
1787
|
+
* Only accepts plain or Shift-modified keys. Rejects Ctrl, Alt, and unsupported
|
|
1788
|
+
* modifier combinations (those are handled by keybinding matching instead).
|
|
1789
|
+
* Prefers the shifted keycode when Shift is held and a shifted key is reported.
|
|
1790
|
+
*
|
|
1791
|
+
* @param data - Raw input data from terminal
|
|
1792
|
+
* @returns The printable character, or undefined if not a printable CSI-u sequence
|
|
1793
|
+
*/
|
|
1794
|
+
function decodeKittyPrintable(data) {
|
|
1795
|
+
const match = data.match(KITTY_CSI_U_REGEX);
|
|
1796
|
+
if (!match) return void 0;
|
|
1797
|
+
const codepoint = Number.parseInt(match[1] ?? "", 10);
|
|
1798
|
+
if (!Number.isFinite(codepoint)) return void 0;
|
|
1799
|
+
const shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : void 0;
|
|
1800
|
+
const modValue = match[4] ? Number.parseInt(match[4], 10) : 1;
|
|
1801
|
+
const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
|
|
1802
|
+
if ((modifier & ~KITTY_PRINTABLE_ALLOWED_MODIFIERS) !== 0) return void 0;
|
|
1803
|
+
if (modifier & (MODIFIERS.alt | MODIFIERS.ctrl)) return void 0;
|
|
1804
|
+
let effectiveCodepoint = codepoint;
|
|
1805
|
+
if (modifier & MODIFIERS.shift && typeof shiftedKey === "number") effectiveCodepoint = shiftedKey;
|
|
1806
|
+
effectiveCodepoint = normalizeKittyFunctionalCodepoint(effectiveCodepoint);
|
|
1807
|
+
if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32) return void 0;
|
|
1808
|
+
try {
|
|
1809
|
+
return String.fromCodePoint(effectiveCodepoint);
|
|
1810
|
+
} catch {
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
//#endregion
|
|
1815
|
+
//#region src/compat/vendor/pi-tui-terminal-colors.ts
|
|
1816
|
+
function hexToRgb(hex) {
|
|
1817
|
+
const normalized = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
1818
|
+
return {
|
|
1819
|
+
r: parseInt(normalized.slice(0, 2), 16),
|
|
1820
|
+
g: parseInt(normalized.slice(2, 4), 16),
|
|
1821
|
+
b: parseInt(normalized.slice(4, 6), 16)
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function parseOscHexChannel(channel) {
|
|
1825
|
+
if (!/^[0-9a-f]+$/i.test(channel)) return;
|
|
1826
|
+
const max = 16 ** channel.length - 1;
|
|
1827
|
+
if (max <= 0) return;
|
|
1828
|
+
return Math.round(parseInt(channel, 16) / max * 255);
|
|
1829
|
+
}
|
|
1830
|
+
const OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN = /^\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)$/i;
|
|
1831
|
+
const COLOR_SCHEME_REPORT_PATTERN = /^(?:\x1b\[\?997;(1|2)n)+$/;
|
|
1832
|
+
function parseOsc11BackgroundColor(data) {
|
|
1833
|
+
const match = data.match(OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN);
|
|
1834
|
+
if (!match) return;
|
|
1835
|
+
const value = match[1].trim();
|
|
1836
|
+
if (value.startsWith("#")) {
|
|
1837
|
+
const hex = value.slice(1);
|
|
1838
|
+
if (/^[0-9a-f]{6}$/i.test(hex)) return hexToRgb(value);
|
|
1839
|
+
if (/^[0-9a-f]{12}$/i.test(hex)) {
|
|
1840
|
+
const r = parseOscHexChannel(hex.slice(0, 4));
|
|
1841
|
+
const g = parseOscHexChannel(hex.slice(4, 8));
|
|
1842
|
+
const b = parseOscHexChannel(hex.slice(8, 12));
|
|
1843
|
+
return r !== void 0 && g !== void 0 && b !== void 0 ? {
|
|
1844
|
+
r,
|
|
1845
|
+
g,
|
|
1846
|
+
b
|
|
1847
|
+
} : void 0;
|
|
1848
|
+
}
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
const [red, green, blue] = value.replace(/^rgba?:/i, "").split("/");
|
|
1852
|
+
if (red === void 0 || green === void 0 || blue === void 0) return;
|
|
1853
|
+
const r = parseOscHexChannel(red);
|
|
1854
|
+
const g = parseOscHexChannel(green);
|
|
1855
|
+
const b = parseOscHexChannel(blue);
|
|
1856
|
+
return r !== void 0 && g !== void 0 && b !== void 0 ? {
|
|
1857
|
+
r,
|
|
1858
|
+
g,
|
|
1859
|
+
b
|
|
1860
|
+
} : void 0;
|
|
1861
|
+
}
|
|
1862
|
+
function parseTerminalColorSchemeReport(data) {
|
|
1863
|
+
const match = data.match(COLOR_SCHEME_REPORT_PATTERN);
|
|
1864
|
+
if (!match) return;
|
|
1865
|
+
return match[1] === "2" ? "light" : "dark";
|
|
1866
|
+
}
|
|
1867
|
+
//#endregion
|
|
1868
|
+
//#region src/compat/vendor/pi-tui-keybindings.ts
|
|
1869
|
+
const TUI_KEYBINDINGS = {
|
|
1870
|
+
"tui.editor.cursorUp": {
|
|
1871
|
+
defaultKeys: "up",
|
|
1872
|
+
description: "Move cursor up"
|
|
1873
|
+
},
|
|
1874
|
+
"tui.editor.cursorDown": {
|
|
1875
|
+
defaultKeys: "down",
|
|
1876
|
+
description: "Move cursor down"
|
|
1877
|
+
},
|
|
1878
|
+
"tui.editor.historyPrevious": {
|
|
1879
|
+
defaultKeys: [],
|
|
1880
|
+
description: "Select previous prompt history entry"
|
|
1881
|
+
},
|
|
1882
|
+
"tui.editor.historyNext": {
|
|
1883
|
+
defaultKeys: [],
|
|
1884
|
+
description: "Select next prompt history entry"
|
|
1885
|
+
},
|
|
1886
|
+
"tui.editor.cursorLeft": {
|
|
1887
|
+
defaultKeys: ["left", "ctrl+b"],
|
|
1888
|
+
description: "Move cursor left"
|
|
1889
|
+
},
|
|
1890
|
+
"tui.editor.cursorRight": {
|
|
1891
|
+
defaultKeys: ["right", "ctrl+f"],
|
|
1892
|
+
description: "Move cursor right"
|
|
1893
|
+
},
|
|
1894
|
+
"tui.editor.cursorWordLeft": {
|
|
1895
|
+
defaultKeys: [
|
|
1896
|
+
"alt+left",
|
|
1897
|
+
"ctrl+left",
|
|
1898
|
+
"alt+b"
|
|
1899
|
+
],
|
|
1900
|
+
description: "Move cursor word left"
|
|
1901
|
+
},
|
|
1902
|
+
"tui.editor.cursorWordRight": {
|
|
1903
|
+
defaultKeys: [
|
|
1904
|
+
"alt+right",
|
|
1905
|
+
"ctrl+right",
|
|
1906
|
+
"alt+f"
|
|
1907
|
+
],
|
|
1908
|
+
description: "Move cursor word right"
|
|
1909
|
+
},
|
|
1910
|
+
"tui.editor.cursorLineStart": {
|
|
1911
|
+
defaultKeys: [
|
|
1912
|
+
"home",
|
|
1913
|
+
"ctrl+home",
|
|
1914
|
+
"ctrl+a"
|
|
1915
|
+
],
|
|
1916
|
+
description: "Move to line start"
|
|
1917
|
+
},
|
|
1918
|
+
"tui.editor.cursorLineEnd": {
|
|
1919
|
+
defaultKeys: [
|
|
1920
|
+
"end",
|
|
1921
|
+
"ctrl+end",
|
|
1922
|
+
"ctrl+e"
|
|
1923
|
+
],
|
|
1924
|
+
description: "Move to line end"
|
|
1925
|
+
},
|
|
1926
|
+
"tui.editor.jumpForward": {
|
|
1927
|
+
defaultKeys: "ctrl+]",
|
|
1928
|
+
description: "Jump forward to character"
|
|
1929
|
+
},
|
|
1930
|
+
"tui.editor.jumpBackward": {
|
|
1931
|
+
defaultKeys: "ctrl+alt+]",
|
|
1932
|
+
description: "Jump backward to character"
|
|
1933
|
+
},
|
|
1934
|
+
"tui.editor.pageUp": {
|
|
1935
|
+
defaultKeys: ["pageUp", "ctrl+pageUp"],
|
|
1936
|
+
description: "Page up"
|
|
1937
|
+
},
|
|
1938
|
+
"tui.editor.pageDown": {
|
|
1939
|
+
defaultKeys: ["pageDown", "ctrl+pageDown"],
|
|
1940
|
+
description: "Page down"
|
|
1941
|
+
},
|
|
1942
|
+
"tui.editor.deleteCharBackward": {
|
|
1943
|
+
defaultKeys: "backspace",
|
|
1944
|
+
description: "Delete character backward"
|
|
1945
|
+
},
|
|
1946
|
+
"tui.editor.deleteCharForward": {
|
|
1947
|
+
defaultKeys: ["delete", "ctrl+d"],
|
|
1948
|
+
description: "Delete character forward"
|
|
1949
|
+
},
|
|
1950
|
+
"tui.editor.deleteWordBackward": {
|
|
1951
|
+
defaultKeys: ["ctrl+w", "alt+backspace"],
|
|
1952
|
+
description: "Delete word backward"
|
|
1953
|
+
},
|
|
1954
|
+
"tui.editor.deleteWordForward": {
|
|
1955
|
+
defaultKeys: ["alt+d", "alt+delete"],
|
|
1956
|
+
description: "Delete word forward"
|
|
1957
|
+
},
|
|
1958
|
+
"tui.editor.deleteToLineStart": {
|
|
1959
|
+
defaultKeys: "ctrl+u",
|
|
1960
|
+
description: "Delete to line start"
|
|
1961
|
+
},
|
|
1962
|
+
"tui.editor.deleteToLineEnd": {
|
|
1963
|
+
defaultKeys: "ctrl+k",
|
|
1964
|
+
description: "Delete to line end"
|
|
1965
|
+
},
|
|
1966
|
+
"tui.editor.yank": {
|
|
1967
|
+
defaultKeys: "ctrl+y",
|
|
1968
|
+
description: "Yank"
|
|
1969
|
+
},
|
|
1970
|
+
"tui.editor.yankPop": {
|
|
1971
|
+
defaultKeys: "alt+y",
|
|
1972
|
+
description: "Yank pop"
|
|
1973
|
+
},
|
|
1974
|
+
"tui.editor.undo": {
|
|
1975
|
+
defaultKeys: "ctrl+-",
|
|
1976
|
+
description: "Undo"
|
|
1977
|
+
},
|
|
1978
|
+
"tui.input.newLine": {
|
|
1979
|
+
defaultKeys: ["shift+enter", "ctrl+j"],
|
|
1980
|
+
description: "Insert newline"
|
|
1981
|
+
},
|
|
1982
|
+
"tui.input.submit": {
|
|
1983
|
+
defaultKeys: "enter",
|
|
1984
|
+
description: "Submit input"
|
|
1985
|
+
},
|
|
1986
|
+
"tui.input.tab": {
|
|
1987
|
+
defaultKeys: "tab",
|
|
1988
|
+
description: "Tab / autocomplete"
|
|
1989
|
+
},
|
|
1990
|
+
"tui.input.copy": {
|
|
1991
|
+
defaultKeys: "ctrl+c",
|
|
1992
|
+
description: "Copy selection"
|
|
1993
|
+
},
|
|
1994
|
+
"tui.select.up": {
|
|
1995
|
+
defaultKeys: "up",
|
|
1996
|
+
description: "Move selection up"
|
|
1997
|
+
},
|
|
1998
|
+
"tui.select.down": {
|
|
1999
|
+
defaultKeys: "down",
|
|
2000
|
+
description: "Move selection down"
|
|
2001
|
+
},
|
|
2002
|
+
"tui.select.pageUp": {
|
|
2003
|
+
defaultKeys: "pageUp",
|
|
2004
|
+
description: "Selection page up"
|
|
2005
|
+
},
|
|
2006
|
+
"tui.select.pageDown": {
|
|
2007
|
+
defaultKeys: "pageDown",
|
|
2008
|
+
description: "Selection page down"
|
|
2009
|
+
},
|
|
2010
|
+
"tui.select.confirm": {
|
|
2011
|
+
defaultKeys: "enter",
|
|
2012
|
+
description: "Confirm selection"
|
|
2013
|
+
},
|
|
2014
|
+
"tui.select.cancel": {
|
|
2015
|
+
defaultKeys: ["escape", "ctrl+c"],
|
|
2016
|
+
description: "Cancel selection"
|
|
2017
|
+
},
|
|
2018
|
+
"tui.altScreen.pageUp": {
|
|
2019
|
+
defaultKeys: "pageUp",
|
|
2020
|
+
description: "Scroll viewport up one page"
|
|
2021
|
+
},
|
|
2022
|
+
"tui.altScreen.pageDown": {
|
|
2023
|
+
defaultKeys: "pageDown",
|
|
2024
|
+
description: "Scroll viewport down one page"
|
|
2025
|
+
},
|
|
2026
|
+
"tui.altScreen.halfPageUp": {
|
|
2027
|
+
defaultKeys: [],
|
|
2028
|
+
description: "Scroll viewport up half a page"
|
|
2029
|
+
},
|
|
2030
|
+
"tui.altScreen.halfPageDown": {
|
|
2031
|
+
defaultKeys: [],
|
|
2032
|
+
description: "Scroll viewport down half a page"
|
|
2033
|
+
},
|
|
2034
|
+
"tui.altScreen.lineUp": {
|
|
2035
|
+
defaultKeys: [],
|
|
2036
|
+
description: "Scroll viewport up one line"
|
|
2037
|
+
},
|
|
2038
|
+
"tui.altScreen.lineDown": {
|
|
2039
|
+
defaultKeys: [],
|
|
2040
|
+
description: "Scroll viewport down one line"
|
|
2041
|
+
},
|
|
2042
|
+
"tui.altScreen.previousPrompt": {
|
|
2043
|
+
defaultKeys: "ctrl+shift+up",
|
|
2044
|
+
description: "Jump to previous semantic prompt"
|
|
2045
|
+
},
|
|
2046
|
+
"tui.altScreen.nextPrompt": {
|
|
2047
|
+
defaultKeys: "ctrl+shift+down",
|
|
2048
|
+
description: "Jump to next semantic prompt"
|
|
2049
|
+
},
|
|
2050
|
+
"tui.altScreen.search": {
|
|
2051
|
+
defaultKeys: "ctrl+shift+f",
|
|
2052
|
+
description: "Search the primary scroll view"
|
|
2053
|
+
},
|
|
2054
|
+
"tui.altScreen.searchNext": {
|
|
2055
|
+
defaultKeys: ["enter", "ctrl+g"],
|
|
2056
|
+
description: "Select the next search match"
|
|
2057
|
+
},
|
|
2058
|
+
"tui.altScreen.searchPrevious": {
|
|
2059
|
+
defaultKeys: ["shift+enter", "ctrl+shift+g"],
|
|
2060
|
+
description: "Select the previous search match"
|
|
2061
|
+
},
|
|
2062
|
+
"tui.altScreen.searchClose": {
|
|
2063
|
+
defaultKeys: "escape",
|
|
2064
|
+
description: "Close transcript search"
|
|
2065
|
+
},
|
|
2066
|
+
"tui.altScreen.top": {
|
|
2067
|
+
defaultKeys: "home",
|
|
2068
|
+
description: "Scroll viewport to top"
|
|
2069
|
+
},
|
|
2070
|
+
"tui.altScreen.bottom": {
|
|
2071
|
+
defaultKeys: "end",
|
|
2072
|
+
description: "Scroll viewport to bottom"
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
function normalizeKeys(keys) {
|
|
2076
|
+
if (keys === void 0) return [];
|
|
2077
|
+
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
2078
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2079
|
+
const result = [];
|
|
2080
|
+
for (const key of keyList) if (!seen.has(key)) {
|
|
2081
|
+
seen.add(key);
|
|
2082
|
+
result.push(key);
|
|
2083
|
+
}
|
|
2084
|
+
return result;
|
|
2085
|
+
}
|
|
2086
|
+
var KeybindingsManager = class {
|
|
2087
|
+
definitions;
|
|
2088
|
+
userBindings;
|
|
2089
|
+
keysById = /* @__PURE__ */ new Map();
|
|
2090
|
+
conflicts = [];
|
|
2091
|
+
constructor(definitions, userBindings = {}) {
|
|
2092
|
+
this.definitions = definitions;
|
|
2093
|
+
this.userBindings = userBindings;
|
|
2094
|
+
this.rebuild();
|
|
2095
|
+
}
|
|
2096
|
+
rebuild() {
|
|
2097
|
+
this.keysById.clear();
|
|
2098
|
+
this.conflicts = [];
|
|
2099
|
+
const userClaims = /* @__PURE__ */ new Map();
|
|
2100
|
+
for (const [keybinding, keys] of Object.entries(this.userBindings)) {
|
|
2101
|
+
if (!(keybinding in this.definitions)) continue;
|
|
2102
|
+
for (const key of normalizeKeys(keys)) {
|
|
2103
|
+
const claimants = userClaims.get(key) ?? /* @__PURE__ */ new Set();
|
|
2104
|
+
claimants.add(keybinding);
|
|
2105
|
+
userClaims.set(key, claimants);
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
for (const [key, keybindings] of userClaims) if (keybindings.size > 1) this.conflicts.push({
|
|
2109
|
+
key,
|
|
2110
|
+
keybindings: [...keybindings]
|
|
2111
|
+
});
|
|
2112
|
+
for (const [id, definition] of Object.entries(this.definitions)) {
|
|
2113
|
+
const userKeys = this.userBindings[id];
|
|
2114
|
+
const keys = userKeys === void 0 ? normalizeKeys(definition.defaultKeys) : normalizeKeys(userKeys);
|
|
2115
|
+
this.keysById.set(id, keys);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
matches(data, keybinding) {
|
|
2119
|
+
const keys = this.keysById.get(keybinding) ?? [];
|
|
2120
|
+
for (const key of keys) if (matchesKey(data, key)) return true;
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
getKeys(keybinding) {
|
|
2124
|
+
return [...this.keysById.get(keybinding) ?? []];
|
|
2125
|
+
}
|
|
2126
|
+
getDefinition(keybinding) {
|
|
2127
|
+
return this.definitions[keybinding];
|
|
2128
|
+
}
|
|
2129
|
+
getConflicts() {
|
|
2130
|
+
return this.conflicts.map((conflict) => ({
|
|
2131
|
+
...conflict,
|
|
2132
|
+
keybindings: [...conflict.keybindings]
|
|
2133
|
+
}));
|
|
2134
|
+
}
|
|
2135
|
+
setUserBindings(userBindings) {
|
|
2136
|
+
this.userBindings = userBindings;
|
|
2137
|
+
this.rebuild();
|
|
2138
|
+
}
|
|
2139
|
+
getUserBindings() {
|
|
2140
|
+
return { ...this.userBindings };
|
|
2141
|
+
}
|
|
2142
|
+
getResolvedBindings() {
|
|
2143
|
+
const resolved = {};
|
|
2144
|
+
for (const id of Object.keys(this.definitions)) {
|
|
2145
|
+
const keys = this.keysById.get(id) ?? [];
|
|
2146
|
+
resolved[id] = keys.length === 1 ? keys[0] : [...keys];
|
|
2147
|
+
}
|
|
2148
|
+
return resolved;
|
|
2149
|
+
}
|
|
2150
|
+
};
|
|
2151
|
+
let globalKeybindings = null;
|
|
2152
|
+
function setKeybindings(keybindings) {
|
|
2153
|
+
globalKeybindings = keybindings;
|
|
2154
|
+
}
|
|
2155
|
+
function getKeybindings() {
|
|
2156
|
+
if (!globalKeybindings) globalKeybindings = new KeybindingsManager(TUI_KEYBINDINGS);
|
|
2157
|
+
return globalKeybindings;
|
|
2158
|
+
}
|
|
2159
|
+
//#endregion
|
|
2160
|
+
//#region src/compat/vendor/pi-tui-latex.ts
|
|
2161
|
+
const SYMBOLS = {
|
|
2162
|
+
alpha: "α",
|
|
2163
|
+
beta: "β",
|
|
2164
|
+
gamma: "γ",
|
|
2165
|
+
delta: "δ",
|
|
2166
|
+
epsilon: "ϵ",
|
|
2167
|
+
varepsilon: "ε",
|
|
2168
|
+
zeta: "ζ",
|
|
2169
|
+
eta: "η",
|
|
2170
|
+
theta: "θ",
|
|
2171
|
+
vartheta: "ϑ",
|
|
2172
|
+
iota: "ι",
|
|
2173
|
+
kappa: "κ",
|
|
2174
|
+
varkappa: "ϰ",
|
|
2175
|
+
lambda: "λ",
|
|
2176
|
+
mu: "μ",
|
|
2177
|
+
nu: "ν",
|
|
2178
|
+
xi: "ξ",
|
|
2179
|
+
pi: "π",
|
|
2180
|
+
varpi: "ϖ",
|
|
2181
|
+
rho: "ρ",
|
|
2182
|
+
varrho: "ϱ",
|
|
2183
|
+
sigma: "σ",
|
|
2184
|
+
varsigma: "ς",
|
|
2185
|
+
tau: "τ",
|
|
2186
|
+
upsilon: "υ",
|
|
2187
|
+
phi: "ϕ",
|
|
2188
|
+
varphi: "φ",
|
|
2189
|
+
chi: "χ",
|
|
2190
|
+
psi: "ψ",
|
|
2191
|
+
omega: "ω",
|
|
2192
|
+
Gamma: "Γ",
|
|
2193
|
+
Delta: "Δ",
|
|
2194
|
+
Theta: "Θ",
|
|
2195
|
+
Lambda: "Λ",
|
|
2196
|
+
Xi: "Ξ",
|
|
2197
|
+
Pi: "Π",
|
|
2198
|
+
Sigma: "Σ",
|
|
2199
|
+
Upsilon: "Υ",
|
|
2200
|
+
Phi: "Φ",
|
|
2201
|
+
Psi: "Ψ",
|
|
2202
|
+
Omega: "Ω",
|
|
2203
|
+
pm: "±",
|
|
2204
|
+
mp: "∓",
|
|
2205
|
+
times: "×",
|
|
2206
|
+
div: "÷",
|
|
2207
|
+
cdot: "·",
|
|
2208
|
+
ast: "∗",
|
|
2209
|
+
star: "⋆",
|
|
2210
|
+
circ: "∘",
|
|
2211
|
+
bullet: "•",
|
|
2212
|
+
oplus: "⊕",
|
|
2213
|
+
ominus: "⊖",
|
|
2214
|
+
otimes: "⊗",
|
|
2215
|
+
oslash: "⊘",
|
|
2216
|
+
odot: "⊙",
|
|
2217
|
+
bigcirc: "○",
|
|
2218
|
+
dagger: "†",
|
|
2219
|
+
ddagger: "‡",
|
|
2220
|
+
amalg: "⨿",
|
|
2221
|
+
uplus: "⊎",
|
|
2222
|
+
sqcap: "⊓",
|
|
2223
|
+
sqcup: "⊔",
|
|
2224
|
+
triangleleft: "◁",
|
|
2225
|
+
triangleright: "▷",
|
|
2226
|
+
wr: "≀",
|
|
2227
|
+
cap: "∩",
|
|
2228
|
+
cup: "∪",
|
|
2229
|
+
bigcap: "⋂",
|
|
2230
|
+
bigcup: "⋃",
|
|
2231
|
+
bigwedge: "⋀",
|
|
2232
|
+
bigvee: "⋁",
|
|
2233
|
+
bigsqcup: "⨆",
|
|
2234
|
+
biguplus: "⨄",
|
|
2235
|
+
bigoplus: "⨁",
|
|
2236
|
+
bigotimes: "⨂",
|
|
2237
|
+
bigodot: "⨀",
|
|
2238
|
+
setminus: "∖",
|
|
2239
|
+
in: "∈",
|
|
2240
|
+
notin: "∉",
|
|
2241
|
+
ni: "∋",
|
|
2242
|
+
subset: "⊂",
|
|
2243
|
+
supset: "⊃",
|
|
2244
|
+
subseteq: "⊆",
|
|
2245
|
+
supseteq: "⊇",
|
|
2246
|
+
sqsubset: "⊏",
|
|
2247
|
+
sqsupset: "⊐",
|
|
2248
|
+
sqsubseteq: "⊑",
|
|
2249
|
+
sqsupseteq: "⊒",
|
|
2250
|
+
prec: "≺",
|
|
2251
|
+
preceq: "≼",
|
|
2252
|
+
succ: "≻",
|
|
2253
|
+
succeq: "≽",
|
|
2254
|
+
ll: "≪",
|
|
2255
|
+
gg: "≫",
|
|
2256
|
+
le: "≤",
|
|
2257
|
+
leq: "≤",
|
|
2258
|
+
leqslant: "≤",
|
|
2259
|
+
ge: "≥",
|
|
2260
|
+
geq: "≥",
|
|
2261
|
+
geqslant: "≥",
|
|
2262
|
+
ne: "≠",
|
|
2263
|
+
neq: "≠",
|
|
2264
|
+
equiv: "≡",
|
|
2265
|
+
approx: "≈",
|
|
2266
|
+
sim: "∼",
|
|
2267
|
+
simeq: "≃",
|
|
2268
|
+
cong: "≅",
|
|
2269
|
+
asymp: "≍",
|
|
2270
|
+
doteq: "≐",
|
|
2271
|
+
propto: "∝",
|
|
2272
|
+
parallel: "∥",
|
|
2273
|
+
perp: "⊥",
|
|
2274
|
+
mid: "∣",
|
|
2275
|
+
vdash: "⊢",
|
|
2276
|
+
dashv: "⊣",
|
|
2277
|
+
models: "⊨",
|
|
2278
|
+
Vdash: "⊩",
|
|
2279
|
+
Vvdash: "⊪",
|
|
2280
|
+
nvdash: "⊬",
|
|
2281
|
+
nvDash: "⊭",
|
|
2282
|
+
forall: "∀",
|
|
2283
|
+
exists: "∃",
|
|
2284
|
+
nexists: "∄",
|
|
2285
|
+
neg: "¬",
|
|
2286
|
+
land: "∧",
|
|
2287
|
+
wedge: "∧",
|
|
2288
|
+
lor: "∨",
|
|
2289
|
+
vee: "∨",
|
|
2290
|
+
to: "→",
|
|
2291
|
+
rightarrow: "→",
|
|
2292
|
+
longrightarrow: "→",
|
|
2293
|
+
leftarrow: "←",
|
|
2294
|
+
longleftarrow: "←",
|
|
2295
|
+
gets: "←",
|
|
2296
|
+
leftrightarrow: "↔",
|
|
2297
|
+
longleftrightarrow: "↔",
|
|
2298
|
+
hookleftarrow: "↩",
|
|
2299
|
+
hookrightarrow: "↪",
|
|
2300
|
+
twoheadleftarrow: "↞",
|
|
2301
|
+
twoheadrightarrow: "↠",
|
|
2302
|
+
leftharpoonup: "↼",
|
|
2303
|
+
leftharpoondown: "↽",
|
|
2304
|
+
rightharpoonup: "⇀",
|
|
2305
|
+
rightharpoondown: "⇁",
|
|
2306
|
+
rightleftharpoons: "⇌",
|
|
2307
|
+
leftrightharpoons: "⇋",
|
|
2308
|
+
nearrow: "↗",
|
|
2309
|
+
searrow: "↘",
|
|
2310
|
+
swarrow: "↙",
|
|
2311
|
+
nwarrow: "↖",
|
|
2312
|
+
rightsquigarrow: "⇝",
|
|
2313
|
+
leadsto: "⇝",
|
|
2314
|
+
Rightarrow: "⇒",
|
|
2315
|
+
Longrightarrow: "⇒",
|
|
2316
|
+
Leftarrow: "⇐",
|
|
2317
|
+
Longleftarrow: "⇐",
|
|
2318
|
+
Leftrightarrow: "⇔",
|
|
2319
|
+
Longleftrightarrow: "⇔",
|
|
2320
|
+
implies: "⇒",
|
|
2321
|
+
iff: "⇔",
|
|
2322
|
+
mapsto: "↦",
|
|
2323
|
+
longmapsto: "↦",
|
|
2324
|
+
uparrow: "↑",
|
|
2325
|
+
downarrow: "↓",
|
|
2326
|
+
partial: "∂",
|
|
2327
|
+
nabla: "∇",
|
|
2328
|
+
int: "∫",
|
|
2329
|
+
iint: "∬",
|
|
2330
|
+
iiint: "∭",
|
|
2331
|
+
oint: "∮",
|
|
2332
|
+
sum: "∑",
|
|
2333
|
+
prod: "∏",
|
|
2334
|
+
coprod: "∐",
|
|
2335
|
+
infty: "∞",
|
|
2336
|
+
emptyset: "∅",
|
|
2337
|
+
varnothing: "∅",
|
|
2338
|
+
angle: "∠",
|
|
2339
|
+
therefore: "∴",
|
|
2340
|
+
because: "∵",
|
|
2341
|
+
aleph: "ℵ",
|
|
2342
|
+
beth: "ℶ",
|
|
2343
|
+
gimel: "ℷ",
|
|
2344
|
+
daleth: "ℸ",
|
|
2345
|
+
top: "⊤",
|
|
2346
|
+
bot: "⊥",
|
|
2347
|
+
triangle: "△",
|
|
2348
|
+
square: "□",
|
|
2349
|
+
lozenge: "◊",
|
|
2350
|
+
checkmark: "✓",
|
|
2351
|
+
complement: "∁",
|
|
2352
|
+
wp: "℘",
|
|
2353
|
+
prime: "′",
|
|
2354
|
+
ldots: "…",
|
|
2355
|
+
dots: "…",
|
|
2356
|
+
cdots: "⋯",
|
|
2357
|
+
vdots: "⋮",
|
|
2358
|
+
ddots: "⋱",
|
|
2359
|
+
ell: "ℓ",
|
|
2360
|
+
hbar: "ℏ",
|
|
2361
|
+
Im: "ℑ",
|
|
2362
|
+
Re: "ℜ",
|
|
2363
|
+
langle: "⟨",
|
|
2364
|
+
rangle: "⟩",
|
|
2365
|
+
vert: "|",
|
|
2366
|
+
lvert: "|",
|
|
2367
|
+
rvert: "|",
|
|
2368
|
+
Vert: "‖",
|
|
2369
|
+
lVert: "‖",
|
|
2370
|
+
rVert: "‖",
|
|
2371
|
+
lbrace: "{",
|
|
2372
|
+
rbrace: "}",
|
|
2373
|
+
backslash: "\\",
|
|
2374
|
+
lfloor: "⌊",
|
|
2375
|
+
rfloor: "⌋",
|
|
2376
|
+
lceil: "⌈",
|
|
2377
|
+
rceil: "⌉",
|
|
2378
|
+
colon: ":"
|
|
2379
|
+
};
|
|
2380
|
+
const NAMED_OPERATORS = /* @__PURE__ */ new Set([
|
|
2381
|
+
"arccos",
|
|
2382
|
+
"arcsin",
|
|
2383
|
+
"arctan",
|
|
2384
|
+
"arg",
|
|
2385
|
+
"cos",
|
|
2386
|
+
"cosh",
|
|
2387
|
+
"cot",
|
|
2388
|
+
"coth",
|
|
2389
|
+
"csc",
|
|
2390
|
+
"deg",
|
|
2391
|
+
"det",
|
|
2392
|
+
"dim",
|
|
2393
|
+
"exp",
|
|
2394
|
+
"gcd",
|
|
2395
|
+
"hom",
|
|
2396
|
+
"inf",
|
|
2397
|
+
"ker",
|
|
2398
|
+
"lg",
|
|
2399
|
+
"lim",
|
|
2400
|
+
"liminf",
|
|
2401
|
+
"limsup",
|
|
2402
|
+
"ln",
|
|
2403
|
+
"log",
|
|
2404
|
+
"max",
|
|
2405
|
+
"min",
|
|
2406
|
+
"Pr",
|
|
2407
|
+
"sec",
|
|
2408
|
+
"sin",
|
|
2409
|
+
"sinh",
|
|
2410
|
+
"sup",
|
|
2411
|
+
"tan",
|
|
2412
|
+
"tanh"
|
|
2413
|
+
]);
|
|
2414
|
+
const LIMIT_OPERATORS = /* @__PURE__ */ new Set([
|
|
2415
|
+
"argmax",
|
|
2416
|
+
"argmin",
|
|
2417
|
+
"inf",
|
|
2418
|
+
"injlim",
|
|
2419
|
+
"lim",
|
|
2420
|
+
"liminf",
|
|
2421
|
+
"limsup",
|
|
2422
|
+
"max",
|
|
2423
|
+
"min",
|
|
2424
|
+
"projlim",
|
|
2425
|
+
"sup"
|
|
2426
|
+
]);
|
|
2427
|
+
const DISPLAY_LIMIT_SYMBOLS = /* @__PURE__ */ new Set([
|
|
2428
|
+
"bigcap",
|
|
2429
|
+
"bigcup",
|
|
2430
|
+
"bigodot",
|
|
2431
|
+
"bigoplus",
|
|
2432
|
+
"bigotimes",
|
|
2433
|
+
"bigsqcup",
|
|
2434
|
+
"biguplus",
|
|
2435
|
+
"bigvee",
|
|
2436
|
+
"bigwedge",
|
|
2437
|
+
"coprod",
|
|
2438
|
+
"int",
|
|
2439
|
+
"iint",
|
|
2440
|
+
"iiint",
|
|
2441
|
+
"oint",
|
|
2442
|
+
"prod",
|
|
2443
|
+
"sum"
|
|
2444
|
+
]);
|
|
2445
|
+
const RELATION_COMMANDS = /* @__PURE__ */ new Set([
|
|
2446
|
+
"Leftarrow",
|
|
2447
|
+
"Leftrightarrow",
|
|
2448
|
+
"Longleftarrow",
|
|
2449
|
+
"Longleftrightarrow",
|
|
2450
|
+
"Longrightarrow",
|
|
2451
|
+
"Rightarrow",
|
|
2452
|
+
"Vdash",
|
|
2453
|
+
"Vvdash",
|
|
2454
|
+
"approx",
|
|
2455
|
+
"asymp",
|
|
2456
|
+
"cong",
|
|
2457
|
+
"dashv",
|
|
2458
|
+
"doteq",
|
|
2459
|
+
"downarrow",
|
|
2460
|
+
"equiv",
|
|
2461
|
+
"ge",
|
|
2462
|
+
"geq",
|
|
2463
|
+
"geqslant",
|
|
2464
|
+
"gets",
|
|
2465
|
+
"gg",
|
|
2466
|
+
"hookleftarrow",
|
|
2467
|
+
"hookrightarrow",
|
|
2468
|
+
"iff",
|
|
2469
|
+
"implies",
|
|
2470
|
+
"in",
|
|
2471
|
+
"leadsto",
|
|
2472
|
+
"le",
|
|
2473
|
+
"leftarrow",
|
|
2474
|
+
"leftharpoondown",
|
|
2475
|
+
"leftharpoonup",
|
|
2476
|
+
"leftrightarrow",
|
|
2477
|
+
"leftrightharpoons",
|
|
2478
|
+
"leq",
|
|
2479
|
+
"leqslant",
|
|
2480
|
+
"ll",
|
|
2481
|
+
"longleftarrow",
|
|
2482
|
+
"longleftrightarrow",
|
|
2483
|
+
"longmapsto",
|
|
2484
|
+
"longrightarrow",
|
|
2485
|
+
"mapsto",
|
|
2486
|
+
"mid",
|
|
2487
|
+
"models",
|
|
2488
|
+
"ne",
|
|
2489
|
+
"nearrow",
|
|
2490
|
+
"neq",
|
|
2491
|
+
"ni",
|
|
2492
|
+
"notin",
|
|
2493
|
+
"nvdash",
|
|
2494
|
+
"nvDash",
|
|
2495
|
+
"nwarrow",
|
|
2496
|
+
"parallel",
|
|
2497
|
+
"perp",
|
|
2498
|
+
"prec",
|
|
2499
|
+
"preceq",
|
|
2500
|
+
"propto",
|
|
2501
|
+
"rightharpoondown",
|
|
2502
|
+
"rightharpoonup",
|
|
2503
|
+
"rightleftharpoons",
|
|
2504
|
+
"rightarrow",
|
|
2505
|
+
"rightsquigarrow",
|
|
2506
|
+
"searrow",
|
|
2507
|
+
"sim",
|
|
2508
|
+
"simeq",
|
|
2509
|
+
"sqsubset",
|
|
2510
|
+
"sqsubseteq",
|
|
2511
|
+
"sqsupset",
|
|
2512
|
+
"sqsupseteq",
|
|
2513
|
+
"subset",
|
|
2514
|
+
"subseteq",
|
|
2515
|
+
"succ",
|
|
2516
|
+
"succeq",
|
|
2517
|
+
"supset",
|
|
2518
|
+
"supseteq",
|
|
2519
|
+
"swarrow",
|
|
2520
|
+
"to",
|
|
2521
|
+
"triangleleft",
|
|
2522
|
+
"triangleright",
|
|
2523
|
+
"twoheadleftarrow",
|
|
2524
|
+
"twoheadrightarrow",
|
|
2525
|
+
"uparrow",
|
|
2526
|
+
"vdash"
|
|
2527
|
+
]);
|
|
2528
|
+
const NEGATED_SYMBOLS = {
|
|
2529
|
+
"<": "≮",
|
|
2530
|
+
">": "≯",
|
|
2531
|
+
"=": "≠",
|
|
2532
|
+
"∈": "∉",
|
|
2533
|
+
"∋": "∌",
|
|
2534
|
+
"∣": "∤",
|
|
2535
|
+
"∥": "∦",
|
|
2536
|
+
"∼": "≁",
|
|
2537
|
+
"≃": "≄",
|
|
2538
|
+
"≅": "≇",
|
|
2539
|
+
"≈": "≉",
|
|
2540
|
+
"≡": "≢",
|
|
2541
|
+
"≤": "≰",
|
|
2542
|
+
"≥": "≱",
|
|
2543
|
+
"≺": "⊀",
|
|
2544
|
+
"≻": "⊁",
|
|
2545
|
+
"⊂": "⊄",
|
|
2546
|
+
"⊃": "⊅",
|
|
2547
|
+
"⊆": "⊈",
|
|
2548
|
+
"⊇": "⊉",
|
|
2549
|
+
"⊢": "⊬",
|
|
2550
|
+
"⊨": "⊭",
|
|
2551
|
+
"↔": "↮",
|
|
2552
|
+
"←": "↚",
|
|
2553
|
+
"→": "↛",
|
|
2554
|
+
"⇒": "⇏",
|
|
2555
|
+
"⇐": "⇍",
|
|
2556
|
+
"⇔": "⇎",
|
|
2557
|
+
"≼": "⋠",
|
|
2558
|
+
"≽": "⋡"
|
|
2559
|
+
};
|
|
2560
|
+
const BLACKBOARD = {
|
|
2561
|
+
C: "ℂ",
|
|
2562
|
+
H: "ℍ",
|
|
2563
|
+
N: "ℕ",
|
|
2564
|
+
P: "ℙ",
|
|
2565
|
+
Q: "ℚ",
|
|
2566
|
+
R: "ℝ",
|
|
2567
|
+
Z: "ℤ"
|
|
2568
|
+
};
|
|
2569
|
+
const SUPERSCRIPTS = {
|
|
2570
|
+
"0": "⁰",
|
|
2571
|
+
"1": "¹",
|
|
2572
|
+
"2": "²",
|
|
2573
|
+
"3": "³",
|
|
2574
|
+
"4": "⁴",
|
|
2575
|
+
"5": "⁵",
|
|
2576
|
+
"6": "⁶",
|
|
2577
|
+
"7": "⁷",
|
|
2578
|
+
"8": "⁸",
|
|
2579
|
+
"9": "⁹",
|
|
2580
|
+
"+": "⁺",
|
|
2581
|
+
"-": "⁻",
|
|
2582
|
+
"=": "⁼",
|
|
2583
|
+
"(": "⁽",
|
|
2584
|
+
")": "⁾",
|
|
2585
|
+
a: "ᵃ",
|
|
2586
|
+
b: "ᵇ",
|
|
2587
|
+
c: "ᶜ",
|
|
2588
|
+
d: "ᵈ",
|
|
2589
|
+
e: "ᵉ",
|
|
2590
|
+
f: "ᶠ",
|
|
2591
|
+
g: "ᵍ",
|
|
2592
|
+
h: "ʰ",
|
|
2593
|
+
i: "ⁱ",
|
|
2594
|
+
j: "ʲ",
|
|
2595
|
+
k: "ᵏ",
|
|
2596
|
+
l: "ˡ",
|
|
2597
|
+
m: "ᵐ",
|
|
2598
|
+
n: "ⁿ",
|
|
2599
|
+
o: "ᵒ",
|
|
2600
|
+
p: "ᵖ",
|
|
2601
|
+
r: "ʳ",
|
|
2602
|
+
s: "ˢ",
|
|
2603
|
+
t: "ᵗ",
|
|
2604
|
+
u: "ᵘ",
|
|
2605
|
+
v: "ᵛ",
|
|
2606
|
+
w: "ʷ",
|
|
2607
|
+
x: "ˣ",
|
|
2608
|
+
y: "ʸ",
|
|
2609
|
+
z: "ᶻ"
|
|
2610
|
+
};
|
|
2611
|
+
const SUBSCRIPTS = {
|
|
2612
|
+
"0": "₀",
|
|
2613
|
+
"1": "₁",
|
|
2614
|
+
"2": "₂",
|
|
2615
|
+
"3": "₃",
|
|
2616
|
+
"4": "₄",
|
|
2617
|
+
"5": "₅",
|
|
2618
|
+
"6": "₆",
|
|
2619
|
+
"7": "₇",
|
|
2620
|
+
"8": "₈",
|
|
2621
|
+
"9": "₉",
|
|
2622
|
+
"+": "₊",
|
|
2623
|
+
"-": "₋",
|
|
2624
|
+
"=": "₌",
|
|
2625
|
+
"(": "₍",
|
|
2626
|
+
")": "₎",
|
|
2627
|
+
a: "ₐ",
|
|
2628
|
+
e: "ₑ",
|
|
2629
|
+
h: "ₕ",
|
|
2630
|
+
i: "ᵢ",
|
|
2631
|
+
j: "ⱼ",
|
|
2632
|
+
k: "ₖ",
|
|
2633
|
+
l: "ₗ",
|
|
2634
|
+
m: "ₘ",
|
|
2635
|
+
n: "ₙ",
|
|
2636
|
+
o: "ₒ",
|
|
2637
|
+
p: "ₚ",
|
|
2638
|
+
r: "ᵣ",
|
|
2639
|
+
s: "ₛ",
|
|
2640
|
+
t: "ₜ",
|
|
2641
|
+
u: "ᵤ",
|
|
2642
|
+
v: "ᵥ",
|
|
2643
|
+
x: "ₓ"
|
|
2644
|
+
};
|
|
2645
|
+
const SPACING_COMMANDS = /* @__PURE__ */ new Set([
|
|
2646
|
+
",",
|
|
2647
|
+
":",
|
|
2648
|
+
";",
|
|
2649
|
+
" ",
|
|
2650
|
+
">",
|
|
2651
|
+
"enspace",
|
|
2652
|
+
"enskip",
|
|
2653
|
+
"medspace",
|
|
2654
|
+
"quad",
|
|
2655
|
+
"qquad",
|
|
2656
|
+
"thickspace",
|
|
2657
|
+
"thinspace"
|
|
2658
|
+
]);
|
|
2659
|
+
const NEGATIVE_SPACING_COMMANDS = /* @__PURE__ */ new Set([
|
|
2660
|
+
"!",
|
|
2661
|
+
"negmedspace",
|
|
2662
|
+
"negthickspace",
|
|
2663
|
+
"negthinspace"
|
|
2664
|
+
]);
|
|
2665
|
+
const NEGATIVE_SPACE = "\0";
|
|
2666
|
+
const IGNORED_COMMANDS = /* @__PURE__ */ new Set([
|
|
2667
|
+
"displaystyle",
|
|
2668
|
+
"limits",
|
|
2669
|
+
"nolimits",
|
|
2670
|
+
"scriptstyle",
|
|
2671
|
+
"scriptscriptstyle",
|
|
2672
|
+
"textstyle"
|
|
2673
|
+
]);
|
|
2674
|
+
const SIZE_COMMANDS = /* @__PURE__ */ new Set([
|
|
2675
|
+
"big",
|
|
2676
|
+
"Big",
|
|
2677
|
+
"bigg",
|
|
2678
|
+
"Bigg",
|
|
2679
|
+
"bigl",
|
|
2680
|
+
"Bigl",
|
|
2681
|
+
"biggl",
|
|
2682
|
+
"Biggl",
|
|
2683
|
+
"bigr",
|
|
2684
|
+
"Bigr",
|
|
2685
|
+
"biggr",
|
|
2686
|
+
"Biggr"
|
|
2687
|
+
]);
|
|
2688
|
+
const PLAIN_WRAPPERS = /* @__PURE__ */ new Set([
|
|
2689
|
+
"emph",
|
|
2690
|
+
"mathcal",
|
|
2691
|
+
"mathbf",
|
|
2692
|
+
"mathfrak",
|
|
2693
|
+
"mathit",
|
|
2694
|
+
"mathrm",
|
|
2695
|
+
"mathnormal",
|
|
2696
|
+
"mathscr",
|
|
2697
|
+
"mathsf",
|
|
2698
|
+
"mathtt",
|
|
2699
|
+
"mathup",
|
|
2700
|
+
"mbox",
|
|
2701
|
+
"overbrace",
|
|
2702
|
+
"pmb",
|
|
2703
|
+
"smash",
|
|
2704
|
+
"substack",
|
|
2705
|
+
"text",
|
|
2706
|
+
"textbf",
|
|
2707
|
+
"textit",
|
|
2708
|
+
"textmd",
|
|
2709
|
+
"textnormal",
|
|
2710
|
+
"textrm",
|
|
2711
|
+
"textsc",
|
|
2712
|
+
"textsf",
|
|
2713
|
+
"textsl",
|
|
2714
|
+
"texttt",
|
|
2715
|
+
"textup",
|
|
2716
|
+
"underbrace",
|
|
2717
|
+
"bm",
|
|
2718
|
+
"boldsymbol"
|
|
2719
|
+
]);
|
|
2720
|
+
const ACCENTS = {
|
|
2721
|
+
acute: "́",
|
|
2722
|
+
bar: "̅",
|
|
2723
|
+
breve: "̆",
|
|
2724
|
+
check: "̌",
|
|
2725
|
+
ddot: "̈",
|
|
2726
|
+
dot: "̇",
|
|
2727
|
+
grave: "̀",
|
|
2728
|
+
hat: "̂",
|
|
2729
|
+
mathring: "̊",
|
|
2730
|
+
overleftarrow: "⃖",
|
|
2731
|
+
overleftrightarrow: "⃡",
|
|
2732
|
+
overline: "̅",
|
|
2733
|
+
overrightarrow: "⃗",
|
|
2734
|
+
tilde: "̃",
|
|
2735
|
+
underline: "̲",
|
|
2736
|
+
vec: "⃗",
|
|
2737
|
+
widehat: "̂",
|
|
2738
|
+
widetilde: "̃"
|
|
2739
|
+
};
|
|
2740
|
+
function replaceCharacters(value, replacements) {
|
|
2741
|
+
let result = "";
|
|
2742
|
+
for (const character of value) {
|
|
2743
|
+
const replacement = replacements[character];
|
|
2744
|
+
if (replacement === void 0) return;
|
|
2745
|
+
result += replacement;
|
|
2746
|
+
}
|
|
2747
|
+
return result;
|
|
2748
|
+
}
|
|
2749
|
+
function formatScript(value, kind) {
|
|
2750
|
+
value = value.trim();
|
|
2751
|
+
const replacements = kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS;
|
|
2752
|
+
const unicode = replaceCharacters(value.replace(/\s*([=+-])\s*/g, "$1"), replacements);
|
|
2753
|
+
if (unicode !== void 0) return unicode;
|
|
2754
|
+
const prefix = kind === "sub" ? "_" : "^";
|
|
2755
|
+
if (Array.from(value).length === 1 || kind === "sub" && /^[A-Za-z]+$/.test(value)) return `${prefix}${value}`;
|
|
2756
|
+
return `${prefix}(${value})`;
|
|
2757
|
+
}
|
|
2758
|
+
function formatFraction(numerator, denominator) {
|
|
2759
|
+
numerator = numerator.trim();
|
|
2760
|
+
denominator = denominator.trim();
|
|
2761
|
+
const simpleNumerator = /^[\p{L}\p{N}.]+$/u.test(numerator);
|
|
2762
|
+
const simpleDenominator = /^[\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;
|
|
2763
|
+
return `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;
|
|
2764
|
+
}
|
|
2765
|
+
function formatRoot(value, symbol = "√") {
|
|
2766
|
+
value = value.trim();
|
|
2767
|
+
return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
|
|
2768
|
+
}
|
|
2769
|
+
const NAMED_OPERATOR_START = "";
|
|
2770
|
+
const NAMED_OPERATOR_END = "";
|
|
2771
|
+
const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
|
|
2772
|
+
const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
|
|
2773
|
+
function normalizeOutput(value) {
|
|
2774
|
+
return value.replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ").replaceAll(NAMED_OPERATOR_START, "").replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ").replaceAll(NAMED_OPERATOR_END, "").split("\n").map((line) => line.replace(/[ \t]+/g, " ").trim()).filter((line, index, lines) => line.length > 0 || index > 0 && index < lines.length - 1).join("\n").trim();
|
|
2775
|
+
}
|
|
2776
|
+
const LAYOUT_MARKER_START = "";
|
|
2777
|
+
const LAYOUT_MARKER_END = "";
|
|
2778
|
+
const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
|
|
2779
|
+
const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
|
|
2780
|
+
const PROTECTED_SPACE = "";
|
|
2781
|
+
function padLayoutLine(line, width, centered = false) {
|
|
2782
|
+
const padding = Math.max(0, width - visibleWidth(line));
|
|
2783
|
+
const left = centered ? Math.floor(padding / 2) : 0;
|
|
2784
|
+
return `${" ".repeat(left)}${line}${" ".repeat(padding - left)}`;
|
|
2785
|
+
}
|
|
2786
|
+
function joinLayouts(layouts) {
|
|
2787
|
+
if (layouts.length === 0) return {
|
|
2788
|
+
lines: [""],
|
|
2789
|
+
width: 0,
|
|
2790
|
+
baseline: 0
|
|
2791
|
+
};
|
|
2792
|
+
const baseline = Math.max(...layouts.map((layout) => layout.baseline));
|
|
2793
|
+
const below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));
|
|
2794
|
+
const lines = [];
|
|
2795
|
+
for (let row = 0; row <= baseline + below; row++) {
|
|
2796
|
+
let line = "";
|
|
2797
|
+
for (const layout of layouts) {
|
|
2798
|
+
const sourceRow = row - baseline + layout.baseline;
|
|
2799
|
+
line += sourceRow >= 0 && sourceRow < layout.lines.length ? padLayoutLine(layout.lines[sourceRow] ?? "", layout.width) : " ".repeat(layout.width);
|
|
2800
|
+
}
|
|
2801
|
+
lines.push(line.trimEnd());
|
|
2802
|
+
}
|
|
2803
|
+
return {
|
|
2804
|
+
lines,
|
|
2805
|
+
width: layouts.reduce((width, layout) => width + layout.width, 0),
|
|
2806
|
+
baseline
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
function renderLayout(source, nodes) {
|
|
2810
|
+
const renderedLines = [];
|
|
2811
|
+
let firstBaseline = 0;
|
|
2812
|
+
for (const sourceLine of source.split("\n")) {
|
|
2813
|
+
const layouts = [];
|
|
2814
|
+
let position = 0;
|
|
2815
|
+
let previousNode;
|
|
2816
|
+
for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
|
|
2817
|
+
const index = match.index;
|
|
2818
|
+
const node = nodes[Number(match[1])];
|
|
2819
|
+
if (!node) continue;
|
|
2820
|
+
if (index > position) {
|
|
2821
|
+
const sliced = sourceLine.slice(position, index);
|
|
2822
|
+
const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
|
|
2823
|
+
const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
|
|
2824
|
+
const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
|
|
2825
|
+
const text = trimmed ? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}` : preserveLeadingSpace || preserveTrailingSpace ? " " : "";
|
|
2826
|
+
layouts.push({
|
|
2827
|
+
lines: [text],
|
|
2828
|
+
width: visibleWidth(text),
|
|
2829
|
+
baseline: 0
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
if (node.type === "fraction") {
|
|
2833
|
+
const numerator = renderLayout(node.numerator, nodes);
|
|
2834
|
+
const denominator = renderLayout(node.denominator, nodes);
|
|
2835
|
+
const contentWidth = Math.max(numerator.width, denominator.width, 1);
|
|
2836
|
+
const width = contentWidth + 2;
|
|
2837
|
+
layouts.push({
|
|
2838
|
+
lines: [
|
|
2839
|
+
...numerator.lines.map((line) => padLayoutLine(line, width, true)),
|
|
2840
|
+
` ${"─".repeat(contentWidth)} `,
|
|
2841
|
+
...denominator.lines.map((line) => padLayoutLine(line, width, true))
|
|
2842
|
+
],
|
|
2843
|
+
width,
|
|
2844
|
+
baseline: numerator.lines.length
|
|
2845
|
+
});
|
|
2846
|
+
} else if (node.type === "operator") {
|
|
2847
|
+
const contentWidth = Math.max(visibleWidth(node.operator), node.lower === void 0 ? 0 : visibleWidth(node.lower), node.upper === void 0 ? 0 : visibleWidth(node.upper));
|
|
2848
|
+
const lines = [];
|
|
2849
|
+
if (node.upper !== void 0) lines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);
|
|
2850
|
+
lines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);
|
|
2851
|
+
if (node.lower !== void 0) lines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);
|
|
2852
|
+
layouts.push({
|
|
2853
|
+
lines,
|
|
2854
|
+
width: contentWidth + 1,
|
|
2855
|
+
baseline: node.upper === void 0 ? 0 : 1
|
|
2856
|
+
});
|
|
2857
|
+
} else {
|
|
2858
|
+
const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
|
|
2859
|
+
layouts.push({
|
|
2860
|
+
lines: node.lines.map((line) => padLayoutLine(line, width)),
|
|
2861
|
+
width,
|
|
2862
|
+
baseline: node.baseline
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
position = index + match[0].length;
|
|
2866
|
+
previousNode = node;
|
|
2867
|
+
}
|
|
2868
|
+
if (position < sourceLine.length) {
|
|
2869
|
+
const sliced = sourceLine.slice(position);
|
|
2870
|
+
const trimmed = previousNode ? sliced.trimStart() : sliced;
|
|
2871
|
+
const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
|
|
2872
|
+
layouts.push({
|
|
2873
|
+
lines: [text],
|
|
2874
|
+
width: visibleWidth(text),
|
|
2875
|
+
baseline: 0
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
const lineLayout = joinLayouts(layouts);
|
|
2879
|
+
if (renderedLines.length === 0) firstBaseline = lineLayout.baseline;
|
|
2880
|
+
renderedLines.push(...lineLayout.lines);
|
|
2881
|
+
}
|
|
2882
|
+
return {
|
|
2883
|
+
lines: renderedLines,
|
|
2884
|
+
width: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),
|
|
2885
|
+
baseline: firstBaseline
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
var LatexParser = class LatexParser {
|
|
2889
|
+
source;
|
|
2890
|
+
layoutNodes;
|
|
2891
|
+
display;
|
|
2892
|
+
position = 0;
|
|
2893
|
+
supported = true;
|
|
2894
|
+
stackFractions = true;
|
|
2895
|
+
constructor(source, layoutNodes, display) {
|
|
2896
|
+
this.source = source;
|
|
2897
|
+
this.layoutNodes = layoutNodes;
|
|
2898
|
+
this.display = display;
|
|
2899
|
+
}
|
|
2900
|
+
render() {
|
|
2901
|
+
const rendered = this.parseSequence();
|
|
2902
|
+
if (!this.supported || this.position !== this.source.length) return;
|
|
2903
|
+
return normalizeOutput(rendered);
|
|
2904
|
+
}
|
|
2905
|
+
parseSequence(endCharacter) {
|
|
2906
|
+
let result = "";
|
|
2907
|
+
while (this.position < this.source.length) {
|
|
2908
|
+
const character = this.source[this.position];
|
|
2909
|
+
if (endCharacter && character === endCharacter) {
|
|
2910
|
+
this.position++;
|
|
2911
|
+
return result;
|
|
2912
|
+
}
|
|
2913
|
+
if (character === "}") {
|
|
2914
|
+
this.supported = false;
|
|
2915
|
+
return result;
|
|
2916
|
+
}
|
|
2917
|
+
if (character === "{") {
|
|
2918
|
+
this.position++;
|
|
2919
|
+
result += this.parseSequence("}");
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
if (character === "\\") {
|
|
2923
|
+
const command = this.parseCommand();
|
|
2924
|
+
if (command === NEGATIVE_SPACE) {
|
|
2925
|
+
result = result.trimEnd();
|
|
2926
|
+
if (result.endsWith(NAMED_OPERATOR_END)) result = result.slice(0, -2);
|
|
2927
|
+
} else result += command;
|
|
2928
|
+
continue;
|
|
2929
|
+
}
|
|
2930
|
+
if (character === "^" || character === "_") {
|
|
2931
|
+
this.position++;
|
|
2932
|
+
result = result.trimEnd();
|
|
2933
|
+
const script = formatScript(this.parseRequiredArgument(false), character === "_" ? "sub" : "sup");
|
|
2934
|
+
if (result.endsWith(NAMED_OPERATOR_END)) result = `${result.slice(0, -2)}${script}${NAMED_OPERATOR_END}`;
|
|
2935
|
+
else result += script;
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
if (/\s/.test(character)) {
|
|
2939
|
+
result += this.parseWhitespace();
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
if (character === "=" || character === "<" || character === ">") {
|
|
2943
|
+
result = `${result.trimEnd()} ${character} `;
|
|
2944
|
+
this.position++;
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
if (character === "&") {
|
|
2948
|
+
this.position++;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
if (character === "~") {
|
|
2952
|
+
this.position++;
|
|
2953
|
+
result += " ";
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
if (character === ".") {
|
|
2957
|
+
const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
|
|
2958
|
+
const node = marker ? this.layoutNodes[Number(marker[1])] : void 0;
|
|
2959
|
+
if (node?.type === "matrix") {
|
|
2960
|
+
const lastLine = node.lines.length - 1;
|
|
2961
|
+
node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
|
|
2962
|
+
this.position++;
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
result += character;
|
|
2967
|
+
this.position++;
|
|
2968
|
+
}
|
|
2969
|
+
if (endCharacter) this.supported = false;
|
|
2970
|
+
return result;
|
|
2971
|
+
}
|
|
2972
|
+
parseWhitespace() {
|
|
2973
|
+
while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) this.position++;
|
|
2974
|
+
return " ";
|
|
2975
|
+
}
|
|
2976
|
+
parseCommand() {
|
|
2977
|
+
this.position++;
|
|
2978
|
+
if (this.position >= this.source.length) {
|
|
2979
|
+
this.supported = false;
|
|
2980
|
+
return "";
|
|
2981
|
+
}
|
|
2982
|
+
let command = "";
|
|
2983
|
+
const first = this.source[this.position] ?? "";
|
|
2984
|
+
if (first === "\n" || first === "\r") {
|
|
2985
|
+
this.position++;
|
|
2986
|
+
if (first === "\r" && this.source[this.position] === "\n") this.position++;
|
|
2987
|
+
return " ";
|
|
2988
|
+
}
|
|
2989
|
+
if (/[A-Za-z]/.test(first)) {
|
|
2990
|
+
const start = this.position;
|
|
2991
|
+
while (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? "")) this.position++;
|
|
2992
|
+
command = this.source.slice(start, this.position);
|
|
2993
|
+
} else {
|
|
2994
|
+
command = first;
|
|
2995
|
+
this.position++;
|
|
2996
|
+
}
|
|
2997
|
+
if (command === "\\") return "\n";
|
|
2998
|
+
if (SPACING_COMMANDS.has(command)) return " ";
|
|
2999
|
+
if (NEGATIVE_SPACING_COMMANDS.has(command)) return NEGATIVE_SPACE;
|
|
3000
|
+
if (IGNORED_COMMANDS.has(command)) return "";
|
|
3001
|
+
if (command === "{" || command === "}" || command === "$" || command === "%" || command === "#" || command === "_" || command === "&") return command;
|
|
3002
|
+
if (command === "|") return "‖";
|
|
3003
|
+
if (command === "not") {
|
|
3004
|
+
const value = this.parseRequiredArgument(false).trim();
|
|
3005
|
+
const negated = NEGATED_SYMBOLS[value];
|
|
3006
|
+
if (negated !== void 0) return ` ${negated} `;
|
|
3007
|
+
const characters = Array.from(value);
|
|
3008
|
+
if (characters.length === 0) {
|
|
3009
|
+
this.supported = false;
|
|
3010
|
+
return "";
|
|
3011
|
+
}
|
|
3012
|
+
return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
|
|
3013
|
+
}
|
|
3014
|
+
if (LIMIT_OPERATORS.has(command)) return this.parseOperator(command, "bracket", true, true);
|
|
3015
|
+
const symbol = SYMBOLS[command];
|
|
3016
|
+
if (symbol !== void 0) {
|
|
3017
|
+
if (DISPLAY_LIMIT_SYMBOLS.has(command)) return this.parseOperator(symbol, "script", true);
|
|
3018
|
+
return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
|
|
3019
|
+
}
|
|
3020
|
+
if (NAMED_OPERATORS.has(command)) return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
|
|
3021
|
+
if (SIZE_COMMANDS.has(command)) return "";
|
|
3022
|
+
if (command === "left" || command === "middle" || command === "right") {
|
|
3023
|
+
if (this.source[this.position] === ".") this.position++;
|
|
3024
|
+
return "";
|
|
3025
|
+
}
|
|
3026
|
+
if (command === "frac" || command === "dfrac" || command === "tfrac") {
|
|
3027
|
+
const shouldStack = this.display && this.stackFractions && command !== "tfrac";
|
|
3028
|
+
const numerator = this.parseRequiredArgument(!shouldStack);
|
|
3029
|
+
const denominator = this.parseRequiredArgument(!shouldStack);
|
|
3030
|
+
if (shouldStack) {
|
|
3031
|
+
const index = this.layoutNodes.push({
|
|
3032
|
+
type: "fraction",
|
|
3033
|
+
numerator: normalizeOutput(numerator),
|
|
3034
|
+
denominator: normalizeOutput(denominator)
|
|
3035
|
+
}) - 1;
|
|
3036
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
3037
|
+
}
|
|
3038
|
+
return formatFraction(numerator, denominator);
|
|
3039
|
+
}
|
|
3040
|
+
if (command === "sqrt") {
|
|
3041
|
+
const degree = this.parseOptionalArgument()?.trim();
|
|
3042
|
+
const value = this.parseRequiredArgument();
|
|
3043
|
+
if (degree === void 0 || degree === "2") return formatRoot(value);
|
|
3044
|
+
if (degree === "3") return formatRoot(value, "∛");
|
|
3045
|
+
if (degree === "4") return formatRoot(value, "∜");
|
|
3046
|
+
return `${formatScript(degree, "sup")}${formatRoot(value)}`;
|
|
3047
|
+
}
|
|
3048
|
+
if (command === "boxed" || command === "fbox") return `[${this.parseRequiredArgument().trim()}]`;
|
|
3049
|
+
if (command === "binom" || command === "dbinom" || command === "tbinom") return `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;
|
|
3050
|
+
const accent = ACCENTS[command];
|
|
3051
|
+
if (accent !== void 0) {
|
|
3052
|
+
const value = this.parseRequiredArgument();
|
|
3053
|
+
return Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;
|
|
3054
|
+
}
|
|
3055
|
+
if (command === "mathbb") {
|
|
3056
|
+
const value = this.parseRequiredArgument();
|
|
3057
|
+
return Array.from(value, (character) => BLACKBOARD[character] ?? character).join("");
|
|
3058
|
+
}
|
|
3059
|
+
if (command === "operatorname") {
|
|
3060
|
+
const starred = this.source[this.position] === "*";
|
|
3061
|
+
if (starred) this.position++;
|
|
3062
|
+
const operator = normalizeOutput(this.parseRequiredArgument()).trim();
|
|
3063
|
+
return this.parseOperator(operator, "bracket", starred, true);
|
|
3064
|
+
}
|
|
3065
|
+
if (command === "mod" || command === "bmod") return " mod ";
|
|
3066
|
+
if (command === "pmod" || command === "pod") {
|
|
3067
|
+
const value = this.parseRequiredArgument().trim();
|
|
3068
|
+
return command === "pmod" ? ` (mod ${value})` : ` (${value})`;
|
|
3069
|
+
}
|
|
3070
|
+
if (command === "overset" || command === "stackrel") {
|
|
3071
|
+
const upper = this.parseRequiredArgument();
|
|
3072
|
+
return `${this.parseRequiredArgument().trim()}${formatScript(upper, "sup")}`;
|
|
3073
|
+
}
|
|
3074
|
+
if (command === "underset") {
|
|
3075
|
+
const lower = this.parseRequiredArgument();
|
|
3076
|
+
return `${this.parseRequiredArgument().trim()}${formatScript(lower, "sub")}`;
|
|
3077
|
+
}
|
|
3078
|
+
if (PLAIN_WRAPPERS.has(command)) {
|
|
3079
|
+
const value = this.parseRequiredArgument();
|
|
3080
|
+
return command.startsWith("text") || command === "mbox" ? value : value.trim();
|
|
3081
|
+
}
|
|
3082
|
+
if (command === "begin") return this.parseEnvironment();
|
|
3083
|
+
if (command === "end") {
|
|
3084
|
+
this.supported = false;
|
|
3085
|
+
return "";
|
|
3086
|
+
}
|
|
3087
|
+
this.supported = false;
|
|
3088
|
+
return `\\${command}`;
|
|
3089
|
+
}
|
|
3090
|
+
parseOperator(operator, inlineLowerStyle, displayLimits, spaced = false) {
|
|
3091
|
+
let useDisplayLimits = displayLimits;
|
|
3092
|
+
let modifierPosition = this.position;
|
|
3093
|
+
while (modifierPosition < this.source.length && /[ \t]/.test(this.source[modifierPosition] ?? "")) modifierPosition++;
|
|
3094
|
+
const modifier = /^\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));
|
|
3095
|
+
if (modifier) {
|
|
3096
|
+
useDisplayLimits = modifier[1] === "limits";
|
|
3097
|
+
this.position = modifierPosition + modifier[0].length;
|
|
3098
|
+
}
|
|
3099
|
+
let lower;
|
|
3100
|
+
let upper;
|
|
3101
|
+
while (true) {
|
|
3102
|
+
let scriptPosition = this.position;
|
|
3103
|
+
while (scriptPosition < this.source.length && /[ \t]/.test(this.source[scriptPosition] ?? "")) scriptPosition++;
|
|
3104
|
+
const kind = this.source[scriptPosition];
|
|
3105
|
+
if (kind !== "_" && kind !== "^") break;
|
|
3106
|
+
this.position = scriptPosition + 1;
|
|
3107
|
+
const value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(" ", "");
|
|
3108
|
+
if (kind === "_") {
|
|
3109
|
+
if (lower !== void 0) this.supported = false;
|
|
3110
|
+
lower = value;
|
|
3111
|
+
} else {
|
|
3112
|
+
if (upper !== void 0) this.supported = false;
|
|
3113
|
+
upper = value;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (this.display && useDisplayLimits && (lower !== void 0 || upper !== void 0)) {
|
|
3117
|
+
const index = this.layoutNodes.push({
|
|
3118
|
+
type: "operator",
|
|
3119
|
+
operator,
|
|
3120
|
+
lower,
|
|
3121
|
+
upper
|
|
3122
|
+
}) - 1;
|
|
3123
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
3124
|
+
}
|
|
3125
|
+
let rendered = operator;
|
|
3126
|
+
if (lower !== void 0) rendered += inlineLowerStyle === "bracket" ? `[${lower}]` : formatScript(lower, "sub");
|
|
3127
|
+
if (upper !== void 0) rendered += formatScript(upper, "sup");
|
|
3128
|
+
return spaced ? ` ${rendered} ` : rendered;
|
|
3129
|
+
}
|
|
3130
|
+
parseRequiredArgument(stackFractions = true) {
|
|
3131
|
+
const previousStackFractions = this.stackFractions;
|
|
3132
|
+
this.stackFractions = previousStackFractions && stackFractions;
|
|
3133
|
+
const value = this.parseRequiredArgumentValue();
|
|
3134
|
+
this.stackFractions = previousStackFractions;
|
|
3135
|
+
return value;
|
|
3136
|
+
}
|
|
3137
|
+
parseRequiredArgumentValue() {
|
|
3138
|
+
while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) this.position++;
|
|
3139
|
+
if (this.position >= this.source.length) {
|
|
3140
|
+
this.supported = false;
|
|
3141
|
+
return "";
|
|
3142
|
+
}
|
|
3143
|
+
if (this.source[this.position] === "{") {
|
|
3144
|
+
this.position++;
|
|
3145
|
+
return this.parseSequence("}");
|
|
3146
|
+
}
|
|
3147
|
+
if (this.source[this.position] === "\\") return this.parseCommand();
|
|
3148
|
+
const value = this.source[this.position] ?? "";
|
|
3149
|
+
this.position++;
|
|
3150
|
+
return value;
|
|
3151
|
+
}
|
|
3152
|
+
parseOptionalArgument() {
|
|
3153
|
+
while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) this.position++;
|
|
3154
|
+
if (this.source[this.position] !== "[") return;
|
|
3155
|
+
const end = this.source.indexOf("]", this.position + 1);
|
|
3156
|
+
if (end < 0) {
|
|
3157
|
+
this.supported = false;
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
const value = this.source.slice(this.position + 1, end);
|
|
3161
|
+
this.position = end + 1;
|
|
3162
|
+
return this.renderNested(value);
|
|
3163
|
+
}
|
|
3164
|
+
readRawGroup() {
|
|
3165
|
+
while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) this.position++;
|
|
3166
|
+
if (this.source[this.position] !== "{") {
|
|
3167
|
+
this.supported = false;
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
const start = ++this.position;
|
|
3171
|
+
let depth = 1;
|
|
3172
|
+
while (this.position < this.source.length) {
|
|
3173
|
+
const character = this.source[this.position];
|
|
3174
|
+
if (character === "\\") {
|
|
3175
|
+
this.position += 2;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
if (character === "{") depth++;
|
|
3179
|
+
if (character === "}") depth--;
|
|
3180
|
+
if (depth === 0) {
|
|
3181
|
+
const value = this.source.slice(start, this.position);
|
|
3182
|
+
this.position++;
|
|
3183
|
+
return value;
|
|
3184
|
+
}
|
|
3185
|
+
this.position++;
|
|
3186
|
+
}
|
|
3187
|
+
this.supported = false;
|
|
3188
|
+
}
|
|
3189
|
+
splitEnvironmentRows(body) {
|
|
3190
|
+
return body.split(/\\\\(?:\[[^\]\n]*\])?/);
|
|
3191
|
+
}
|
|
3192
|
+
parseEnvironment() {
|
|
3193
|
+
const environment = this.readRawGroup();
|
|
3194
|
+
if (!environment) return "";
|
|
3195
|
+
const endMarker = `\\end{${environment}}`;
|
|
3196
|
+
const end = this.source.indexOf(endMarker, this.position);
|
|
3197
|
+
if (end < 0) {
|
|
3198
|
+
this.supported = false;
|
|
3199
|
+
return "";
|
|
3200
|
+
}
|
|
3201
|
+
const body = this.source.slice(this.position, end);
|
|
3202
|
+
this.position = end + endMarker.length;
|
|
3203
|
+
if (environment === "equation" || environment === "equation*" || environment === "displaymath") return this.renderNested(body).trim();
|
|
3204
|
+
if (environment === "aligned" || environment === "align" || environment === "align*" || environment === "alignedat" || environment === "alignat" || environment === "alignat*" || environment === "gather" || environment === "gathered" || environment === "multline" || environment === "multline*" || environment === "split") {
|
|
3205
|
+
const alignedAt = [
|
|
3206
|
+
"alignedat",
|
|
3207
|
+
"alignat",
|
|
3208
|
+
"alignat*"
|
|
3209
|
+
].includes(environment);
|
|
3210
|
+
const alignedBody = alignedAt ? body.replace(/^\s*\{[^}]*\}/, "") : body;
|
|
3211
|
+
return this.splitEnvironmentRows(alignedBody).map((row) => {
|
|
3212
|
+
const cells = row.split("&");
|
|
3213
|
+
const source = alignedAt ? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) => cells.slice(index * 2, index * 2 + 2).join("")).join(" ") : cells.join("");
|
|
3214
|
+
return this.renderNested(source).trim();
|
|
3215
|
+
}).filter(Boolean).join("\n");
|
|
3216
|
+
}
|
|
3217
|
+
if (environment === "cases" || environment === "cases*") {
|
|
3218
|
+
const rows = this.splitEnvironmentRows(body).map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim())).filter((row) => row.some(Boolean));
|
|
3219
|
+
return rows.map((row, index) => {
|
|
3220
|
+
const value = (row[0] ?? "").replace(/,\s*$/, "");
|
|
3221
|
+
const condition = row[1] ?? "";
|
|
3222
|
+
const delimiter = index === 0 ? "⎧" : index === rows.length - 1 ? "⎩" : "⎨";
|
|
3223
|
+
const conditionPrefix = /^(?:if|when|for|otherwise)\b/i.test(condition) ? " " : " if ";
|
|
3224
|
+
return `${delimiter} ${value}${condition ? `${conditionPrefix}${condition}` : ""}`;
|
|
3225
|
+
}).join("\n");
|
|
3226
|
+
}
|
|
3227
|
+
if ([
|
|
3228
|
+
"array",
|
|
3229
|
+
"matrix",
|
|
3230
|
+
"smallmatrix",
|
|
3231
|
+
"pmatrix",
|
|
3232
|
+
"bmatrix",
|
|
3233
|
+
"Bmatrix",
|
|
3234
|
+
"vmatrix",
|
|
3235
|
+
"Vmatrix"
|
|
3236
|
+
].includes(environment)) {
|
|
3237
|
+
const matrixBody = environment === "array" ? body.replace(/^\s*\{[^}]*\}/, "") : body;
|
|
3238
|
+
return this.renderMatrix(environment, matrixBody);
|
|
3239
|
+
}
|
|
3240
|
+
this.supported = false;
|
|
3241
|
+
return body;
|
|
3242
|
+
}
|
|
3243
|
+
renderMatrix(environment, body) {
|
|
3244
|
+
const matrix = this.splitEnvironmentRows(body).map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim())).filter((row) => row.some(Boolean));
|
|
3245
|
+
const columnCount = Math.max(0, ...matrix.map((row) => row.length));
|
|
3246
|
+
const columnWidths = Array.from({ length: columnCount }, (_, column) => Math.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? ""))));
|
|
3247
|
+
const rows = matrix.map((row) => Array.from({ length: columnCount }, (_, column) => {
|
|
3248
|
+
const cell = row[column] ?? "";
|
|
3249
|
+
return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
|
|
3250
|
+
}).join(" │ "));
|
|
3251
|
+
let lines;
|
|
3252
|
+
if (environment === "array" || environment === "matrix" || environment === "smallmatrix") lines = rows;
|
|
3253
|
+
else {
|
|
3254
|
+
const delimiter = {
|
|
3255
|
+
pmatrix: [
|
|
3256
|
+
"⎛",
|
|
3257
|
+
"⎞",
|
|
3258
|
+
"⎜",
|
|
3259
|
+
"⎟",
|
|
3260
|
+
"⎝",
|
|
3261
|
+
"⎠"
|
|
3262
|
+
],
|
|
3263
|
+
bmatrix: [
|
|
3264
|
+
"⎡",
|
|
3265
|
+
"⎤",
|
|
3266
|
+
"⎢",
|
|
3267
|
+
"⎥",
|
|
3268
|
+
"⎣",
|
|
3269
|
+
"⎦"
|
|
3270
|
+
],
|
|
3271
|
+
Bmatrix: [
|
|
3272
|
+
"⎧",
|
|
3273
|
+
"⎫",
|
|
3274
|
+
"⎨",
|
|
3275
|
+
"⎬",
|
|
3276
|
+
"⎩",
|
|
3277
|
+
"⎭"
|
|
3278
|
+
],
|
|
3279
|
+
vmatrix: [
|
|
3280
|
+
"│",
|
|
3281
|
+
"│",
|
|
3282
|
+
"│",
|
|
3283
|
+
"│",
|
|
3284
|
+
"│",
|
|
3285
|
+
"│"
|
|
3286
|
+
],
|
|
3287
|
+
Vmatrix: [
|
|
3288
|
+
"║",
|
|
3289
|
+
"║",
|
|
3290
|
+
"║",
|
|
3291
|
+
"║",
|
|
3292
|
+
"║",
|
|
3293
|
+
"║"
|
|
3294
|
+
]
|
|
3295
|
+
}[environment];
|
|
3296
|
+
if (!delimiter) {
|
|
3297
|
+
this.supported = false;
|
|
3298
|
+
return rows.join("\n");
|
|
3299
|
+
}
|
|
3300
|
+
lines = rows.map((row, index) => {
|
|
3301
|
+
return `${index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2]} ${row} ${index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3]}`;
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
if (lines.length <= 1) return lines[0] ?? "";
|
|
3305
|
+
const index = this.layoutNodes.push({
|
|
3306
|
+
type: "matrix",
|
|
3307
|
+
lines,
|
|
3308
|
+
baseline: 0
|
|
3309
|
+
}) - 1;
|
|
3310
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
3311
|
+
}
|
|
3312
|
+
renderNested(source, stackFractions = true) {
|
|
3313
|
+
const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
|
|
3314
|
+
if (rendered === void 0) {
|
|
3315
|
+
this.supported = false;
|
|
3316
|
+
return source;
|
|
3317
|
+
}
|
|
3318
|
+
return rendered;
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
/**
|
|
3322
|
+
* Render a basic LaTeX math expression as terminal-friendly Unicode text.
|
|
3323
|
+
* Returns undefined when the expression contains unsupported or malformed syntax.
|
|
3324
|
+
*/
|
|
3325
|
+
function renderLatex(source, options = {}) {
|
|
3326
|
+
const layoutNodes = [];
|
|
3327
|
+
const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
|
|
3328
|
+
if (rendered === void 0) return;
|
|
3329
|
+
if (layoutNodes.length === 0) return rendered.replaceAll(PROTECTED_SPACE, " ");
|
|
3330
|
+
const lines = renderLayout(rendered, layoutNodes).lines;
|
|
3331
|
+
const indentation = Math.min(...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length));
|
|
3332
|
+
return lines.map((line) => line.slice(indentation).trimEnd()).join("\n").trimEnd().replaceAll(PROTECTED_SPACE, " ");
|
|
3333
|
+
}
|
|
3334
|
+
//#endregion
|
|
3335
|
+
//#region src/compat/vendor/pi-tui-terminal-image.ts
|
|
3336
|
+
let cachedCapabilities = null;
|
|
3337
|
+
let cellDimensions = {
|
|
3338
|
+
widthPx: 9,
|
|
3339
|
+
heightPx: 18
|
|
3340
|
+
};
|
|
3341
|
+
function getCellDimensions() {
|
|
3342
|
+
return cellDimensions;
|
|
3343
|
+
}
|
|
3344
|
+
function setCellDimensions(dims) {
|
|
3345
|
+
cellDimensions = dims;
|
|
3346
|
+
}
|
|
3347
|
+
/**
|
|
3348
|
+
* Checks whether the attached tmux client forwards OSC 8 hyperlinks to the
|
|
3349
|
+
* outer terminal. tmux only re-emits them when its `client_termfeatures` lists
|
|
3350
|
+
* `hyperlinks`, and strips them otherwise. On any error fallbacks `false`.
|
|
3351
|
+
*/
|
|
3352
|
+
function probeTmuxHyperlinks() {
|
|
3353
|
+
try {
|
|
3354
|
+
return execSync("tmux display-message -p '#{client_termfeatures}'", {
|
|
3355
|
+
encoding: "utf8",
|
|
3356
|
+
timeout: 250,
|
|
3357
|
+
stdio: [
|
|
3358
|
+
"ignore",
|
|
3359
|
+
"pipe",
|
|
3360
|
+
"ignore"
|
|
3361
|
+
]
|
|
3362
|
+
}).split(",").map((feature) => feature.trim()).includes("hyperlinks");
|
|
3363
|
+
} catch {
|
|
3364
|
+
return false;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
function detectCapabilities(tmuxForwardsHyperlink = probeTmuxHyperlinks) {
|
|
3368
|
+
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || "";
|
|
3369
|
+
const terminalEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || "";
|
|
3370
|
+
const term = process.env.TERM?.toLowerCase() || "";
|
|
3371
|
+
const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
|
|
3372
|
+
const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit";
|
|
3373
|
+
const isWindowsConsole = process.platform === "win32";
|
|
3374
|
+
if (process.env.TMUX || term.startsWith("tmux")) return {
|
|
3375
|
+
images: null,
|
|
3376
|
+
trueColor: hasTrueColorHint,
|
|
3377
|
+
hyperlinks: tmuxForwardsHyperlink()
|
|
3378
|
+
};
|
|
3379
|
+
if (term.startsWith("screen")) return {
|
|
3380
|
+
images: null,
|
|
3381
|
+
trueColor: hasTrueColorHint,
|
|
3382
|
+
hyperlinks: false
|
|
3383
|
+
};
|
|
3384
|
+
if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") return {
|
|
3385
|
+
images: "kitty",
|
|
3386
|
+
trueColor: true,
|
|
3387
|
+
hyperlinks: true
|
|
3388
|
+
};
|
|
3389
|
+
if (termProgram === "ghostty" || term.includes("ghostty") || process.env.GHOSTTY_RESOURCES_DIR) return {
|
|
3390
|
+
images: "kitty",
|
|
3391
|
+
trueColor: true,
|
|
3392
|
+
hyperlinks: true
|
|
3393
|
+
};
|
|
3394
|
+
if (process.env.WEZTERM_PANE || termProgram === "wezterm") return {
|
|
3395
|
+
images: "kitty",
|
|
3396
|
+
trueColor: true,
|
|
3397
|
+
hyperlinks: true
|
|
3398
|
+
};
|
|
3399
|
+
if (termProgram === "warpterminal" || process.env.WARP_SESSION_ID || process.env.WARP_TERMINAL_SESSION_UUID) return {
|
|
3400
|
+
images: "kitty",
|
|
3401
|
+
trueColor: true,
|
|
3402
|
+
hyperlinks: true
|
|
3403
|
+
};
|
|
3404
|
+
if (process.env.ITERM_SESSION_ID || termProgram === "iterm.app") return {
|
|
3405
|
+
images: "iterm2",
|
|
3406
|
+
trueColor: true,
|
|
3407
|
+
hyperlinks: true
|
|
3408
|
+
};
|
|
3409
|
+
if (process.env.WT_SESSION) return {
|
|
3410
|
+
images: null,
|
|
3411
|
+
trueColor: true,
|
|
3412
|
+
hyperlinks: true
|
|
3413
|
+
};
|
|
3414
|
+
if (termProgram === "vscode") return {
|
|
3415
|
+
images: null,
|
|
3416
|
+
trueColor: true,
|
|
3417
|
+
hyperlinks: true
|
|
3418
|
+
};
|
|
3419
|
+
if (termProgram === "alacritty") return {
|
|
3420
|
+
images: null,
|
|
3421
|
+
trueColor: true,
|
|
3422
|
+
hyperlinks: true
|
|
3423
|
+
};
|
|
3424
|
+
if (terminalEmulator === "jetbrains-jediterm") return {
|
|
3425
|
+
images: null,
|
|
3426
|
+
trueColor: true,
|
|
3427
|
+
hyperlinks: false
|
|
3428
|
+
};
|
|
3429
|
+
if (isWindowsConsole) return {
|
|
3430
|
+
images: null,
|
|
3431
|
+
trueColor: true,
|
|
3432
|
+
hyperlinks: false
|
|
3433
|
+
};
|
|
3434
|
+
return {
|
|
3435
|
+
images: null,
|
|
3436
|
+
trueColor: hasTrueColorHint,
|
|
3437
|
+
hyperlinks: false
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
function getCapabilities() {
|
|
3441
|
+
if (!cachedCapabilities) cachedCapabilities = detectCapabilities();
|
|
3442
|
+
return cachedCapabilities;
|
|
3443
|
+
}
|
|
3444
|
+
function resetCapabilitiesCache() {
|
|
3445
|
+
cachedCapabilities = null;
|
|
3446
|
+
}
|
|
3447
|
+
/** Override the cached capabilities. Useful in tests to exercise both code paths. */
|
|
3448
|
+
function setCapabilities(caps) {
|
|
3449
|
+
cachedCapabilities = caps;
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* Generate a random image ID for Kitty graphics protocol.
|
|
3453
|
+
* Uses random IDs to avoid collisions between different module instances
|
|
3454
|
+
* (e.g., main app vs extensions).
|
|
3455
|
+
*/
|
|
3456
|
+
function allocateImageId() {
|
|
3457
|
+
return Math.floor(Math.random() * 4294967294) + 1;
|
|
3458
|
+
}
|
|
3459
|
+
function encodeKitty(base64Data, options = {}) {
|
|
3460
|
+
const CHUNK_SIZE = 4096;
|
|
3461
|
+
const params = [
|
|
3462
|
+
"a=T",
|
|
3463
|
+
"f=100",
|
|
3464
|
+
"q=2"
|
|
3465
|
+
];
|
|
3466
|
+
if (options.moveCursor === false) params.push("C=1");
|
|
3467
|
+
if (options.columns) params.push(`c=${options.columns}`);
|
|
3468
|
+
if (options.rows) params.push(`r=${options.rows}`);
|
|
3469
|
+
if (options.imageId) params.push(`i=${options.imageId}`);
|
|
3470
|
+
if (base64Data.length <= CHUNK_SIZE) return `\x1b_G${params.join(",")};${base64Data}\x1b\\`;
|
|
3471
|
+
const chunks = [];
|
|
3472
|
+
let offset = 0;
|
|
3473
|
+
let isFirst = true;
|
|
3474
|
+
while (offset < base64Data.length) {
|
|
3475
|
+
const chunk = base64Data.slice(offset, offset + CHUNK_SIZE);
|
|
3476
|
+
const isLast = offset + CHUNK_SIZE >= base64Data.length;
|
|
3477
|
+
if (isFirst) {
|
|
3478
|
+
chunks.push(`\x1b_G${params.join(",")},m=1;${chunk}\x1b\\`);
|
|
3479
|
+
isFirst = false;
|
|
3480
|
+
} else if (isLast) chunks.push(`\x1b_Gm=0;${chunk}\x1b\\`);
|
|
3481
|
+
else chunks.push(`\x1b_Gm=1;${chunk}\x1b\\`);
|
|
3482
|
+
offset += CHUNK_SIZE;
|
|
3483
|
+
}
|
|
3484
|
+
return chunks.join("");
|
|
3485
|
+
}
|
|
3486
|
+
/**
|
|
3487
|
+
* Delete a Kitty graphics image by ID.
|
|
3488
|
+
* Uses uppercase 'I' to also free the image data.
|
|
3489
|
+
*/
|
|
3490
|
+
function deleteKittyImage(imageId) {
|
|
3491
|
+
return `\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`;
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Delete all visible Kitty graphics images.
|
|
3495
|
+
* Uses uppercase 'A' to also free the image data.
|
|
3496
|
+
*/
|
|
3497
|
+
function deleteAllKittyImages() {
|
|
3498
|
+
return "\x1B_Ga=d,d=A,q=2\x1B\\";
|
|
3499
|
+
}
|
|
3500
|
+
function encodeITerm2(base64Data, options = {}) {
|
|
3501
|
+
const params = [`inline=${options.inline !== false ? 1 : 0}`, `size=${Buffer.byteLength(base64Data, "base64")}`];
|
|
3502
|
+
if (options.width !== void 0) params.push(`width=${options.width}`);
|
|
3503
|
+
if (options.height !== void 0) params.push(`height=${options.height}`);
|
|
3504
|
+
if (options.name) {
|
|
3505
|
+
const nameBase64 = Buffer.from(options.name).toString("base64");
|
|
3506
|
+
params.push(`name=${nameBase64}`);
|
|
3507
|
+
}
|
|
3508
|
+
if (options.preserveAspectRatio === false) params.push("preserveAspectRatio=0");
|
|
3509
|
+
return `\x1b]1337;File=${params.join(";")}:${base64Data}\x07`;
|
|
3510
|
+
}
|
|
3511
|
+
function calculateImageCellSize(imageDimensions, maxWidthCells, maxHeightCells, cellDimensions = {
|
|
3512
|
+
widthPx: 9,
|
|
3513
|
+
heightPx: 18
|
|
3514
|
+
}) {
|
|
3515
|
+
const maxWidth = Math.max(1, Math.floor(maxWidthCells));
|
|
3516
|
+
const maxHeight = maxHeightCells === void 0 ? void 0 : Math.max(1, Math.floor(maxHeightCells));
|
|
3517
|
+
const imageWidth = Math.max(1, imageDimensions.widthPx);
|
|
3518
|
+
const imageHeight = Math.max(1, imageDimensions.heightPx);
|
|
3519
|
+
const widthScale = maxWidth * cellDimensions.widthPx / imageWidth;
|
|
3520
|
+
const heightScale = maxHeight === void 0 ? widthScale : maxHeight * cellDimensions.heightPx / imageHeight;
|
|
3521
|
+
const scale = Math.min(widthScale, heightScale);
|
|
3522
|
+
const scaledWidthPx = imageWidth * scale;
|
|
3523
|
+
const scaledHeightPx = imageHeight * scale;
|
|
3524
|
+
const columns = Math.ceil(scaledWidthPx / cellDimensions.widthPx);
|
|
3525
|
+
const rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx);
|
|
3526
|
+
return {
|
|
3527
|
+
columns: Math.max(1, Math.min(maxWidth, columns)),
|
|
3528
|
+
rows: Math.max(1, maxHeight === void 0 ? rows : Math.min(maxHeight, rows))
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
function calculateImageRows(imageDimensions, targetWidthCells, cellDimensions = {
|
|
3532
|
+
widthPx: 9,
|
|
3533
|
+
heightPx: 18
|
|
3534
|
+
}) {
|
|
3535
|
+
return calculateImageCellSize(imageDimensions, targetWidthCells, void 0, cellDimensions).rows;
|
|
3536
|
+
}
|
|
3537
|
+
function getPngDimensions(base64Data) {
|
|
3538
|
+
try {
|
|
3539
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
3540
|
+
if (buffer.length < 24) return null;
|
|
3541
|
+
if (buffer[0] !== 137 || buffer[1] !== 80 || buffer[2] !== 78 || buffer[3] !== 71) return null;
|
|
3542
|
+
return {
|
|
3543
|
+
widthPx: buffer.readUInt32BE(16),
|
|
3544
|
+
heightPx: buffer.readUInt32BE(20)
|
|
3545
|
+
};
|
|
3546
|
+
} catch {
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
function getJpegDimensions(base64Data) {
|
|
3551
|
+
try {
|
|
3552
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
3553
|
+
if (buffer.length < 2) return null;
|
|
3554
|
+
if (buffer[0] !== 255 || buffer[1] !== 216) return null;
|
|
3555
|
+
let offset = 2;
|
|
3556
|
+
while (offset < buffer.length - 9) {
|
|
3557
|
+
if (buffer[offset] !== 255) {
|
|
3558
|
+
offset++;
|
|
3559
|
+
continue;
|
|
3560
|
+
}
|
|
3561
|
+
const marker = buffer[offset + 1];
|
|
3562
|
+
if (marker >= 192 && marker <= 194) {
|
|
3563
|
+
const height = buffer.readUInt16BE(offset + 5);
|
|
3564
|
+
return {
|
|
3565
|
+
widthPx: buffer.readUInt16BE(offset + 7),
|
|
3566
|
+
heightPx: height
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
if (offset + 3 >= buffer.length) return null;
|
|
3570
|
+
const length = buffer.readUInt16BE(offset + 2);
|
|
3571
|
+
if (length < 2) return null;
|
|
3572
|
+
offset += 2 + length;
|
|
3573
|
+
}
|
|
3574
|
+
return null;
|
|
3575
|
+
} catch {
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
function getGifDimensions(base64Data) {
|
|
3580
|
+
try {
|
|
3581
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
3582
|
+
if (buffer.length < 10) return null;
|
|
3583
|
+
const sig = buffer.slice(0, 6).toString("ascii");
|
|
3584
|
+
if (sig !== "GIF87a" && sig !== "GIF89a") return null;
|
|
3585
|
+
return {
|
|
3586
|
+
widthPx: buffer.readUInt16LE(6),
|
|
3587
|
+
heightPx: buffer.readUInt16LE(8)
|
|
3588
|
+
};
|
|
3589
|
+
} catch {
|
|
3590
|
+
return null;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
function getWebpDimensions(base64Data) {
|
|
3594
|
+
try {
|
|
3595
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
3596
|
+
if (buffer.length < 30) return null;
|
|
3597
|
+
const riff = buffer.slice(0, 4).toString("ascii");
|
|
3598
|
+
const webp = buffer.slice(8, 12).toString("ascii");
|
|
3599
|
+
if (riff !== "RIFF" || webp !== "WEBP") return null;
|
|
3600
|
+
const chunk = buffer.slice(12, 16).toString("ascii");
|
|
3601
|
+
if (chunk === "VP8 ") {
|
|
3602
|
+
if (buffer.length < 30) return null;
|
|
3603
|
+
return {
|
|
3604
|
+
widthPx: buffer.readUInt16LE(26) & 16383,
|
|
3605
|
+
heightPx: buffer.readUInt16LE(28) & 16383
|
|
3606
|
+
};
|
|
3607
|
+
} else if (chunk === "VP8L") {
|
|
3608
|
+
if (buffer.length < 25) return null;
|
|
3609
|
+
const bits = buffer.readUInt32LE(21);
|
|
3610
|
+
return {
|
|
3611
|
+
widthPx: (bits & 16383) + 1,
|
|
3612
|
+
heightPx: (bits >> 14 & 16383) + 1
|
|
3613
|
+
};
|
|
3614
|
+
} else if (chunk === "VP8X") {
|
|
3615
|
+
if (buffer.length < 30) return null;
|
|
3616
|
+
return {
|
|
3617
|
+
widthPx: (buffer[24] | buffer[25] << 8 | buffer[26] << 16) + 1,
|
|
3618
|
+
heightPx: (buffer[27] | buffer[28] << 8 | buffer[29] << 16) + 1
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
return null;
|
|
3622
|
+
} catch {
|
|
3623
|
+
return null;
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
function getImageDimensions(base64Data, mimeType) {
|
|
3627
|
+
if (mimeType === "image/png") return getPngDimensions(base64Data);
|
|
3628
|
+
if (mimeType === "image/jpeg") return getJpegDimensions(base64Data);
|
|
3629
|
+
if (mimeType === "image/gif") return getGifDimensions(base64Data);
|
|
3630
|
+
if (mimeType === "image/webp") return getWebpDimensions(base64Data);
|
|
3631
|
+
return null;
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Wrap text in an OSC 8 hyperlink sequence.
|
|
3635
|
+
* The text is rendered as a clickable hyperlink in terminals that support OSC 8
|
|
3636
|
+
* (Ghostty, Kitty, WezTerm, iTerm2, VSCode, and others).
|
|
3637
|
+
* In terminals that do not support OSC 8, the escape sequences are ignored
|
|
3638
|
+
* and only the plain text is displayed.
|
|
3639
|
+
*
|
|
3640
|
+
* @param text - The visible text to display
|
|
3641
|
+
* @param url - The URL to link to
|
|
3642
|
+
*/
|
|
3643
|
+
function hyperlink(text, url) {
|
|
3644
|
+
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
3645
|
+
}
|
|
3646
|
+
/** Shorten home-prefixed absolute paths to ~/... for compact display. */
|
|
3647
|
+
function shortenImagePath(filename) {
|
|
3648
|
+
const home = homedir();
|
|
3649
|
+
if (home && (filename === home || filename.startsWith(`${home}/`) || filename.startsWith(`${home}\\`))) return `~${filename.slice(home.length)}`;
|
|
3650
|
+
return filename;
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Text fallback when the terminal cannot render inline images.
|
|
3654
|
+
* Absolute paths are shown shortened (~/...) and, when OSC 8 hyperlinks are
|
|
3655
|
+
* available, linked to file:// so the full path remains openable.
|
|
3656
|
+
*/
|
|
3657
|
+
function imageFallback(mimeType, dimensions, filename) {
|
|
3658
|
+
const parts = [];
|
|
3659
|
+
if (filename) {
|
|
3660
|
+
const display = shortenImagePath(filename);
|
|
3661
|
+
if (getCapabilities().hyperlinks && isAbsolute(filename)) parts.push(hyperlink(display, pathToFileURL(filename).href));
|
|
3662
|
+
else parts.push(display);
|
|
3663
|
+
}
|
|
3664
|
+
parts.push(`[${mimeType}]`);
|
|
3665
|
+
if (dimensions) parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`);
|
|
3666
|
+
return `[Image: ${parts.join(" ")}]`;
|
|
3667
|
+
}
|
|
3668
|
+
//#endregion
|
|
3669
|
+
//#region src/compat/vendor/pi-tui-autocomplete.ts
|
|
3670
|
+
const PATH_DELIMITERS = /* @__PURE__ */ new Set([
|
|
3671
|
+
" ",
|
|
3672
|
+
" ",
|
|
3673
|
+
"\"",
|
|
3674
|
+
"'",
|
|
3675
|
+
"="
|
|
3676
|
+
]);
|
|
3677
|
+
function toDisplayPath(value) {
|
|
3678
|
+
return value.replace(/\\/g, "/");
|
|
3679
|
+
}
|
|
3680
|
+
function escapeRegex(value) {
|
|
3681
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3682
|
+
}
|
|
3683
|
+
function buildFdPathQuery(query) {
|
|
3684
|
+
const normalized = toDisplayPath(query);
|
|
3685
|
+
if (!normalized.includes("/")) return normalized;
|
|
3686
|
+
const hasTrailingSeparator = normalized.endsWith("/");
|
|
3687
|
+
const trimmed = normalized.replace(/^\/+|\/+$/g, "");
|
|
3688
|
+
if (!trimmed) return normalized;
|
|
3689
|
+
const separatorPattern = "[\\\\/]";
|
|
3690
|
+
const segments = trimmed.split("/").filter(Boolean).map((segment) => escapeRegex(segment));
|
|
3691
|
+
if (segments.length === 0) return normalized;
|
|
3692
|
+
let pattern = segments.join(separatorPattern);
|
|
3693
|
+
if (hasTrailingSeparator) pattern += separatorPattern;
|
|
3694
|
+
return pattern;
|
|
3695
|
+
}
|
|
3696
|
+
function findLastDelimiter(text) {
|
|
3697
|
+
for (let i = text.length - 1; i >= 0; i -= 1) if (PATH_DELIMITERS.has(text[i] ?? "")) return i;
|
|
3698
|
+
return -1;
|
|
3699
|
+
}
|
|
3700
|
+
function findUnclosedQuoteStart(text) {
|
|
3701
|
+
let inQuotes = false;
|
|
3702
|
+
let quoteStart = -1;
|
|
3703
|
+
for (let i = 0; i < text.length; i += 1) if (text[i] === "\"") {
|
|
3704
|
+
inQuotes = !inQuotes;
|
|
3705
|
+
if (inQuotes) quoteStart = i;
|
|
3706
|
+
}
|
|
3707
|
+
return inQuotes ? quoteStart : null;
|
|
3708
|
+
}
|
|
3709
|
+
function isTokenStart(text, index) {
|
|
3710
|
+
return index === 0 || PATH_DELIMITERS.has(text[index - 1] ?? "");
|
|
3711
|
+
}
|
|
3712
|
+
function extractQuotedPrefix(text) {
|
|
3713
|
+
const quoteStart = findUnclosedQuoteStart(text);
|
|
3714
|
+
if (quoteStart === null) return null;
|
|
3715
|
+
if (quoteStart > 0 && text[quoteStart - 1] === "@") {
|
|
3716
|
+
if (!isTokenStart(text, quoteStart - 1)) return null;
|
|
3717
|
+
return text.slice(quoteStart - 1);
|
|
3718
|
+
}
|
|
3719
|
+
if (!isTokenStart(text, quoteStart)) return null;
|
|
3720
|
+
return text.slice(quoteStart);
|
|
3721
|
+
}
|
|
3722
|
+
function parsePathPrefix(prefix) {
|
|
3723
|
+
if (prefix.startsWith("@\"")) return {
|
|
3724
|
+
rawPrefix: prefix.slice(2),
|
|
3725
|
+
isAtPrefix: true,
|
|
3726
|
+
isQuotedPrefix: true
|
|
3727
|
+
};
|
|
3728
|
+
if (prefix.startsWith("\"")) return {
|
|
3729
|
+
rawPrefix: prefix.slice(1),
|
|
3730
|
+
isAtPrefix: false,
|
|
3731
|
+
isQuotedPrefix: true
|
|
3732
|
+
};
|
|
3733
|
+
if (prefix.startsWith("@")) return {
|
|
3734
|
+
rawPrefix: prefix.slice(1),
|
|
3735
|
+
isAtPrefix: true,
|
|
3736
|
+
isQuotedPrefix: false
|
|
3737
|
+
};
|
|
3738
|
+
return {
|
|
3739
|
+
rawPrefix: prefix,
|
|
3740
|
+
isAtPrefix: false,
|
|
3741
|
+
isQuotedPrefix: false
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function buildCompletionValue(path, options) {
|
|
3745
|
+
const needsQuotes = options.isQuotedPrefix || path.includes(" ");
|
|
3746
|
+
const prefix = options.isAtPrefix ? "@" : "";
|
|
3747
|
+
if (!needsQuotes) return `${prefix}${path}`;
|
|
3748
|
+
return `${`${prefix}"`}${path}"`;
|
|
3749
|
+
}
|
|
3750
|
+
async function walkDirectoryWithFd(baseDir, fdPath, query, maxResults, signal) {
|
|
3751
|
+
const args = [
|
|
3752
|
+
"--base-directory",
|
|
3753
|
+
baseDir,
|
|
3754
|
+
"--max-results",
|
|
3755
|
+
String(maxResults),
|
|
3756
|
+
"--type",
|
|
3757
|
+
"f",
|
|
3758
|
+
"--type",
|
|
3759
|
+
"d",
|
|
3760
|
+
"--follow",
|
|
3761
|
+
"--hidden",
|
|
3762
|
+
"--exclude",
|
|
3763
|
+
".git",
|
|
3764
|
+
"--exclude",
|
|
3765
|
+
".git/*",
|
|
3766
|
+
"--exclude",
|
|
3767
|
+
".git/**"
|
|
3768
|
+
];
|
|
3769
|
+
if (toDisplayPath(query).includes("/")) args.push("--full-path");
|
|
3770
|
+
if (query) args.push(buildFdPathQuery(query));
|
|
3771
|
+
return await new Promise((resolve) => {
|
|
3772
|
+
if (signal.aborted) {
|
|
3773
|
+
resolve([]);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
const child = spawn(fdPath, args, { stdio: [
|
|
3777
|
+
"ignore",
|
|
3778
|
+
"pipe",
|
|
3779
|
+
"pipe"
|
|
3780
|
+
] });
|
|
3781
|
+
let stdout = "";
|
|
3782
|
+
let resolved = false;
|
|
3783
|
+
const finish = (results) => {
|
|
3784
|
+
if (resolved) return;
|
|
3785
|
+
resolved = true;
|
|
3786
|
+
signal.removeEventListener("abort", onAbort);
|
|
3787
|
+
resolve(results);
|
|
3788
|
+
};
|
|
3789
|
+
const onAbort = () => {
|
|
3790
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
3791
|
+
};
|
|
3792
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3793
|
+
child.stdout.setEncoding("utf-8");
|
|
3794
|
+
child.stdout.on("data", (chunk) => {
|
|
3795
|
+
stdout += chunk;
|
|
3796
|
+
});
|
|
3797
|
+
child.on("error", () => {
|
|
3798
|
+
finish([]);
|
|
3799
|
+
});
|
|
3800
|
+
child.on("close", (code) => {
|
|
3801
|
+
if (signal.aborted || code !== 0 || !stdout) {
|
|
3802
|
+
finish([]);
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
3806
|
+
const results = [];
|
|
3807
|
+
for (const line of lines) {
|
|
3808
|
+
const displayLine = toDisplayPath(line);
|
|
3809
|
+
const hasTrailingSeparator = displayLine.endsWith("/");
|
|
3810
|
+
const normalizedPath = hasTrailingSeparator ? displayLine.slice(0, -1) : displayLine;
|
|
3811
|
+
if (normalizedPath === ".git" || normalizedPath.startsWith(".git/") || normalizedPath.includes("/.git/")) continue;
|
|
3812
|
+
results.push({
|
|
3813
|
+
path: displayLine,
|
|
3814
|
+
isDirectory: hasTrailingSeparator
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
finish(results);
|
|
3818
|
+
});
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
var CombinedAutocompleteProvider = class {
|
|
3822
|
+
commands;
|
|
3823
|
+
basePath;
|
|
3824
|
+
fdPath;
|
|
3825
|
+
constructor(commands = [], basePath, fdPath = null) {
|
|
3826
|
+
this.commands = commands;
|
|
3827
|
+
this.basePath = basePath;
|
|
3828
|
+
this.fdPath = fdPath;
|
|
3829
|
+
}
|
|
3830
|
+
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
|
3831
|
+
const textBeforeCursor = (lines[cursorLine] || "").slice(0, cursorCol);
|
|
3832
|
+
const atPrefix = this.extractAtPrefix(textBeforeCursor);
|
|
3833
|
+
if (atPrefix) {
|
|
3834
|
+
const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
|
|
3835
|
+
const suggestions = await this.getFuzzyFileSuggestions(rawPrefix, {
|
|
3836
|
+
isQuotedPrefix,
|
|
3837
|
+
signal: options.signal
|
|
3838
|
+
});
|
|
3839
|
+
if (suggestions.length === 0) return null;
|
|
3840
|
+
return {
|
|
3841
|
+
items: suggestions,
|
|
3842
|
+
prefix: atPrefix
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
if (!options.force && textBeforeCursor.startsWith("/")) {
|
|
3846
|
+
const spaceIndex = textBeforeCursor.indexOf(" ");
|
|
3847
|
+
if (spaceIndex === -1) {
|
|
3848
|
+
const prefix = textBeforeCursor.slice(1);
|
|
3849
|
+
const filtered = fuzzyFilter(this.commands.map((cmd) => {
|
|
3850
|
+
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
3851
|
+
const hint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : void 0;
|
|
3852
|
+
const desc = cmd.description ?? "";
|
|
3853
|
+
return {
|
|
3854
|
+
name,
|
|
3855
|
+
label: name,
|
|
3856
|
+
description: (hint ? desc ? `${hint} — ${desc}` : hint : desc) || void 0
|
|
3857
|
+
};
|
|
3858
|
+
}), prefix, (item) => item.name).map((item) => ({
|
|
3859
|
+
value: item.name,
|
|
3860
|
+
label: item.label,
|
|
3861
|
+
...item.description && { description: item.description }
|
|
3862
|
+
}));
|
|
3863
|
+
if (filtered.length === 0) return null;
|
|
3864
|
+
return {
|
|
3865
|
+
items: filtered,
|
|
3866
|
+
prefix: textBeforeCursor
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
const commandName = textBeforeCursor.slice(1, spaceIndex);
|
|
3870
|
+
const argumentText = textBeforeCursor.slice(spaceIndex + 1);
|
|
3871
|
+
const command = this.commands.find((cmd) => {
|
|
3872
|
+
return ("name" in cmd ? cmd.name : cmd.value) === commandName;
|
|
3873
|
+
});
|
|
3874
|
+
if (!command || !("getArgumentCompletions" in command) || !command.getArgumentCompletions) return null;
|
|
3875
|
+
const argumentSuggestions = await command.getArgumentCompletions(argumentText);
|
|
3876
|
+
if (!Array.isArray(argumentSuggestions) || argumentSuggestions.length === 0) return null;
|
|
3877
|
+
return {
|
|
3878
|
+
items: argumentSuggestions,
|
|
3879
|
+
prefix: argumentText
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
const pathMatch = this.extractPathPrefix(textBeforeCursor, options.force ?? false);
|
|
3883
|
+
if (pathMatch === null) return null;
|
|
3884
|
+
const suggestions = this.getFileSuggestions(pathMatch);
|
|
3885
|
+
if (suggestions.length === 0) return null;
|
|
3886
|
+
return {
|
|
3887
|
+
items: suggestions,
|
|
3888
|
+
prefix: pathMatch
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
3892
|
+
const currentLine = lines[cursorLine] || "";
|
|
3893
|
+
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
3894
|
+
const afterCursor = currentLine.slice(cursorCol);
|
|
3895
|
+
const isQuotedPrefix = prefix.startsWith("\"") || prefix.startsWith("@\"");
|
|
3896
|
+
const hasLeadingQuoteAfterCursor = afterCursor.startsWith("\"");
|
|
3897
|
+
const hasTrailingQuoteInItem = item.value.endsWith("\"");
|
|
3898
|
+
const adjustedAfterCursor = isQuotedPrefix && hasTrailingQuoteInItem && hasLeadingQuoteAfterCursor ? afterCursor.slice(1) : afterCursor;
|
|
3899
|
+
if (prefix.startsWith("/") && beforePrefix.trim() === "" && !prefix.slice(1).includes("/")) {
|
|
3900
|
+
const newLine = `${beforePrefix}/${item.value} ${adjustedAfterCursor}`;
|
|
3901
|
+
const newLines = [...lines];
|
|
3902
|
+
newLines[cursorLine] = newLine;
|
|
3903
|
+
return {
|
|
3904
|
+
lines: newLines,
|
|
3905
|
+
cursorLine,
|
|
3906
|
+
cursorCol: beforePrefix.length + item.value.length + 2
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
if (prefix.startsWith("@")) {
|
|
3910
|
+
const isDirectory = item.label.endsWith("/");
|
|
3911
|
+
const suffix = isDirectory ? "" : " ";
|
|
3912
|
+
const newLine = `${beforePrefix + item.value}${suffix}${adjustedAfterCursor}`;
|
|
3913
|
+
const newLines = [...lines];
|
|
3914
|
+
newLines[cursorLine] = newLine;
|
|
3915
|
+
const hasTrailingQuote = item.value.endsWith("\"");
|
|
3916
|
+
const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
|
|
3917
|
+
return {
|
|
3918
|
+
lines: newLines,
|
|
3919
|
+
cursorLine,
|
|
3920
|
+
cursorCol: beforePrefix.length + cursorOffset + suffix.length
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
3924
|
+
if (textBeforeCursor.includes("/") && textBeforeCursor.includes(" ")) {
|
|
3925
|
+
const newLine = beforePrefix + item.value + adjustedAfterCursor;
|
|
3926
|
+
const newLines = [...lines];
|
|
3927
|
+
newLines[cursorLine] = newLine;
|
|
3928
|
+
const isDirectory = item.label.endsWith("/");
|
|
3929
|
+
const hasTrailingQuote = item.value.endsWith("\"");
|
|
3930
|
+
const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
|
|
3931
|
+
return {
|
|
3932
|
+
lines: newLines,
|
|
3933
|
+
cursorLine,
|
|
3934
|
+
cursorCol: beforePrefix.length + cursorOffset
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
const newLine = beforePrefix + item.value + adjustedAfterCursor;
|
|
3938
|
+
const newLines = [...lines];
|
|
3939
|
+
newLines[cursorLine] = newLine;
|
|
3940
|
+
const isDirectory = item.label.endsWith("/");
|
|
3941
|
+
const hasTrailingQuote = item.value.endsWith("\"");
|
|
3942
|
+
const cursorOffset = isDirectory && hasTrailingQuote ? item.value.length - 1 : item.value.length;
|
|
3943
|
+
return {
|
|
3944
|
+
lines: newLines,
|
|
3945
|
+
cursorLine,
|
|
3946
|
+
cursorCol: beforePrefix.length + cursorOffset
|
|
3947
|
+
};
|
|
3948
|
+
}
|
|
3949
|
+
extractAtPrefix(text) {
|
|
3950
|
+
const quotedPrefix = extractQuotedPrefix(text);
|
|
3951
|
+
if (quotedPrefix?.startsWith("@\"")) return quotedPrefix;
|
|
3952
|
+
const lastDelimiterIndex = findLastDelimiter(text);
|
|
3953
|
+
const tokenStart = lastDelimiterIndex === -1 ? 0 : lastDelimiterIndex + 1;
|
|
3954
|
+
if (text[tokenStart] === "@") return text.slice(tokenStart);
|
|
3955
|
+
return null;
|
|
3956
|
+
}
|
|
3957
|
+
extractPathPrefix(text, forceExtract = false) {
|
|
3958
|
+
const quotedPrefix = extractQuotedPrefix(text);
|
|
3959
|
+
if (quotedPrefix) return quotedPrefix;
|
|
3960
|
+
const lastDelimiterIndex = findLastDelimiter(text);
|
|
3961
|
+
const pathPrefix = lastDelimiterIndex === -1 ? text : text.slice(lastDelimiterIndex + 1);
|
|
3962
|
+
if (forceExtract) return pathPrefix;
|
|
3963
|
+
if (pathPrefix.includes("/") || pathPrefix.startsWith(".") || pathPrefix.startsWith("~/")) return pathPrefix;
|
|
3964
|
+
if (pathPrefix === "" && text.endsWith(" ")) return pathPrefix;
|
|
3965
|
+
return null;
|
|
3966
|
+
}
|
|
3967
|
+
expandHomePath(path) {
|
|
3968
|
+
if (path.startsWith("~/")) {
|
|
3969
|
+
const expandedPath = join$1(homedir$1(), path.slice(2));
|
|
3970
|
+
return path.endsWith("/") && !expandedPath.endsWith("/") ? `${expandedPath}/` : expandedPath;
|
|
3971
|
+
} else if (path === "~") return homedir$1();
|
|
3972
|
+
return path;
|
|
3973
|
+
}
|
|
3974
|
+
resolveScopedFuzzyQuery(rawQuery) {
|
|
3975
|
+
const normalizedQuery = toDisplayPath(rawQuery);
|
|
3976
|
+
const slashIndex = normalizedQuery.lastIndexOf("/");
|
|
3977
|
+
if (slashIndex === -1) return null;
|
|
3978
|
+
const displayBase = normalizedQuery.slice(0, slashIndex + 1);
|
|
3979
|
+
const query = normalizedQuery.slice(slashIndex + 1);
|
|
3980
|
+
let baseDir;
|
|
3981
|
+
if (displayBase.startsWith("~/")) baseDir = this.expandHomePath(displayBase);
|
|
3982
|
+
else if (displayBase.startsWith("/")) baseDir = displayBase;
|
|
3983
|
+
else baseDir = join$1(this.basePath, displayBase);
|
|
3984
|
+
try {
|
|
3985
|
+
if (!statSync(baseDir).isDirectory()) return null;
|
|
3986
|
+
} catch {
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
return {
|
|
3990
|
+
baseDir,
|
|
3991
|
+
query,
|
|
3992
|
+
displayBase
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
scopedPathForDisplay(displayBase, relativePath) {
|
|
3996
|
+
const normalizedRelativePath = toDisplayPath(relativePath);
|
|
3997
|
+
if (displayBase === "/") return `/${normalizedRelativePath}`;
|
|
3998
|
+
return `${toDisplayPath(displayBase)}${normalizedRelativePath}`;
|
|
3999
|
+
}
|
|
4000
|
+
getFileSuggestions(prefix) {
|
|
4001
|
+
try {
|
|
4002
|
+
let searchDir;
|
|
4003
|
+
let searchPrefix;
|
|
4004
|
+
const { rawPrefix, isAtPrefix, isQuotedPrefix } = parsePathPrefix(prefix);
|
|
4005
|
+
let expandedPrefix = rawPrefix;
|
|
4006
|
+
if (expandedPrefix.startsWith("~")) expandedPrefix = this.expandHomePath(expandedPrefix);
|
|
4007
|
+
if (rawPrefix === "" || rawPrefix === "./" || rawPrefix === "../" || rawPrefix === "~" || rawPrefix === "~/" || rawPrefix === "/" || isAtPrefix && rawPrefix === "") {
|
|
4008
|
+
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) searchDir = expandedPrefix;
|
|
4009
|
+
else searchDir = join$1(this.basePath, expandedPrefix);
|
|
4010
|
+
searchPrefix = "";
|
|
4011
|
+
} else if (rawPrefix.endsWith("/")) {
|
|
4012
|
+
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) searchDir = expandedPrefix;
|
|
4013
|
+
else searchDir = join$1(this.basePath, expandedPrefix);
|
|
4014
|
+
searchPrefix = "";
|
|
4015
|
+
} else {
|
|
4016
|
+
const dir = dirname$1(expandedPrefix);
|
|
4017
|
+
const file = basename$1(expandedPrefix);
|
|
4018
|
+
if (rawPrefix.startsWith("~") || expandedPrefix.startsWith("/")) searchDir = dir;
|
|
4019
|
+
else searchDir = join$1(this.basePath, dir);
|
|
4020
|
+
searchPrefix = file;
|
|
4021
|
+
}
|
|
4022
|
+
const entries = readdirSync(searchDir, { withFileTypes: true });
|
|
4023
|
+
const suggestions = [];
|
|
4024
|
+
for (const entry of entries) {
|
|
4025
|
+
if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) continue;
|
|
4026
|
+
let isDirectory = entry.isDirectory();
|
|
4027
|
+
if (!isDirectory && entry.isSymbolicLink()) try {
|
|
4028
|
+
const fullPath = join$1(searchDir, entry.name);
|
|
4029
|
+
isDirectory = statSync(fullPath).isDirectory();
|
|
4030
|
+
} catch {}
|
|
4031
|
+
let relativePath;
|
|
4032
|
+
const name = entry.name;
|
|
4033
|
+
const displayPrefix = rawPrefix;
|
|
4034
|
+
if (displayPrefix.endsWith("/")) relativePath = displayPrefix + name;
|
|
4035
|
+
else if (displayPrefix.includes("/") || displayPrefix.includes("\\")) {
|
|
4036
|
+
if (displayPrefix.startsWith("~/")) {
|
|
4037
|
+
const homeRelativeDir = displayPrefix.slice(2);
|
|
4038
|
+
const dir = dirname$1(homeRelativeDir);
|
|
4039
|
+
relativePath = `~/${dir === "." ? name : join$1(dir, name)}`;
|
|
4040
|
+
} else if (displayPrefix.startsWith("/")) {
|
|
4041
|
+
const dir = dirname$1(displayPrefix);
|
|
4042
|
+
if (dir === "/") relativePath = `/${name}`;
|
|
4043
|
+
else relativePath = `${dir}/${name}`;
|
|
4044
|
+
} else {
|
|
4045
|
+
relativePath = join$1(dirname$1(displayPrefix), name);
|
|
4046
|
+
if (displayPrefix.startsWith("./") && !relativePath.startsWith("./")) relativePath = `./${relativePath}`;
|
|
4047
|
+
}
|
|
4048
|
+
} else if (displayPrefix.startsWith("~")) relativePath = `~/${name}`;
|
|
4049
|
+
else relativePath = name;
|
|
4050
|
+
relativePath = toDisplayPath(relativePath);
|
|
4051
|
+
const value = buildCompletionValue(isDirectory ? `${relativePath}/` : relativePath, {
|
|
4052
|
+
isDirectory,
|
|
4053
|
+
isAtPrefix,
|
|
4054
|
+
isQuotedPrefix
|
|
4055
|
+
});
|
|
4056
|
+
suggestions.push({
|
|
4057
|
+
value,
|
|
4058
|
+
label: name + (isDirectory ? "/" : "")
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
suggestions.sort((a, b) => {
|
|
4062
|
+
const aIsDir = a.value.endsWith("/");
|
|
4063
|
+
const bIsDir = b.value.endsWith("/");
|
|
4064
|
+
if (aIsDir && !bIsDir) return -1;
|
|
4065
|
+
if (!aIsDir && bIsDir) return 1;
|
|
4066
|
+
return a.label.localeCompare(b.label);
|
|
4067
|
+
});
|
|
4068
|
+
return suggestions;
|
|
4069
|
+
} catch (_e) {
|
|
4070
|
+
return [];
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
scoreEntry(filePath, query, isDirectory) {
|
|
4074
|
+
const lowerFileName = basename$1(filePath).toLowerCase();
|
|
4075
|
+
const lowerQuery = query.toLowerCase();
|
|
4076
|
+
let score = 0;
|
|
4077
|
+
if (lowerFileName === lowerQuery) score = 100;
|
|
4078
|
+
else if (lowerFileName.startsWith(lowerQuery)) score = 80;
|
|
4079
|
+
else if (lowerFileName.includes(lowerQuery)) score = 50;
|
|
4080
|
+
else if (filePath.toLowerCase().includes(lowerQuery)) score = 30;
|
|
4081
|
+
if (isDirectory && score > 0) score += 10;
|
|
4082
|
+
return score;
|
|
4083
|
+
}
|
|
4084
|
+
async getFuzzyFileSuggestions(query, options) {
|
|
4085
|
+
if (!this.fdPath || options.signal.aborted) return [];
|
|
4086
|
+
try {
|
|
4087
|
+
const scopedQuery = this.resolveScopedFuzzyQuery(query);
|
|
4088
|
+
const fdBaseDir = scopedQuery?.baseDir ?? this.basePath;
|
|
4089
|
+
const fdQuery = scopedQuery?.query ?? query;
|
|
4090
|
+
const entries = await walkDirectoryWithFd(fdBaseDir, this.fdPath, fdQuery, 100, options.signal);
|
|
4091
|
+
if (options.signal.aborted) return [];
|
|
4092
|
+
const scoredEntries = entries.map((entry) => ({
|
|
4093
|
+
...entry,
|
|
4094
|
+
score: fdQuery ? this.scoreEntry(entry.path, fdQuery, entry.isDirectory) : 1
|
|
4095
|
+
})).filter((entry) => entry.score > 0);
|
|
4096
|
+
scoredEntries.sort((a, b) => b.score - a.score);
|
|
4097
|
+
const topEntries = scoredEntries.slice(0, 20);
|
|
4098
|
+
const suggestions = [];
|
|
4099
|
+
for (const { path: entryPath, isDirectory } of topEntries) {
|
|
4100
|
+
const pathWithoutSlash = isDirectory ? entryPath.slice(0, -1) : entryPath;
|
|
4101
|
+
const displayPath = scopedQuery ? this.scopedPathForDisplay(scopedQuery.displayBase, pathWithoutSlash) : pathWithoutSlash;
|
|
4102
|
+
const entryName = basename$1(pathWithoutSlash);
|
|
4103
|
+
const value = buildCompletionValue(isDirectory ? `${displayPath}/` : displayPath, {
|
|
4104
|
+
isDirectory,
|
|
4105
|
+
isAtPrefix: true,
|
|
4106
|
+
isQuotedPrefix: options.isQuotedPrefix
|
|
4107
|
+
});
|
|
4108
|
+
suggestions.push({
|
|
4109
|
+
value,
|
|
4110
|
+
label: entryName + (isDirectory ? "/" : ""),
|
|
4111
|
+
description: displayPath
|
|
4112
|
+
});
|
|
4113
|
+
}
|
|
4114
|
+
return suggestions;
|
|
4115
|
+
} catch {
|
|
4116
|
+
return [];
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
4120
|
+
const textBeforeCursor = (lines[cursorLine] || "").slice(0, cursorCol);
|
|
4121
|
+
if (textBeforeCursor.trim().startsWith("/") && !textBeforeCursor.trim().includes(" ")) return false;
|
|
4122
|
+
return true;
|
|
4123
|
+
}
|
|
4124
|
+
};
|
|
4125
|
+
//#endregion
|
|
4126
|
+
//#region src/compat/pi-tui.ts
|
|
4127
|
+
const CURSOR_MARKER = "\x1B_pi:c\x07";
|
|
4128
|
+
function isFocusable(value) {
|
|
4129
|
+
return typeof value === "object" && value !== null && "focused" in value;
|
|
4130
|
+
}
|
|
4131
|
+
function blankLines(count) {
|
|
4132
|
+
return Array.from({ length: Math.max(0, count) }, () => "");
|
|
4133
|
+
}
|
|
4134
|
+
var Container = class {
|
|
4135
|
+
children = [];
|
|
4136
|
+
addChild(child) {
|
|
4137
|
+
this.children.push(child);
|
|
4138
|
+
}
|
|
4139
|
+
removeChild(child) {
|
|
4140
|
+
this.children = this.children.filter((candidate) => candidate !== child);
|
|
4141
|
+
}
|
|
4142
|
+
clear() {
|
|
4143
|
+
this.children = [];
|
|
4144
|
+
}
|
|
4145
|
+
invalidate() {
|
|
4146
|
+
for (const child of this.children) child.invalidate();
|
|
4147
|
+
}
|
|
4148
|
+
render(width) {
|
|
4149
|
+
return this.children.flatMap((child) => child.render(width));
|
|
4150
|
+
}
|
|
4151
|
+
};
|
|
4152
|
+
var Text = class {
|
|
4153
|
+
text;
|
|
4154
|
+
paddingX;
|
|
4155
|
+
paddingY;
|
|
4156
|
+
customBgFn;
|
|
4157
|
+
constructor(text = "", paddingX = 1, paddingY = 1, customBgFn) {
|
|
4158
|
+
this.text = text;
|
|
4159
|
+
this.paddingX = paddingX;
|
|
4160
|
+
this.paddingY = paddingY;
|
|
4161
|
+
this.customBgFn = customBgFn;
|
|
4162
|
+
}
|
|
4163
|
+
setText(text) {
|
|
4164
|
+
this.text = text;
|
|
4165
|
+
}
|
|
4166
|
+
setCustomBgFn(customBgFn) {
|
|
4167
|
+
this.customBgFn = customBgFn;
|
|
4168
|
+
}
|
|
4169
|
+
invalidate() {}
|
|
4170
|
+
render(width) {
|
|
4171
|
+
const pad = " ".repeat(Math.max(0, this.paddingX));
|
|
4172
|
+
const inner = Math.max(1, width - this.paddingX * 2);
|
|
4173
|
+
const rendered = this.text.split("\n").flatMap((line) => wrapTextWithAnsi(line, inner)).map((line) => {
|
|
4174
|
+
const value = `${pad}${line}${pad}`;
|
|
4175
|
+
return this.customBgFn === void 0 ? value : this.customBgFn(value);
|
|
4176
|
+
});
|
|
4177
|
+
return [
|
|
4178
|
+
...blankLines(this.paddingY),
|
|
4179
|
+
...rendered,
|
|
4180
|
+
...blankLines(this.paddingY)
|
|
4181
|
+
];
|
|
4182
|
+
}
|
|
4183
|
+
};
|
|
4184
|
+
var TruncatedText = class {
|
|
4185
|
+
text;
|
|
4186
|
+
paddingX;
|
|
4187
|
+
paddingY;
|
|
4188
|
+
constructor(text, paddingX = 0, paddingY = 0) {
|
|
4189
|
+
this.text = text;
|
|
4190
|
+
this.paddingX = paddingX;
|
|
4191
|
+
this.paddingY = paddingY;
|
|
4192
|
+
}
|
|
4193
|
+
invalidate() {}
|
|
4194
|
+
render(width) {
|
|
4195
|
+
const pad = " ".repeat(Math.max(0, this.paddingX));
|
|
4196
|
+
const inner = Math.max(1, width - this.paddingX * 2);
|
|
4197
|
+
const lines = this.text.split("\n").map((line) => `${pad}${truncateToWidth(line, inner)}${pad}`);
|
|
4198
|
+
return [
|
|
4199
|
+
...blankLines(this.paddingY),
|
|
4200
|
+
...lines,
|
|
4201
|
+
...blankLines(this.paddingY)
|
|
4202
|
+
];
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
var Spacer = class {
|
|
4206
|
+
lines;
|
|
4207
|
+
constructor(lines = 1) {
|
|
4208
|
+
this.lines = lines;
|
|
4209
|
+
}
|
|
4210
|
+
setLines(lines) {
|
|
4211
|
+
this.lines = lines;
|
|
4212
|
+
}
|
|
4213
|
+
invalidate() {}
|
|
4214
|
+
render(_width) {
|
|
4215
|
+
return blankLines(this.lines);
|
|
4216
|
+
}
|
|
4217
|
+
};
|
|
4218
|
+
var Box = class {
|
|
4219
|
+
paddingX;
|
|
4220
|
+
paddingY;
|
|
4221
|
+
children = [];
|
|
4222
|
+
bgFn;
|
|
4223
|
+
constructor(paddingX = 1, paddingY = 1, bgFn) {
|
|
4224
|
+
this.paddingX = paddingX;
|
|
4225
|
+
this.paddingY = paddingY;
|
|
4226
|
+
this.bgFn = bgFn;
|
|
4227
|
+
}
|
|
4228
|
+
addChild(component) {
|
|
4229
|
+
this.children.push(component);
|
|
4230
|
+
}
|
|
4231
|
+
removeChild(component) {
|
|
4232
|
+
this.children = this.children.filter((candidate) => candidate !== component);
|
|
4233
|
+
}
|
|
4234
|
+
clear() {
|
|
4235
|
+
this.children = [];
|
|
4236
|
+
}
|
|
4237
|
+
setBgFn(bgFn) {
|
|
4238
|
+
this.bgFn = bgFn;
|
|
4239
|
+
}
|
|
4240
|
+
invalidate() {
|
|
4241
|
+
for (const child of this.children) child.invalidate();
|
|
4242
|
+
}
|
|
4243
|
+
render(width) {
|
|
4244
|
+
const pad = " ".repeat(Math.max(0, this.paddingX));
|
|
4245
|
+
const inner = Math.max(1, width - this.paddingX * 2);
|
|
4246
|
+
const lines = this.children.flatMap((child) => child.render(inner));
|
|
4247
|
+
const rendered = [
|
|
4248
|
+
...blankLines(this.paddingY),
|
|
4249
|
+
...lines.map((line) => `${pad}${line}${pad}`),
|
|
4250
|
+
...blankLines(this.paddingY)
|
|
4251
|
+
];
|
|
4252
|
+
return this.bgFn === void 0 ? rendered : rendered.map((line) => this.bgFn(line));
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
4255
|
+
var Markdown = class {
|
|
4256
|
+
text;
|
|
4257
|
+
paddingX;
|
|
4258
|
+
paddingY;
|
|
4259
|
+
theme;
|
|
4260
|
+
options;
|
|
4261
|
+
constructor(text = "", paddingX = 1, paddingY = 1, theme, options) {
|
|
4262
|
+
this.text = text;
|
|
4263
|
+
this.paddingX = paddingX;
|
|
4264
|
+
this.paddingY = paddingY;
|
|
4265
|
+
this.theme = theme;
|
|
4266
|
+
this.options = options;
|
|
4267
|
+
}
|
|
4268
|
+
setText(text) {
|
|
4269
|
+
this.text = text;
|
|
4270
|
+
}
|
|
4271
|
+
invalidate() {}
|
|
4272
|
+
render(width) {
|
|
4273
|
+
const pad = " ".repeat(Math.max(0, this.paddingX));
|
|
4274
|
+
const inner = Math.max(1, width - this.paddingX * 2);
|
|
4275
|
+
const lines = this.text.split("\n").flatMap((line) => wrapTextWithAnsi(line, inner));
|
|
4276
|
+
return [
|
|
4277
|
+
...blankLines(this.paddingY),
|
|
4278
|
+
...lines.map((line) => `${pad}${line}${pad}`),
|
|
4279
|
+
...blankLines(this.paddingY)
|
|
4280
|
+
];
|
|
4281
|
+
}
|
|
4282
|
+
};
|
|
4283
|
+
var SelectList = class {
|
|
4284
|
+
items;
|
|
4285
|
+
maxVisible;
|
|
4286
|
+
theme;
|
|
4287
|
+
layout;
|
|
4288
|
+
onSelect;
|
|
4289
|
+
onCancel;
|
|
4290
|
+
onSelectionChange;
|
|
4291
|
+
filtered;
|
|
4292
|
+
selectedIndex = 0;
|
|
4293
|
+
constructor(items, maxVisible, theme, layout = {}) {
|
|
4294
|
+
this.items = items;
|
|
4295
|
+
this.maxVisible = maxVisible;
|
|
4296
|
+
this.theme = theme;
|
|
4297
|
+
this.layout = layout;
|
|
4298
|
+
this.filtered = [...items];
|
|
4299
|
+
}
|
|
4300
|
+
setFilter(filter) {
|
|
4301
|
+
const query = filter.toLowerCase();
|
|
4302
|
+
this.filtered = this.items.filter((item) => item.label.toLowerCase().includes(query) || item.value.toLowerCase().includes(query));
|
|
4303
|
+
this.selectedIndex = 0;
|
|
4304
|
+
}
|
|
4305
|
+
setSelectedIndex(index) {
|
|
4306
|
+
this.selectedIndex = Math.max(0, Math.min(index, this.filtered.length - 1));
|
|
4307
|
+
}
|
|
4308
|
+
getSelectedItem() {
|
|
4309
|
+
return this.filtered[this.selectedIndex] ?? null;
|
|
4310
|
+
}
|
|
4311
|
+
invalidate() {}
|
|
4312
|
+
handleInput(_keyData) {}
|
|
4313
|
+
render(width) {
|
|
4314
|
+
return this.filtered.slice(0, this.maxVisible).map((item, index) => truncateToWidth(`${index === this.selectedIndex ? "→ " : " "}${item.label}`, Math.max(1, width)));
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
var SettingsList = class {
|
|
4318
|
+
items;
|
|
4319
|
+
maxVisible;
|
|
4320
|
+
theme;
|
|
4321
|
+
onChange;
|
|
4322
|
+
onCancel;
|
|
4323
|
+
options;
|
|
4324
|
+
constructor(items, maxVisible, theme, onChange, onCancel, options = {}) {
|
|
4325
|
+
this.items = items;
|
|
4326
|
+
this.maxVisible = maxVisible;
|
|
4327
|
+
this.theme = theme;
|
|
4328
|
+
this.onChange = onChange;
|
|
4329
|
+
this.onCancel = onCancel;
|
|
4330
|
+
this.options = options;
|
|
4331
|
+
}
|
|
4332
|
+
updateValue(id, newValue) {
|
|
4333
|
+
const item = this.items.find((candidate) => candidate.id === id);
|
|
4334
|
+
if (item !== void 0) item.currentValue = newValue;
|
|
4335
|
+
}
|
|
4336
|
+
invalidate() {}
|
|
4337
|
+
handleInput(_data) {}
|
|
4338
|
+
render(width) {
|
|
4339
|
+
return this.items.slice(0, this.maxVisible).map((item) => truncateToWidth(`${item.label}: ${item.currentValue}`, Math.max(1, width)));
|
|
4340
|
+
}
|
|
4341
|
+
};
|
|
4342
|
+
var Input = class {
|
|
4343
|
+
focused = false;
|
|
4344
|
+
onSubmit;
|
|
4345
|
+
onEscape;
|
|
4346
|
+
value = "";
|
|
4347
|
+
getValue() {
|
|
4348
|
+
return this.value;
|
|
4349
|
+
}
|
|
4350
|
+
setValue(value) {
|
|
4351
|
+
this.value = value;
|
|
4352
|
+
}
|
|
4353
|
+
handleInput(data) {
|
|
4354
|
+
if (data === "\r" || data === "\n") {
|
|
4355
|
+
this.onSubmit?.(this.value);
|
|
4356
|
+
return;
|
|
4357
|
+
}
|
|
4358
|
+
if (data === "\x1B") {
|
|
4359
|
+
this.onEscape?.();
|
|
4360
|
+
return;
|
|
4361
|
+
}
|
|
4362
|
+
if (data >= " ") this.value += data;
|
|
4363
|
+
}
|
|
4364
|
+
invalidate() {}
|
|
4365
|
+
render(width) {
|
|
4366
|
+
return [truncateToWidth(this.value, Math.max(1, width))];
|
|
4367
|
+
}
|
|
4368
|
+
};
|
|
4369
|
+
var Editor = class {
|
|
4370
|
+
tui;
|
|
4371
|
+
theme;
|
|
4372
|
+
options;
|
|
4373
|
+
focused = false;
|
|
4374
|
+
borderColor = (str) => str;
|
|
4375
|
+
onSubmit;
|
|
4376
|
+
onChange;
|
|
4377
|
+
disableSubmit = false;
|
|
4378
|
+
text = "";
|
|
4379
|
+
history = [];
|
|
4380
|
+
autocompleteProvider;
|
|
4381
|
+
constructor(tui, theme = {}, options = {}) {
|
|
4382
|
+
this.tui = tui;
|
|
4383
|
+
this.theme = theme;
|
|
4384
|
+
this.options = options;
|
|
4385
|
+
}
|
|
4386
|
+
getText() {
|
|
4387
|
+
return this.text;
|
|
4388
|
+
}
|
|
4389
|
+
setText(text) {
|
|
4390
|
+
this.text = text;
|
|
4391
|
+
this.onChange?.(text);
|
|
4392
|
+
}
|
|
4393
|
+
getExpandedText() {
|
|
4394
|
+
return this.text;
|
|
4395
|
+
}
|
|
4396
|
+
insertTextAtCursor(text) {
|
|
4397
|
+
this.setText(this.text + text);
|
|
4398
|
+
}
|
|
4399
|
+
addToHistory(text) {
|
|
4400
|
+
this.history.push(text);
|
|
4401
|
+
}
|
|
4402
|
+
setAutocompleteProvider(provider) {
|
|
4403
|
+
this.autocompleteProvider = provider;
|
|
4404
|
+
}
|
|
4405
|
+
getPaddingX() {
|
|
4406
|
+
return this.options.paddingX ?? 0;
|
|
4407
|
+
}
|
|
4408
|
+
setPaddingX(padding) {
|
|
4409
|
+
this.options.paddingX = padding;
|
|
4410
|
+
}
|
|
4411
|
+
getAutocompleteMaxVisible() {
|
|
4412
|
+
return this.options.autocompleteMaxVisible ?? 5;
|
|
4413
|
+
}
|
|
4414
|
+
setAutocompleteMaxVisible(maxVisible) {
|
|
4415
|
+
this.options.autocompleteMaxVisible = maxVisible;
|
|
4416
|
+
}
|
|
4417
|
+
handleInput(data) {
|
|
4418
|
+
if (data === "\r" || data === "\n") {
|
|
4419
|
+
if (!this.disableSubmit) this.onSubmit?.(this.text);
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
if (data >= " ") this.setText(this.text + data);
|
|
4423
|
+
}
|
|
4424
|
+
invalidate() {}
|
|
4425
|
+
render(width) {
|
|
4426
|
+
return this.text.split("\n").flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width)));
|
|
4427
|
+
}
|
|
4428
|
+
};
|
|
4429
|
+
var Stack = class extends Container {
|
|
4430
|
+
options;
|
|
4431
|
+
constructor(children = [], options = {}) {
|
|
4432
|
+
super();
|
|
4433
|
+
this.options = options;
|
|
4434
|
+
for (const child of children) this.addChild("component" in child ? child.component : child);
|
|
4435
|
+
}
|
|
4436
|
+
};
|
|
4437
|
+
var VStack = class extends Stack {};
|
|
4438
|
+
var HStack = class extends Stack {
|
|
4439
|
+
render(width) {
|
|
4440
|
+
const columns = this.children.map((child) => child.render(width));
|
|
4441
|
+
const height = Math.max(0, ...columns.map((column) => column.length));
|
|
4442
|
+
const lines = [];
|
|
4443
|
+
for (let row = 0; row < height; row += 1) lines.push(columns.map((column) => column[row] ?? "").join(" "));
|
|
4444
|
+
return lines;
|
|
4445
|
+
}
|
|
4446
|
+
};
|
|
4447
|
+
var Loader = class extends Text {
|
|
4448
|
+
constructor(message = "", indicator) {
|
|
4449
|
+
super(message, 0, 0);
|
|
4450
|
+
}
|
|
4451
|
+
start() {}
|
|
4452
|
+
stop() {}
|
|
4453
|
+
setMessage(message) {
|
|
4454
|
+
this.setText(message);
|
|
4455
|
+
}
|
|
4456
|
+
setIndicator(_indicator) {}
|
|
4457
|
+
};
|
|
4458
|
+
var CancellableLoader = class extends Loader {
|
|
4459
|
+
onCancel;
|
|
4460
|
+
handleInput(data) {
|
|
4461
|
+
if (data === "\x1B" || data === "") this.onCancel?.();
|
|
4462
|
+
}
|
|
4463
|
+
dispose() {}
|
|
4464
|
+
};
|
|
4465
|
+
var Image = class {
|
|
4466
|
+
base64Data;
|
|
4467
|
+
mimeType;
|
|
4468
|
+
options;
|
|
4469
|
+
constructor(base64Data = "", mimeType = "image/png", options = {}) {
|
|
4470
|
+
this.base64Data = base64Data;
|
|
4471
|
+
this.mimeType = mimeType;
|
|
4472
|
+
this.options = options;
|
|
4473
|
+
}
|
|
4474
|
+
getImageId() {}
|
|
4475
|
+
invalidate() {}
|
|
4476
|
+
render(width) {
|
|
4477
|
+
return [truncateToWidth(`[image ${this.mimeType}]`, Math.max(1, width))];
|
|
4478
|
+
}
|
|
4479
|
+
};
|
|
4480
|
+
var ScrollView = class extends Container {
|
|
4481
|
+
options;
|
|
4482
|
+
scrollTop = 0;
|
|
4483
|
+
constructor(component, options = {}) {
|
|
4484
|
+
super();
|
|
4485
|
+
this.options = options;
|
|
4486
|
+
this.addChild(component);
|
|
4487
|
+
}
|
|
4488
|
+
setScrollbar(_scrollbar) {}
|
|
4489
|
+
getContentWidth(width) {
|
|
4490
|
+
return Math.max(1, width - 1);
|
|
4491
|
+
}
|
|
4492
|
+
setScrollbarActive(_active) {}
|
|
4493
|
+
scrollTo(scrollTop, _options = {}) {
|
|
4494
|
+
this.scrollTop = Math.max(0, scrollTop);
|
|
4495
|
+
}
|
|
4496
|
+
scrollBy(lines) {
|
|
4497
|
+
this.scrollTop = Math.max(0, this.scrollTop + lines);
|
|
4498
|
+
return this.scrollTop;
|
|
4499
|
+
}
|
|
4500
|
+
scrollToStart() {
|
|
4501
|
+
this.scrollTop = 0;
|
|
4502
|
+
}
|
|
4503
|
+
scrollToEnd() {}
|
|
4504
|
+
updateLayout(_contentHeight, _viewportHeight, _requestRender) {}
|
|
4505
|
+
};
|
|
4506
|
+
function isViewportTUI(value) {
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4509
|
+
//#endregion
|
|
4510
|
+
export { matchesKey as $, getCellDimensions as A, setCellDimensions as B, calculateImageRows as C, encodeITerm2 as D, detectCapabilities as E, getWebpDimensions as F, setKeybindings as G, KeybindingsManager as H, hyperlink as I, Key as J, parseOsc11BackgroundColor as K, imageFallback as L, getImageDimensions as M, getJpegDimensions as N, encodeKitty as O, getPngDimensions as P, isKittyProtocolActive as Q, resetCapabilitiesCache as R, allocateImageId as S, deleteKittyImage as T, TUI_KEYBINDINGS as U, renderLatex as V, getKeybindings as W, isKeyRelease as X, decodeKittyPrintable as Y, isKeyRepeat as Z, TruncatedText as _, stripTerminalSequences as _t, Editor as a, applyBackgroundToLine as at, isViewportTUI as b, wrapTextWithAnsi as bt, Input as c, getGraphemeCellRange as ct, Marked as d, getWordSegmenter as dt, parseKey as et, ScrollView as f, isPunctuationChar as ft, Text as g, sliceWithWidth as gt, Spacer as h, sliceByColumn as ht, Container as i, PUNCTUATION_REGEX as it, getGifDimensions as j, getCapabilities as k, Loader as l, getGraphemeSegmenter as lt, SettingsList as m, normalizeTerminalOutput as mt, CURSOR_MARKER as n, fuzzyFilter as nt, HStack as o, cjkBreakRegex as ot, SelectList as p, isWhitespaceChar as pt, parseTerminalColorSchemeReport as q, CancellableLoader as r, fuzzyMatch as rt, Image as s, extractAnsiCode as st, Box as t, setKittyProtocolActive as tt, Markdown as u, getOsc8LinkAtColumn as ut, VStack as v, truncateToWidth as vt, deleteAllKittyImages as w, CombinedAutocompleteProvider as x, isFocusable as y, visibleWidth as yt, setCapabilities as z };
|
|
4511
|
+
|
|
4512
|
+
//# sourceMappingURL=pi-tui-5CYLcj-_.mjs.map
|