jsmdcui 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -0
- package/README.md +216 -33
- package/clean.sh +9 -0
- package/demo.resized.jpg +0 -0
- package/{image-processor.md → demos/image-processor.md} +1 -0
- package/{image-processor.zh-TW.md → demos/image-processor.zh-TW.md} +1 -0
- package/demos/maze.md +144 -0
- package/{select.md → demos/select.md} +2 -0
- package/demos/todo-zh.md +190 -0
- package/demos/todo.md +191 -0
- package/edit +9 -0
- package/package.json +1 -1
- package/runmd.mjs +77 -16
- package/runtime/help/help.md +216 -33
- package/runtime/jsplugins/cdp/cdp.js +5 -2
- package/runtime/jsplugins/chapter/chapter.js +4 -2
- package/runtime/jsplugins/example/example.js +10 -7
- package/runtime/syntax/markdown.yaml +1 -0
- package/single-exe/packAssets.sh +1 -1
- package/src/cui/fence-events.mjs +106 -0
- package/src/cui/id-collision.mjs +10 -28
- package/src/cui/kitty-debug.mjs +25 -0
- package/src/cui/kitty-images.mjs +200 -0
- package/src/cui/rpc.mjs +169 -10
- package/src/cui/server.mjs +14 -3
- package/src/cui/task-checkbox.mjs +44 -0
- package/src/index.js +615 -101
- package/src/platform/terminal.js +5 -0
- package/src/plugins/js-bridge.js +354 -21
- package/src/screen/screen.js +108 -1
- package/tests/cat-markdown.test.js +6 -5
- package/tests/demo.test.js +99 -9
- package/tests/fence-events.test.js +405 -0
- package/tests/heading-list-selector.test.js +346 -0
- package/tests/id-collision.test.js +14 -2
- package/tests/js-plugin-prompts.test.js +46 -0
- package/tests/key-event.md +10 -0
- package/tests/kitty-demo.md +184 -0
- package/tests/kitty-images.test.js +169 -0
- package/tests/platform-terminal.test.js +8 -0
- package/tests/task-checkbox.test.js +33 -0
- package/tests/textarea.md +8 -0
- package/tests/wui-responsive-images.test.js +35 -0
- package/tests/wui.test.js +16 -1
- package/tui +4 -2
- package/wui +6 -2
package/src/index.js
CHANGED
|
@@ -93,20 +93,23 @@ function printProfileReport() {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
import child_process from "node:child_process"
|
|
96
|
-
import { accessSync, constants, existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
96
|
+
import { accessSync, closeSync, constants, existsSync, openSync, readSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
97
97
|
import { mkdir } from "node:fs/promises";
|
|
98
98
|
import { dirname, basename, join, resolve, sep } from "node:path";
|
|
99
99
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
100
100
|
import process from "node:process";
|
|
101
|
-
import { toggleTaskCheckboxBeforeColumn } from "./cui/task-checkbox.mjs";
|
|
101
|
+
import { toggleTaskCheckboxBeforeColumn, updateAnsiTaskCheckbox } from "./cui/task-checkbox.mjs";
|
|
102
|
+
import { fenceEventMap, inlineFenceEventCode } from "./cui/fence-events.mjs";
|
|
102
103
|
import { checkMarkdownIdCollisions, formatMarkdownIdCheckAnsi } from "./cui/id-collision.mjs";
|
|
104
|
+
import { fitKittyImageToWidth, prepareKittyImages } from "./cui/kitty-images.mjs";
|
|
105
|
+
import { logKittyPlacement } from "./cui/kitty-debug.mjs";
|
|
103
106
|
import { Config } from "./config/config.js";
|
|
104
107
|
import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
|
|
105
108
|
import { cleanConfig } from "./config/clean.js";
|
|
106
109
|
import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
|
|
107
|
-
import { assetPath, hasInternalAssets, listInternalAssetDirs, readInternalAssetText } from "./runtime/assets.js";
|
|
110
|
+
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "./runtime/assets.js";
|
|
108
111
|
//import { PluginManager } from "./plugins/manager.js";
|
|
109
|
-
import { JsPluginManager, buildMicroGlobal, runAction, listActions } from "./plugins/js-bridge.js";
|
|
112
|
+
import { JsPluginManager, buildMicroGlobal, buildTuiBlockIndex, findTuiBlockInIndex, insertTuiTextareaNewline, mergeTuiTextareaBackward, mergeTuiTextareaForward, runAction, listActions } from "./plugins/js-bridge.js";
|
|
110
113
|
import { Colorscheme } from "./config/colorscheme.js";
|
|
111
114
|
import { detectSyntax, loadSyntaxDefinitions } from "./highlight/parser.js";
|
|
112
115
|
import { Highlighter } from "./highlight/highlighter.js";
|
|
@@ -115,10 +118,15 @@ import { Screen } from "./screen/screen.js";
|
|
|
115
118
|
import { VT100 } from "./screen/vt100.js";
|
|
116
119
|
import { ClipboardManager, probeOSC52, osc52Clipboard } from "./platform/clipboard.js";
|
|
117
120
|
import { platformId, run as runCommand, runSync, fetchHttpBytes, detectHttpBackend } from "./platform/commands.js";
|
|
121
|
+
import { controllingTerminalInputPath } from "./platform/terminal.js";
|
|
118
122
|
import { shellSplit } from "./shell/shell.js";
|
|
119
123
|
import { styleToAnsi } from "./display/ansi-style.js";
|
|
120
124
|
import { encodeBinaryToBuffer, decodeBinaryBytes } from "./buffer/fixed3-codec.js";
|
|
121
125
|
import { writeBackup, removeBackup, applyBackup } from "./buffer/backup.js";
|
|
126
|
+
|
|
127
|
+
let kittyImageMode = "off";
|
|
128
|
+
let allowRemoteKittyImages = false;
|
|
129
|
+
const remoteMarkdownSources = new Map();
|
|
122
130
|
import { isHex3Encoding, isMdcuiEncoding } from "./runtime/encodings.js";
|
|
123
131
|
import { createInterface } from "node:readline/promises";
|
|
124
132
|
|
|
@@ -313,7 +321,12 @@ async function readTextFileWithEncoding(path, encoding = "utf-8", inferMdcui = t
|
|
|
313
321
|
|
|
314
322
|
async function fetchTextWithEncoding(url, encoding = "utf-8", inferMdcui = true) {
|
|
315
323
|
const bytes = await fetchHttpBytes(url);
|
|
316
|
-
return decodeAndRenderTextBytes(
|
|
324
|
+
return decodeAndRenderTextBytes(
|
|
325
|
+
new Uint8Array(bytes),
|
|
326
|
+
encodingForPath(url, encoding, inferMdcui),
|
|
327
|
+
process.stdout.columns || 80,
|
|
328
|
+
url,
|
|
329
|
+
);
|
|
317
330
|
}
|
|
318
331
|
|
|
319
332
|
function normalizeEncodingLabel(encoding = "utf-8") {
|
|
@@ -337,20 +350,31 @@ async function decodeAndRenderTextBytes(bytes, encoding = "utf-8", width = proce
|
|
|
337
350
|
const decoded = decodeTextBytesWithEncoding(bytes, encoding);
|
|
338
351
|
if (!isMdcuiEncoding(decoded.encoding)) return decoded;
|
|
339
352
|
const renderWidth = Math.max(1, Math.trunc(Number(width) || 80));
|
|
340
|
-
const { rendered, tuiSourceText } = await renderMdcui(decoded.text, renderWidth, mdpath);
|
|
353
|
+
const { rendered, tuiSourceText, images } = await renderMdcui(decoded.text, renderWidth, mdpath, mdpath);
|
|
341
354
|
const styled = parseAnsiStyledText(rendered);
|
|
342
|
-
return { text: styled.text, encoding: "mdcui", sourceText: decoded.text, tuiSourceText, ansiText: rendered, ansiStyleLines: styled.styleLines, mdcuiRenderWidth: renderWidth };
|
|
355
|
+
return { text: styled.text, encoding: "mdcui", sourceText: decoded.text, tuiSourceText, ansiText: rendered, ansiStyleLines: styled.styleLines, mdcuiImages: images, mdcuiRenderWidth: renderWidth };
|
|
343
356
|
}
|
|
344
357
|
|
|
345
|
-
async function renderMdcui(markdown, width = process.stdout.columns || 80, mdpath = null) {
|
|
358
|
+
async function renderMdcui(markdown, width = process.stdout.columns || 80, mdpath = null, imageBasePath = mdpath) {
|
|
346
359
|
const runmd = await import("../runmd.mjs");
|
|
347
360
|
let md = String(markdown);
|
|
348
361
|
if (mdpath && !isHttpUrl(mdpath)) {
|
|
349
362
|
md = await runmd.extractJs(md, mdpath);
|
|
350
363
|
await runmd.createWui(md, mdpath);
|
|
351
364
|
}
|
|
365
|
+
const tui = runmd.createTui(md, Math.max(1, Math.trunc(Number(width) || 80)));
|
|
366
|
+
const resolvedImageBasePath = mdpath && !isHttpUrl(mdpath)
|
|
367
|
+
? remoteMarkdownSources.get(resolve(mdpath)) ?? imageBasePath
|
|
368
|
+
: imageBasePath;
|
|
369
|
+
const prepared = kittyImageMode === "off"
|
|
370
|
+
? { rendered: tui, images: [] }
|
|
371
|
+
: await prepareKittyImages(tui, resolvedImageBasePath, width, {
|
|
372
|
+
allowUrl: allowRemoteKittyImages,
|
|
373
|
+
kittyMode: kittyImageMode,
|
|
374
|
+
});
|
|
352
375
|
return {
|
|
353
|
-
rendered:
|
|
376
|
+
rendered: prepared.rendered,
|
|
377
|
+
images: prepared.images,
|
|
354
378
|
tuiSourceText: md,
|
|
355
379
|
};
|
|
356
380
|
}
|
|
@@ -590,6 +614,12 @@ function resizeMdcuiTextBlock(buf, y, x) {
|
|
|
590
614
|
ansiLines.splice(row, deleteCount, ...replacement);
|
|
591
615
|
buf._mdcuiAnsiText = ansiLines.join("\n");
|
|
592
616
|
}
|
|
617
|
+
if (Array.isArray(buf._mdcuiImages)) {
|
|
618
|
+
const delta = replacement.length - deleteCount;
|
|
619
|
+
buf._mdcuiImages = buf._mdcuiImages
|
|
620
|
+
.filter((image) => image.line < row || image.line >= row + deleteCount)
|
|
621
|
+
.map((image) => image.line >= row + deleteCount ? { ...image, line: image.line + delta } : image);
|
|
622
|
+
}
|
|
593
623
|
|
|
594
624
|
if (insertAt >= 0) {
|
|
595
625
|
if (buf.cursor.y >= insertAt) buf.cursor.y++;
|
|
@@ -641,6 +671,21 @@ function canEditMdcuiSelection(buf, selection) {
|
|
|
641
671
|
return first.y === last.y && prefixLength > 0 && first.x >= prefixLength;
|
|
642
672
|
}
|
|
643
673
|
|
|
674
|
+
function mdcuiTextareaAtCursor(buf) {
|
|
675
|
+
if (!isMdcuiEncoding(buf?.encoding)) return null;
|
|
676
|
+
let cache = buf._mdcuiControlBlockIndex;
|
|
677
|
+
if (!cache || cache.lines !== buf.lines || cache.lineCount !== buf.lines.length) {
|
|
678
|
+
cache = {
|
|
679
|
+
lines: buf.lines,
|
|
680
|
+
lineCount: buf.lines.length,
|
|
681
|
+
blocks: buildTuiBlockIndex(buf.lines),
|
|
682
|
+
};
|
|
683
|
+
buf._mdcuiControlBlockIndex = cache;
|
|
684
|
+
}
|
|
685
|
+
const block = findTuiBlockInIndex(cache.blocks, buf.cursor.y);
|
|
686
|
+
return block?.header?.tag === "textarea" ? block : null;
|
|
687
|
+
}
|
|
688
|
+
|
|
644
689
|
function isEditLockedBuffer(buf) {
|
|
645
690
|
return isMdcuiEncoding(buf?.encoding ?? buf?.Settings?.encoding);
|
|
646
691
|
}
|
|
@@ -943,6 +988,100 @@ function takeDisplay(text, maxWidth) {
|
|
|
943
988
|
return out;
|
|
944
989
|
}
|
|
945
990
|
|
|
991
|
+
function keyboardEventJson(event) {
|
|
992
|
+
const target = event?.target;
|
|
993
|
+
return {
|
|
994
|
+
type: String(event?.type ?? ""),
|
|
995
|
+
key: String(event?.key ?? ""),
|
|
996
|
+
code: String(event?.code ?? ""),
|
|
997
|
+
raw: String(event?.raw ?? ""),
|
|
998
|
+
ctrlKey: Boolean(event?.ctrlKey),
|
|
999
|
+
shiftKey: Boolean(event?.shiftKey),
|
|
1000
|
+
altKey: Boolean(event?.altKey),
|
|
1001
|
+
metaKey: Boolean(event?.metaKey),
|
|
1002
|
+
repeat: Boolean(event?.repeat),
|
|
1003
|
+
defaultPrevented: Boolean(event?.defaultPrevented),
|
|
1004
|
+
target: {
|
|
1005
|
+
id: String(target?.id ?? ""),
|
|
1006
|
+
tagName: String(target?.tagName ?? ""),
|
|
1007
|
+
className: String(target?.className ?? ""),
|
|
1008
|
+
value: String(target?.value ?? ""),
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function tuiKeyEventForFront(event, target, type) {
|
|
1014
|
+
const sequence = String(event?.key ?? "");
|
|
1015
|
+
const parts = sequence.split("-");
|
|
1016
|
+
const aliases = {
|
|
1017
|
+
enter: "Enter",
|
|
1018
|
+
escape: "Escape",
|
|
1019
|
+
tab: "Tab",
|
|
1020
|
+
backtab: "Tab",
|
|
1021
|
+
backspace: "Backspace",
|
|
1022
|
+
delete: "Delete",
|
|
1023
|
+
left: "ArrowLeft",
|
|
1024
|
+
right: "ArrowRight",
|
|
1025
|
+
up: "ArrowUp",
|
|
1026
|
+
down: "ArrowDown",
|
|
1027
|
+
home: "Home",
|
|
1028
|
+
end: "End",
|
|
1029
|
+
pageup: "PageUp",
|
|
1030
|
+
pagedown: "PageDown",
|
|
1031
|
+
space: " ",
|
|
1032
|
+
};
|
|
1033
|
+
let base = parts.at(-1) || sequence;
|
|
1034
|
+
if (sequence === "backtab") base = "tab";
|
|
1035
|
+
const raw = String(event?.raw ?? "");
|
|
1036
|
+
const key = aliases[base] ?? (sequence === raw && [...raw].length === 1 ? raw : base);
|
|
1037
|
+
let defaultPrevented = false;
|
|
1038
|
+
let propagationStopped = false;
|
|
1039
|
+
const result = {
|
|
1040
|
+
type,
|
|
1041
|
+
key,
|
|
1042
|
+
raw,
|
|
1043
|
+
ctrlKey: parts.includes("ctrl"),
|
|
1044
|
+
altKey: parts.includes("alt"),
|
|
1045
|
+
shiftKey: sequence === "backtab" || parts.includes("shift"),
|
|
1046
|
+
metaKey: parts.includes("meta"),
|
|
1047
|
+
repeat: false,
|
|
1048
|
+
target,
|
|
1049
|
+
currentTarget: target,
|
|
1050
|
+
get defaultPrevented() { return defaultPrevented; },
|
|
1051
|
+
get propagationStopped() { return propagationStopped; },
|
|
1052
|
+
preventDefault() { defaultPrevented = true; },
|
|
1053
|
+
stopPropagation() { propagationStopped = true; },
|
|
1054
|
+
};
|
|
1055
|
+
Object.defineProperty(result, "toJSON", {
|
|
1056
|
+
configurable: true,
|
|
1057
|
+
value() { return keyboardEventJson(this); },
|
|
1058
|
+
});
|
|
1059
|
+
return result;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function indexedMdcuiFenceBlockAtLine(buffer, lineIndex) {
|
|
1063
|
+
const declarations = buffer?._mdcuiFenceEvents;
|
|
1064
|
+
if (!isMdcuiEncoding(buffer?.encoding) || declarations?.size === 0) return null;
|
|
1065
|
+
let cache = buffer._mdcuiFenceBlockIndex;
|
|
1066
|
+
// Render/reopen replaces the lines array; structural selector edits explicitly
|
|
1067
|
+
// clear this cache. Ordinary character edits leave block boundaries unchanged.
|
|
1068
|
+
if (
|
|
1069
|
+
!cache
|
|
1070
|
+
|| cache.lines !== buffer.lines
|
|
1071
|
+
|| cache.lineCount !== buffer.lines.length
|
|
1072
|
+
|| cache.declarations !== declarations
|
|
1073
|
+
) {
|
|
1074
|
+
cache = {
|
|
1075
|
+
lines: buffer.lines,
|
|
1076
|
+
lineCount: buffer.lines.length,
|
|
1077
|
+
declarations,
|
|
1078
|
+
blocks: buildTuiBlockIndex(buffer.lines, declarations),
|
|
1079
|
+
};
|
|
1080
|
+
buffer._mdcuiFenceBlockIndex = cache;
|
|
1081
|
+
}
|
|
1082
|
+
return findTuiBlockInIndex(cache.blocks, lineIndex);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
946
1085
|
function parseArgs(argv) {
|
|
947
1086
|
const flags = {
|
|
948
1087
|
version: false,
|
|
@@ -955,10 +1094,8 @@ function parseArgs(argv) {
|
|
|
955
1094
|
exportReadme: false,
|
|
956
1095
|
changelog: false,
|
|
957
1096
|
testapp: false,
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
demoImgtool: false,
|
|
961
|
-
demoImgtoolZh: false,
|
|
1097
|
+
demoList: false,
|
|
1098
|
+
demo: null,
|
|
962
1099
|
allowUrl: false,
|
|
963
1100
|
buildExe: false,
|
|
964
1101
|
buildFor: "",
|
|
@@ -968,6 +1105,7 @@ function parseArgs(argv) {
|
|
|
968
1105
|
plugin: "",
|
|
969
1106
|
cdpPort: 0,
|
|
970
1107
|
cdpAddress: "",
|
|
1108
|
+
kittyMode: "off",
|
|
971
1109
|
settings: new Map(),
|
|
972
1110
|
};
|
|
973
1111
|
const files = [];
|
|
@@ -1000,11 +1138,25 @@ function parseArgs(argv) {
|
|
|
1000
1138
|
else if (arg === "--export-readme") flags.exportReadme = true;
|
|
1001
1139
|
else if (arg === "--changelog") flags.changelog = true;
|
|
1002
1140
|
else if (arg === "--testapp.md") flags.testapp = true;
|
|
1003
|
-
else if (arg === "--demo") flags.
|
|
1004
|
-
else if (arg === "--demo
|
|
1005
|
-
|
|
1006
|
-
|
|
1141
|
+
else if (arg === "--demo-list") flags.demoList = true;
|
|
1142
|
+
else if (arg === "--demo") {
|
|
1143
|
+
flags.demo = { option: arg, filename: "testapp.md", asset: "testapp.md" };
|
|
1144
|
+
}
|
|
1145
|
+
else if (arg === "--demo-imgtool") {
|
|
1146
|
+
flags.demo = { option: arg, filename: "image-processor.md", asset: "demos/image-processor.md" };
|
|
1147
|
+
}
|
|
1148
|
+
else if (arg === "--demo-imgtool-zh") {
|
|
1149
|
+
flags.demo = { option: arg, filename: "image-processor.zh-TW.md", asset: "demos/image-processor.zh-TW.md" };
|
|
1150
|
+
}
|
|
1151
|
+
else if (arg.startsWith("--demo-")) {
|
|
1152
|
+
const name = arg.slice("--demo-".length);
|
|
1153
|
+
flags.demo = /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)
|
|
1154
|
+
? { option: arg, filename: `${name}.md`, asset: `demos/${name}.md` }
|
|
1155
|
+
: { option: arg, error: "demo filename must contain only letters, numbers, dots, underscores, and hyphens" };
|
|
1156
|
+
}
|
|
1007
1157
|
else if (arg === "--allow-url") flags.allowUrl = true;
|
|
1158
|
+
else if (arg === "--kitty") flags.kittyMode = "extended";
|
|
1159
|
+
else if (arg === "--kitty-compat") flags.kittyMode = "compat";
|
|
1008
1160
|
else if (arg === "--build-exe") flags.buildExe = true;
|
|
1009
1161
|
else if (arg === "--build-for") flags.buildFor = argv[++i] ?? "";
|
|
1010
1162
|
else if (arg === "-debug") flags.debug = true;
|
|
@@ -1056,6 +1208,10 @@ Modes:
|
|
|
1056
1208
|
-encoding mdcui
|
|
1057
1209
|
Render Markdown through runmd.mjs#createTui; .md files use this automatically
|
|
1058
1210
|
Writes .front.js, .back.js, .html, -rpc.js, and -server.js beside the .md file
|
|
1211
|
+
--kitty
|
|
1212
|
+
Display Markdown images with Kitty graphics and the jsgotty MIME extension
|
|
1213
|
+
--kitty-compat
|
|
1214
|
+
Display Markdown images with Kitty graphics without the non-standard MIME U field
|
|
1059
1215
|
|
|
1060
1216
|
Settings:
|
|
1061
1217
|
-SETTING VALUE
|
|
@@ -1086,22 +1242,23 @@ Information:
|
|
|
1086
1242
|
Demo:
|
|
1087
1243
|
--testapp.md
|
|
1088
1244
|
Write the bundled testapp.md to stdout & exit
|
|
1245
|
+
--demo-list
|
|
1246
|
+
List the bundled demos and their command-line options, then exit
|
|
1089
1247
|
--demo
|
|
1090
1248
|
Use the existing ./testapp.md without overwriting it, or write the bundled demo if missing
|
|
1091
1249
|
Open it in the TUI and write 5 generated files beside it
|
|
1092
|
-
--demo
|
|
1093
|
-
|
|
1250
|
+
--demo-<filename>
|
|
1251
|
+
Load demos/<filename>.md, preserving an existing ./<filename>.md or writing the bundled copy
|
|
1094
1252
|
Open it in the TUI and write 5 generated files beside it
|
|
1253
|
+
For example: --demo-select, --demo-todo, or --demo-todo-zh
|
|
1095
1254
|
--demo-imgtool
|
|
1096
|
-
|
|
1097
|
-
Open the Bun.Image processor in the TUI and write 5 generated files beside it
|
|
1255
|
+
Alias for --demo-image-processor
|
|
1098
1256
|
--demo-imgtool-zh
|
|
1099
|
-
|
|
1100
|
-
Open the Traditional Chinese Bun.Image processor in the TUI and write 5 generated files beside it
|
|
1257
|
+
Alias for --demo-image-processor.zh-TW
|
|
1101
1258
|
|
|
1102
1259
|
Remote Markdown:
|
|
1103
1260
|
--allow-url
|
|
1104
|
-
Download HTTP(S) Markdown
|
|
1261
|
+
Download HTTP(S) Markdown and, with Kitty mode, its HTTP(S) images; allow its code to run
|
|
1105
1262
|
|
|
1106
1263
|
Experimental:
|
|
1107
1264
|
--build-exe Build a Bun single-file executable and exit
|
|
@@ -1149,7 +1306,7 @@ class BufferModel {
|
|
|
1149
1306
|
get searchPattern() { return this._searchPattern ?? ""; }
|
|
1150
1307
|
set searchPattern(v) { this._searchPattern = v ?? ""; this.searchMatches?.clear(); }
|
|
1151
1308
|
|
|
1152
|
-
constructor({ path = "", text = "", command = {}, type = "default", readonly = false, modTimeMs = null, encoding = DEFAULT_SETTINGS.encoding, ansiStyleLines = null, ansiText = null, sourceText = null, tuiSourceText = null, mdcuiRenderWidth = 0 } = {}) {
|
|
1309
|
+
constructor({ path = "", text = "", command = {}, type = "default", readonly = false, modTimeMs = null, encoding = DEFAULT_SETTINGS.encoding, ansiStyleLines = null, ansiText = null, sourceText = null, tuiSourceText = null, mdcuiImages = null, mdcuiRenderWidth = 0 } = {}) {
|
|
1153
1310
|
this.path = path;
|
|
1154
1311
|
this.type = type;
|
|
1155
1312
|
this.name = path ? basename(path) : "No name";
|
|
@@ -1162,6 +1319,8 @@ class BufferModel {
|
|
|
1162
1319
|
this._mdcuiAnsiText = isMdcuiEncoding(this.encoding) ? String(ansiText ?? "") : null;
|
|
1163
1320
|
this._mdcuiSourceText = isMdcuiEncoding(this.encoding) ? String(sourceText ?? text) : null;
|
|
1164
1321
|
this._mdcuiTuiSourceText = isMdcuiEncoding(this.encoding) ? String(tuiSourceText ?? sourceText ?? text) : null;
|
|
1322
|
+
this._mdcuiFenceEvents = isMdcuiEncoding(this.encoding) ? fenceEventMap(sourceText ?? tuiSourceText ?? text) : new Map();
|
|
1323
|
+
this._mdcuiImages = isMdcuiEncoding(this.encoding) ? (mdcuiImages ?? []) : [];
|
|
1165
1324
|
this._mdcuiRenderWidth = Math.trunc(Number(mdcuiRenderWidth) || 0);
|
|
1166
1325
|
this.cursor = { x: 0, y: 0 };
|
|
1167
1326
|
this.scroll = { x: 0, y: 0, row: 0 };
|
|
@@ -1262,6 +1421,7 @@ class BufferModel {
|
|
|
1262
1421
|
let sourceText = null;
|
|
1263
1422
|
let tuiSourceText = null;
|
|
1264
1423
|
let mdcuiRenderWidth = 0;
|
|
1424
|
+
let mdcuiImages = null;
|
|
1265
1425
|
if (existsSync(path)) {
|
|
1266
1426
|
const info = statSync(path);
|
|
1267
1427
|
if (info.isDirectory()) throw new Error(`${path} is a directory`);
|
|
@@ -1275,9 +1435,10 @@ class BufferModel {
|
|
|
1275
1435
|
sourceText = decoded.sourceText ?? null;
|
|
1276
1436
|
tuiSourceText = decoded.tuiSourceText ?? null;
|
|
1277
1437
|
mdcuiRenderWidth = decoded.mdcuiRenderWidth ?? 0;
|
|
1438
|
+
mdcuiImages = decoded.mdcuiImages ?? null;
|
|
1278
1439
|
if (isMdcuiEncoding(encoding)) readonly = true;
|
|
1279
1440
|
}
|
|
1280
|
-
const buffer = new BufferModel({ path, text, command, readonly, modTimeMs, encoding, ansiStyleLines, ansiText, sourceText, tuiSourceText, mdcuiRenderWidth });
|
|
1441
|
+
const buffer = new BufferModel({ path, text, command, readonly, modTimeMs, encoding, ansiStyleLines, ansiText, sourceText, tuiSourceText, mdcuiImages, mdcuiRenderWidth });
|
|
1281
1442
|
buffer._configDir = context?.config?.configDir ?? null;
|
|
1282
1443
|
attachSyntax(buffer, context, path, text);
|
|
1283
1444
|
return buffer;
|
|
@@ -1517,9 +1678,39 @@ class BufferModel {
|
|
|
1517
1678
|
this.cursor.x = x;
|
|
1518
1679
|
}
|
|
1519
1680
|
|
|
1681
|
+
_historyState() {
|
|
1682
|
+
return {
|
|
1683
|
+
lines: this.lines.slice(),
|
|
1684
|
+
cursor: { ...this.cursor },
|
|
1685
|
+
serial: this._undoSerial,
|
|
1686
|
+
ansiStyleLines: Array.isArray(this._ansiStyleLines)
|
|
1687
|
+
? this._ansiStyleLines.map((line) => Array.isArray(line) ? line.slice() : line)
|
|
1688
|
+
: this._ansiStyleLines,
|
|
1689
|
+
mdcuiAnsiText: this._mdcuiAnsiText,
|
|
1690
|
+
mdcuiImages: Array.isArray(this._mdcuiImages)
|
|
1691
|
+
? this._mdcuiImages.map((image) => ({ ...image }))
|
|
1692
|
+
: this._mdcuiImages,
|
|
1693
|
+
headingTaskListAnchors: this._mdcuiHeadingTaskListAnchors instanceof Map
|
|
1694
|
+
? new Map([...this._mdcuiHeadingTaskListAnchors].map(([key, value]) => [key, { ...value }]))
|
|
1695
|
+
: this._mdcuiHeadingTaskListAnchors,
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
_restoreHistoryState(state) {
|
|
1700
|
+
this.lines = state.lines;
|
|
1701
|
+
this.cursor = { ...state.cursor };
|
|
1702
|
+
this._undoSerial = state.serial ?? 0;
|
|
1703
|
+
this._ansiStyleLines = state.ansiStyleLines;
|
|
1704
|
+
this._mdcuiAnsiText = state.mdcuiAnsiText;
|
|
1705
|
+
this._mdcuiImages = state.mdcuiImages;
|
|
1706
|
+
this._mdcuiHeadingTaskListAnchors = state.headingTaskListAnchors;
|
|
1707
|
+
this._mdcuiFenceBlockIndex = null;
|
|
1708
|
+
this._mdcuiControlBlockIndex = null;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1520
1711
|
pushUndo(force = false) {
|
|
1521
1712
|
if (!force && this.isEditLocked() && !canEditMdcuiAtCursor(this)) return;
|
|
1522
|
-
this.undoStack.push(
|
|
1713
|
+
this.undoStack.push(this._historyState());
|
|
1523
1714
|
this._undoSerial = (this._undoSerial ?? 0) + 1;
|
|
1524
1715
|
if (this.undoStack.length > 500) this.undoStack.shift();
|
|
1525
1716
|
this.redoStack = [];
|
|
@@ -1528,12 +1719,10 @@ class BufferModel {
|
|
|
1528
1719
|
undo() {
|
|
1529
1720
|
if (this.isEditLocked() && !isMdcuiEncoding(this.encoding)) return false;
|
|
1530
1721
|
if (!this.undoStack.length) return false;
|
|
1531
|
-
this.redoStack.push(
|
|
1722
|
+
this.redoStack.push(this._historyState());
|
|
1532
1723
|
const s = this.undoStack.pop();
|
|
1533
|
-
this.
|
|
1724
|
+
this._restoreHistoryState(s);
|
|
1534
1725
|
this.invalidateHighlightFrom(0, { force: true });
|
|
1535
|
-
this.cursor = { ...s.cursor };
|
|
1536
|
-
this._undoSerial = s.serial ?? 0;
|
|
1537
1726
|
this.modified = this._undoSerial !== this._savedSerial;
|
|
1538
1727
|
return true;
|
|
1539
1728
|
}
|
|
@@ -1541,12 +1730,10 @@ class BufferModel {
|
|
|
1541
1730
|
redo() {
|
|
1542
1731
|
if (this.isEditLocked() && !isMdcuiEncoding(this.encoding)) return false;
|
|
1543
1732
|
if (!this.redoStack.length) return false;
|
|
1544
|
-
this.undoStack.push(
|
|
1733
|
+
this.undoStack.push(this._historyState());
|
|
1545
1734
|
const s = this.redoStack.pop();
|
|
1546
|
-
this.
|
|
1735
|
+
this._restoreHistoryState(s);
|
|
1547
1736
|
this.invalidateHighlightFrom(0, { force: true });
|
|
1548
|
-
this.cursor = { ...s.cursor };
|
|
1549
|
-
this._undoSerial = s.serial ?? 0;
|
|
1550
1737
|
this.modified = this._undoSerial !== this._savedSerial;
|
|
1551
1738
|
return true;
|
|
1552
1739
|
}
|
|
@@ -1677,7 +1864,9 @@ class BufferModel {
|
|
|
1677
1864
|
this._mdcuiAnsiText = isMdcuiEncoding(this.encoding) ? String(decoded.ansiText ?? "") : null;
|
|
1678
1865
|
this._mdcuiSourceText = isMdcuiEncoding(this.encoding) ? String(decoded.sourceText ?? text) : null;
|
|
1679
1866
|
this._mdcuiTuiSourceText = isMdcuiEncoding(this.encoding) ? String(decoded.tuiSourceText ?? decoded.sourceText ?? text) : null;
|
|
1867
|
+
this._mdcuiFenceEvents = isMdcuiEncoding(this.encoding) ? fenceEventMap(decoded.sourceText ?? decoded.tuiSourceText ?? text) : new Map();
|
|
1680
1868
|
this._mdcuiRenderWidth = decoded.mdcuiRenderWidth ?? 0;
|
|
1869
|
+
this._mdcuiImages = decoded.mdcuiImages ?? [];
|
|
1681
1870
|
const readonly = isMdcuiEncoding(this.encoding);
|
|
1682
1871
|
this.fileformat = detectFileFormat(text, this.Settings.fileformat ?? DEFAULT_SETTINGS.fileformat);
|
|
1683
1872
|
this.Settings.fileformat = this.fileformat;
|
|
@@ -1709,7 +1898,9 @@ class BufferModel {
|
|
|
1709
1898
|
this._mdcuiAnsiText = isMdcuiEncoding(this.encoding) ? String(decoded.ansiText ?? "") : null;
|
|
1710
1899
|
this._mdcuiSourceText = isMdcuiEncoding(this.encoding) ? String(decoded.sourceText ?? text) : null;
|
|
1711
1900
|
this._mdcuiTuiSourceText = isMdcuiEncoding(this.encoding) ? String(decoded.tuiSourceText ?? decoded.sourceText ?? text) : null;
|
|
1901
|
+
this._mdcuiFenceEvents = isMdcuiEncoding(this.encoding) ? fenceEventMap(decoded.sourceText ?? decoded.tuiSourceText ?? text) : new Map();
|
|
1712
1902
|
this._mdcuiRenderWidth = decoded.mdcuiRenderWidth ?? 0;
|
|
1903
|
+
this._mdcuiImages = decoded.mdcuiImages ?? [];
|
|
1713
1904
|
this.fileformat = detectFileFormat(text, this.Settings.fileformat ?? DEFAULT_SETTINGS.fileformat);
|
|
1714
1905
|
this.Settings.fileformat = this.fileformat;
|
|
1715
1906
|
this.lines = normalizeBufferText(text).split("\n");
|
|
@@ -1733,12 +1924,13 @@ class BufferModel {
|
|
|
1733
1924
|
if (!isMdcuiEncoding(this.encoding) || this._mdcuiTuiSourceText == null) return false;
|
|
1734
1925
|
const renderWidth = Math.max(1, Math.trunc(Number(width) || 80));
|
|
1735
1926
|
if (renderWidth === this._mdcuiRenderWidth) return false;
|
|
1736
|
-
const { rendered } = await renderMdcui(this._mdcuiTuiSourceText, renderWidth);
|
|
1927
|
+
const { rendered, images } = await renderMdcui(this._mdcuiTuiSourceText, renderWidth, null, this.path);
|
|
1737
1928
|
const styled = parseAnsiStyledText(rendered);
|
|
1738
1929
|
this.lines = normalizeBufferText(styled.text).split("\n");
|
|
1739
1930
|
if (this.lines.length === 0) this.lines = [""];
|
|
1740
1931
|
this._ansiStyleLines = styled.styleLines;
|
|
1741
1932
|
this._mdcuiAnsiText = rendered;
|
|
1933
|
+
this._mdcuiImages = images;
|
|
1742
1934
|
this._mdcuiRenderWidth = renderWidth;
|
|
1743
1935
|
this.fileformat = detectFileFormat(styled.text, this.Settings.fileformat ?? DEFAULT_SETTINGS.fileformat);
|
|
1744
1936
|
this.Settings.fileformat = this.fileformat;
|
|
@@ -2404,7 +2596,10 @@ class App {
|
|
|
2404
2596
|
this.clipboard = new ClipboardManager();
|
|
2405
2597
|
this.context = context;
|
|
2406
2598
|
this.shellRunning = false;
|
|
2407
|
-
this.screen = new Screen({
|
|
2599
|
+
this.screen = new Screen({
|
|
2600
|
+
mouse: DEFAULT_SETTINGS.mouse !== false,
|
|
2601
|
+
kittyMode: context.kittyMode ?? "off",
|
|
2602
|
+
});
|
|
2408
2603
|
this.tabRects = [];
|
|
2409
2604
|
this._escBuf = null; // pending lone ESC bytes waiting for alt-key combo
|
|
2410
2605
|
this._escTimer = null;
|
|
@@ -2449,16 +2644,14 @@ class App {
|
|
|
2449
2644
|
}
|
|
2450
2645
|
|
|
2451
2646
|
async start() {
|
|
2452
|
-
this.
|
|
2647
|
+
this.installProtectedPrompts();
|
|
2453
2648
|
// When stdin was a pipe (content already consumed in loadBuffers), open the
|
|
2454
2649
|
// controlling terminal directly so the event loop has a live handle and
|
|
2455
|
-
// keyboard input works.
|
|
2650
|
+
// keyboard input works. Unix: /dev/tty; Windows: CONIN$.
|
|
2456
2651
|
if (!process.stdin.isTTY) {
|
|
2457
2652
|
try {
|
|
2458
|
-
const { openSync } = await import("node:fs");
|
|
2459
2653
|
const { ReadStream } = await import("node:tty");
|
|
2460
|
-
const
|
|
2461
|
-
const fd = openSync(ttyPath, "r+");
|
|
2654
|
+
const fd = openSync(controllingTerminalInputPath(), "r");
|
|
2462
2655
|
this._ttyStream = new ReadStream(fd);
|
|
2463
2656
|
} catch {
|
|
2464
2657
|
this._ttyStream = process.stdin;
|
|
@@ -2488,6 +2681,7 @@ class App {
|
|
|
2488
2681
|
});
|
|
2489
2682
|
process.on("SIGINT", () => {}); // Ctrl+C is handled as copy in handleEvent
|
|
2490
2683
|
this.screen.init();
|
|
2684
|
+
this._started = true;
|
|
2491
2685
|
// Update backup prompt to screen-aware version now that TUI is running.
|
|
2492
2686
|
if (this.context._termPrompt) {
|
|
2493
2687
|
this.context._termPrompt = async (msg) => {
|
|
@@ -2572,6 +2766,8 @@ class App {
|
|
|
2572
2766
|
if (p.type === "term") p.terminal?.close();
|
|
2573
2767
|
(this._ttyStream ?? process.stdin).setRawMode?.(false);
|
|
2574
2768
|
this.screen.fini();
|
|
2769
|
+
this._started = false;
|
|
2770
|
+
this.restoreProtectedPrompts();
|
|
2575
2771
|
|
|
2576
2772
|
if (this.context?.config?.getGlobalOption("savehistory") !== false) {
|
|
2577
2773
|
try { await saveHistory(this.context.config.configDir); } catch {}
|
|
@@ -2590,6 +2786,157 @@ class App {
|
|
|
2590
2786
|
process.exit(code);
|
|
2591
2787
|
}
|
|
2592
2788
|
|
|
2789
|
+
installProtectedPrompts() {
|
|
2790
|
+
if (this._protectedPrompts) return;
|
|
2791
|
+
const saved = {
|
|
2792
|
+
alert: {
|
|
2793
|
+
had: Object.prototype.hasOwnProperty.call(globalThis, "alert"),
|
|
2794
|
+
value: globalThis.alert,
|
|
2795
|
+
},
|
|
2796
|
+
confirm: {
|
|
2797
|
+
had: Object.prototype.hasOwnProperty.call(globalThis, "confirm"),
|
|
2798
|
+
value: globalThis.confirm,
|
|
2799
|
+
},
|
|
2800
|
+
prompt: {
|
|
2801
|
+
had: Object.prototype.hasOwnProperty.call(globalThis, "prompt"),
|
|
2802
|
+
value: globalThis.prompt,
|
|
2803
|
+
},
|
|
2804
|
+
};
|
|
2805
|
+
this._protectedPrompts = saved;
|
|
2806
|
+
this._protectedPromptGlobals = {
|
|
2807
|
+
alert: this.protectedAlert.bind(this),
|
|
2808
|
+
confirm: this.protectedConfirm.bind(this),
|
|
2809
|
+
prompt: this.protectedPrompt.bind(this),
|
|
2810
|
+
};
|
|
2811
|
+
Object.assign(globalThis, this._protectedPromptGlobals);
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
runProtectedPrompt(name, fallback, args) {
|
|
2815
|
+
const nativeFn = this._protectedPrompts?.[name]?.value;
|
|
2816
|
+
const tty = this._ttyStream ?? process.stdin;
|
|
2817
|
+
const wasRaw = typeof tty?.isRaw === "boolean" ? tty.isRaw : true;
|
|
2818
|
+
if (
|
|
2819
|
+
!this._started
|
|
2820
|
+
|| this.shellRunning
|
|
2821
|
+
|| this._alertRunning
|
|
2822
|
+
|| !wasRaw
|
|
2823
|
+
|| typeof nativeFn !== "function"
|
|
2824
|
+
) {
|
|
2825
|
+
this.message = String(args[0] ?? "");
|
|
2826
|
+
return fallback;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
const inputHandler = this._inputHandler;
|
|
2830
|
+
const listenerAttached = Boolean(
|
|
2831
|
+
inputHandler && tty.listeners?.("data").includes(inputHandler),
|
|
2832
|
+
);
|
|
2833
|
+
const wasPaused = tty.isPaused?.() ?? false;
|
|
2834
|
+
let value;
|
|
2835
|
+
let failed = false;
|
|
2836
|
+
let failure;
|
|
2837
|
+
const cleanupFailures = [];
|
|
2838
|
+
const cleanup = (fn) => {
|
|
2839
|
+
try { fn(); } catch (error) { cleanupFailures.push(error); }
|
|
2840
|
+
};
|
|
2841
|
+
|
|
2842
|
+
try {
|
|
2843
|
+
this._alertRunning = true;
|
|
2844
|
+
// Do not let the editor's flowing-mode data listener race the blocking
|
|
2845
|
+
// prompt for bytes from the same terminal.
|
|
2846
|
+
if (listenerAttached) tty.removeListener("data", inputHandler);
|
|
2847
|
+
tty.pause?.();
|
|
2848
|
+
tty.setRawMode?.(false);
|
|
2849
|
+
this.screen.fini();
|
|
2850
|
+
this.screen.previous = null;
|
|
2851
|
+
// Bun's native prompts always read fd 0. If Markdown itself came from a
|
|
2852
|
+
// pipe, read synchronously from the controlling terminal instead.
|
|
2853
|
+
value = tty !== process.stdin
|
|
2854
|
+
? this.runProtectedTtyPrompt(name, fallback, args)
|
|
2855
|
+
: Reflect.apply(nativeFn, globalThis, args);
|
|
2856
|
+
} catch (error) {
|
|
2857
|
+
failed = true;
|
|
2858
|
+
failure = error;
|
|
2859
|
+
} finally {
|
|
2860
|
+
cleanup(() => tty.setRawMode?.(wasRaw));
|
|
2861
|
+
cleanup(() => { this.screen.previous = null; });
|
|
2862
|
+
cleanup(() => this.screen.init());
|
|
2863
|
+
cleanup(() => { this._alertRunning = false; });
|
|
2864
|
+
cleanup(() => {
|
|
2865
|
+
if (listenerAttached) tty.on("data", inputHandler);
|
|
2866
|
+
});
|
|
2867
|
+
cleanup(() => {
|
|
2868
|
+
if (!wasPaused) tty.resume?.();
|
|
2869
|
+
});
|
|
2870
|
+
cleanup(() => this.render());
|
|
2871
|
+
}
|
|
2872
|
+
if (failed) throw failure;
|
|
2873
|
+
if (cleanupFailures.length > 0) throw cleanupFailures[0];
|
|
2874
|
+
return value;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
runProtectedTtyPrompt(name, fallback, args) {
|
|
2878
|
+
const message = String(args[0] ?? "");
|
|
2879
|
+
const defaultValue = String(args[1] ?? "");
|
|
2880
|
+
const fd = openSync(controllingTerminalInputPath(), "r");
|
|
2881
|
+
try {
|
|
2882
|
+
if (name === "alert") {
|
|
2883
|
+
process.stdout.write(`${message}\n\nPress ENTER to continue...`);
|
|
2884
|
+
this.readProtectedPromptLine(fd);
|
|
2885
|
+
return undefined;
|
|
2886
|
+
}
|
|
2887
|
+
const suffix = name === "confirm"
|
|
2888
|
+
? " [y/N] "
|
|
2889
|
+
: defaultValue ? ` (${defaultValue}) ` : " ";
|
|
2890
|
+
process.stdout.write(message + suffix);
|
|
2891
|
+
const answer = this.readProtectedPromptLine(fd);
|
|
2892
|
+
if (answer == null) return fallback;
|
|
2893
|
+
if (name === "confirm") return /^(?:y|yes)$/i.test(answer.trim());
|
|
2894
|
+
return answer === "" ? defaultValue : answer;
|
|
2895
|
+
} finally {
|
|
2896
|
+
closeSync(fd);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
readProtectedPromptLine(fd) {
|
|
2901
|
+
const chunks = [];
|
|
2902
|
+
const chunk = Buffer.allocUnsafe(1024);
|
|
2903
|
+
while (true) {
|
|
2904
|
+
const length = readSync(fd, chunk, 0, chunk.length, null);
|
|
2905
|
+
if (length === 0) return chunks.length > 0 ? Buffer.concat(chunks).toString() : null;
|
|
2906
|
+
const bytes = chunk.subarray(0, length);
|
|
2907
|
+
const newline = bytes.findIndex((byte) => byte === 0x0a || byte === 0x0d);
|
|
2908
|
+
chunks.push(Buffer.from(newline < 0 ? bytes : bytes.subarray(0, newline)));
|
|
2909
|
+
if (newline >= 0) return Buffer.concat(chunks).toString();
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
protectedAlert(msg = "") {
|
|
2914
|
+
return this.runProtectedPrompt("alert", undefined, [String(msg)]);
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
protectedConfirm(msg = "") {
|
|
2918
|
+
return this.runProtectedPrompt("confirm", false, [String(msg)]);
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
protectedPrompt(msg = "", defaultValue = "") {
|
|
2922
|
+
return this.runProtectedPrompt(
|
|
2923
|
+
"prompt",
|
|
2924
|
+
defaultValue,
|
|
2925
|
+
[String(msg), String(defaultValue)],
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
restoreProtectedPrompts() {
|
|
2930
|
+
const saved = this._protectedPrompts;
|
|
2931
|
+
if (!saved) return;
|
|
2932
|
+
for (const name of ["alert", "confirm", "prompt"]) {
|
|
2933
|
+
if (saved[name].had) globalThis[name] = saved[name].value;
|
|
2934
|
+
else delete globalThis[name];
|
|
2935
|
+
}
|
|
2936
|
+
this._protectedPrompts = null;
|
|
2937
|
+
this._protectedPromptGlobals = null;
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2593
2940
|
layoutEditorArea() {
|
|
2594
2941
|
const promptHeight = this.prompt ? 1 : 0;
|
|
2595
2942
|
const tabBarHeight = this.tabs.length > 1 ? 1 : 0;
|
|
@@ -2638,6 +2985,7 @@ class App {
|
|
|
2638
2985
|
|
|
2639
2986
|
const defaultStyle = this.context.colorscheme?.defaultStyle ?? {};
|
|
2640
2987
|
this.screen.fill(" ", defaultStyle);
|
|
2988
|
+
this._kittyFrameImages = [];
|
|
2641
2989
|
|
|
2642
2990
|
this.tabRects = [];
|
|
2643
2991
|
if (tabBarHeight) this.renderTabbar(defaultStyle);
|
|
@@ -2813,6 +3161,7 @@ class App {
|
|
|
2813
3161
|
this.screen.setCursor(0, 0, false);
|
|
2814
3162
|
}
|
|
2815
3163
|
|
|
3164
|
+
this.screen.setKittyImages(this._kittyFrameImages);
|
|
2816
3165
|
this.screen.show();
|
|
2817
3166
|
}
|
|
2818
3167
|
|
|
@@ -2995,6 +3344,59 @@ class App {
|
|
|
2995
3344
|
? (this.context.colorscheme.get("cursor-line")?.fg ?? null)
|
|
2996
3345
|
: null;
|
|
2997
3346
|
|
|
3347
|
+
const addKittyImage = (lineNo, screenRow, subRow = 0) => {
|
|
3348
|
+
if (subRow !== 0 || !Array.isArray(buf._mdcuiImages)) return;
|
|
3349
|
+
for (const image of buf._mdcuiImages) {
|
|
3350
|
+
if (image.line !== lineNo) continue;
|
|
3351
|
+
const { cols, rows } = fitKittyImageToWidth(image, maxW);
|
|
3352
|
+
const visibleTop = Math.max(pane.y, screenRow);
|
|
3353
|
+
const visibleBottom = Math.min(pane.y + pane.h, screenRow + rows);
|
|
3354
|
+
const visibleRows = visibleBottom - visibleTop;
|
|
3355
|
+
if (visibleRows < 1 || cols < 1) continue;
|
|
3356
|
+
const clippedTopRows = visibleTop - screenRow;
|
|
3357
|
+
const pixelHeight = Math.max(1, Math.trunc(Number(image.pixelHeight) || 1));
|
|
3358
|
+
const sourceY = Math.min(pixelHeight - 1, Math.round(pixelHeight * clippedTopRows / rows));
|
|
3359
|
+
const sourceBottom = Math.min(pixelHeight, Math.round(pixelHeight * (clippedTopRows + visibleRows) / rows));
|
|
3360
|
+
const sourceHeight = Math.max(1, sourceBottom - sourceY);
|
|
3361
|
+
const sourceWidth = Math.max(1, Math.trunc(Number(image.pixelWidth) || 1));
|
|
3362
|
+
const x = pane.x + gutterW;
|
|
3363
|
+
const placementId = ((image.id ^ Math.imul(x + 1, 73856093) ^ Math.imul(visibleTop + 1, 19349663) ^ Math.imul(sourceY + 1, 83492791)) >>> 0) % 2147483646 + 1;
|
|
3364
|
+
logKittyPlacement("app-placement", {
|
|
3365
|
+
buffer: buf.path,
|
|
3366
|
+
imagePath: image.path,
|
|
3367
|
+
imageId: image.id,
|
|
3368
|
+
placementId,
|
|
3369
|
+
imageLogicalLine: image.line,
|
|
3370
|
+
renderedLine: lineNo,
|
|
3371
|
+
subRow,
|
|
3372
|
+
pane: { x: pane.x, y: pane.y, width: pane.w, height: pane.h },
|
|
3373
|
+
scroll: { ...buf.scroll },
|
|
3374
|
+
gutterWidth: gutterW,
|
|
3375
|
+
maxWidth: maxW,
|
|
3376
|
+
screenX: x,
|
|
3377
|
+
nominalScreenY: screenRow,
|
|
3378
|
+
screenY: visibleTop,
|
|
3379
|
+
cols,
|
|
3380
|
+
rows: visibleRows,
|
|
3381
|
+
originalCols: image.cols,
|
|
3382
|
+
originalRows: image.rows,
|
|
3383
|
+
sourceRect: { x: 0, y: sourceY, width: sourceWidth, height: sourceHeight },
|
|
3384
|
+
});
|
|
3385
|
+
this._kittyFrameImages.push({
|
|
3386
|
+
...image,
|
|
3387
|
+
x,
|
|
3388
|
+
y: visibleTop,
|
|
3389
|
+
cols,
|
|
3390
|
+
rows: visibleRows,
|
|
3391
|
+
sourceX: 0,
|
|
3392
|
+
sourceY,
|
|
3393
|
+
sourceWidth,
|
|
3394
|
+
sourceHeight,
|
|
3395
|
+
placementId,
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
|
|
2998
3400
|
const hasDiff = (buf.Settings?.diffgutter ?? false) && !!buf.diffBase;
|
|
2999
3401
|
if (hasDiff && !buf._diffOnUpdate) buf._diffOnUpdate = () => this.render();
|
|
3000
3402
|
const diffMarks = hasDiff ? getDiffMarkers(buf) : null;
|
|
@@ -3009,6 +3411,18 @@ class App {
|
|
|
3009
3411
|
const msgWarnStyle = cs?.styles?.has("gutter-warning") ? cs.get("gutter-warning") : defaultStyle;
|
|
3010
3412
|
const msgErrStyle = cs?.styles?.has("gutter-error") ? cs.get("gutter-error") : defaultStyle;
|
|
3011
3413
|
|
|
3414
|
+
// When the image anchor has scrolled above the pane, its lower portion can
|
|
3415
|
+
// still intersect the viewport. It will not be encountered by the normal
|
|
3416
|
+
// visible-line loop, so add that clipped placement explicitly.
|
|
3417
|
+
for (const image of buf._mdcuiImages ?? []) {
|
|
3418
|
+
if (image.line >= buf.scroll.y) continue;
|
|
3419
|
+
const nominalScreenRow = pane.y + image.line - buf.scroll.y;
|
|
3420
|
+
const { rows } = fitKittyImageToWidth(image, maxW);
|
|
3421
|
+
if (nominalScreenRow < pane.y && nominalScreenRow + rows > pane.y) {
|
|
3422
|
+
addKittyImage(image.line, nominalScreenRow);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3012
3426
|
const renderGutter = (lineNo, row, screenRow, subRow = 0) => {
|
|
3013
3427
|
// Message indicator: 2 cols, '> ' with kind-based style (Go: drawGutter)
|
|
3014
3428
|
if (msgW > 0) {
|
|
@@ -3052,6 +3466,7 @@ class App {
|
|
|
3052
3466
|
if (lineNo < buf.lines.length) {
|
|
3053
3467
|
const cells = renderHighlightedCells(buf, lineNo, buf.scroll.x, maxW, this.context.colorscheme, pane.selection, getLineSearchRanges(buf, lineNo), braceMatches, isCL ? clBg : null);
|
|
3054
3468
|
putCells(this.screen, pane.x + gutterW, screenRow, cells, maxW);
|
|
3469
|
+
addKittyImage(lineNo, screenRow);
|
|
3055
3470
|
}
|
|
3056
3471
|
}
|
|
3057
3472
|
} else {
|
|
@@ -3075,6 +3490,7 @@ class App {
|
|
|
3075
3490
|
|
|
3076
3491
|
const cells = renderHighlightedCells(buf, lineNo, segStart, maxW, this.context.colorscheme, pane.selection, _swSearchRanges, braceMatches, isCL ? clBg : null);
|
|
3077
3492
|
putCells(this.screen, pane.x + gutterW, screenRow, cells, maxW);
|
|
3493
|
+
addKittyImage(lineNo, screenRow, subRow);
|
|
3078
3494
|
|
|
3079
3495
|
if (subRow + 1 < breaks.length) {
|
|
3080
3496
|
sloc = { line: lineNo, row: subRow + 1 };
|
|
@@ -3631,6 +4047,7 @@ class App {
|
|
|
3631
4047
|
|
|
3632
4048
|
const text = event.raw;
|
|
3633
4049
|
const seq = event.key;
|
|
4050
|
+
const keydownBlock = indexedMdcuiFenceBlockAtLine(buf, buf?.cursor.y);
|
|
3634
4051
|
if (buf) buf.allowCursorOffscreen = false;
|
|
3635
4052
|
|
|
3636
4053
|
if (this._rawMode) {
|
|
@@ -3641,6 +4058,12 @@ class App {
|
|
|
3641
4058
|
return;
|
|
3642
4059
|
}
|
|
3643
4060
|
|
|
4061
|
+
const keydownEvent = await this.dispatchMdcuiFenceEvent(buf, keydownBlock, event, "keydown");
|
|
4062
|
+
if (keydownEvent?.defaultPrevented && !["ctrl-q", "alt-q", "escape"].includes(seq)) {
|
|
4063
|
+
this.render();
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
|
|
3644
4067
|
// Reset undo insert chain on any non-printable-char key
|
|
3645
4068
|
if (!(seq === text && text.length === 1 && text >= " ")) this._undoInsertChain = false;
|
|
3646
4069
|
|
|
@@ -3829,15 +4252,28 @@ class App {
|
|
|
3829
4252
|
case "backspace":
|
|
3830
4253
|
buf.pushUndo();
|
|
3831
4254
|
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
3832
|
-
else if (await this.runPluginBool("preBackspace"))
|
|
4255
|
+
else if (await this.runPluginBool("preBackspace")) {
|
|
4256
|
+
const textarea = mdcuiTextareaAtCursor(buf);
|
|
4257
|
+
if (!mergeTuiTextareaBackward(buf, textarea)) buf.backspace();
|
|
4258
|
+
}
|
|
3833
4259
|
break;
|
|
3834
4260
|
case "delete":
|
|
3835
4261
|
buf.pushUndo();
|
|
3836
4262
|
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
3837
|
-
else
|
|
4263
|
+
else {
|
|
4264
|
+
const textarea = mdcuiTextareaAtCursor(buf);
|
|
4265
|
+
if (!mergeTuiTextareaForward(buf, textarea)) buf.deleteForward();
|
|
4266
|
+
}
|
|
3838
4267
|
break;
|
|
3839
4268
|
case "enter":
|
|
3840
4269
|
if (isMdcuiEncoding(buf?.encoding)) {
|
|
4270
|
+
const textarea = mdcuiTextareaAtCursor(buf);
|
|
4271
|
+
if (textarea && canEditMdcuiAtCursor(buf)) {
|
|
4272
|
+
buf.pushUndo();
|
|
4273
|
+
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
4274
|
+
insertTuiTextareaNewline(buf, textarea);
|
|
4275
|
+
break;
|
|
4276
|
+
}
|
|
3841
4277
|
await this.handleMdcuiCellCallback(buf, buf.cursor.y, buf.cursor.x, "enter");
|
|
3842
4278
|
break;
|
|
3843
4279
|
}
|
|
@@ -4013,6 +4449,48 @@ class App {
|
|
|
4013
4449
|
this.render();
|
|
4014
4450
|
}
|
|
4015
4451
|
|
|
4452
|
+
async dispatchMdcuiFenceEvent(buf, block, inputEvent, eventName) {
|
|
4453
|
+
const id = block?.header?.id;
|
|
4454
|
+
const declaration = id ? buf?._mdcuiFenceEvents?.get(id) : null;
|
|
4455
|
+
const handler = declaration?.events?.get(eventName);
|
|
4456
|
+
const code = inlineFenceEventCode(handler);
|
|
4457
|
+
if (
|
|
4458
|
+
!code
|
|
4459
|
+
|| declaration.tag !== block?.header?.tag
|
|
4460
|
+
|| !buf?.path
|
|
4461
|
+
|| isHttpUrl(buf.path)
|
|
4462
|
+
) return false;
|
|
4463
|
+
|
|
4464
|
+
const frontPath = `${buf.path}.front.js`;
|
|
4465
|
+
if (!existsSync(frontPath)) return false;
|
|
4466
|
+
try {
|
|
4467
|
+
const selection = globalThis.$?.(`${declaration.tag}#${id}`);
|
|
4468
|
+
const target = {
|
|
4469
|
+
id,
|
|
4470
|
+
tagName: declaration.tag.toUpperCase(),
|
|
4471
|
+
className: declaration.classes.join(" "),
|
|
4472
|
+
};
|
|
4473
|
+
Object.defineProperty(target, "value", {
|
|
4474
|
+
enumerable: true,
|
|
4475
|
+
get: () => selection?.val?.() ?? "",
|
|
4476
|
+
set: (value) => selection?.val?.(value),
|
|
4477
|
+
});
|
|
4478
|
+
const event = tuiKeyEventForFront(inputEvent, target, eventName);
|
|
4479
|
+
const [{ evalFront }, frontMod] = await Promise.all([
|
|
4480
|
+
import("./cui/rpc.mjs"),
|
|
4481
|
+
import(localModuleUrl(frontPath)),
|
|
4482
|
+
]);
|
|
4483
|
+
const result = await evalFront(frontMod, code, { event });
|
|
4484
|
+
if (result && typeof result === "object" && result.ok === false) {
|
|
4485
|
+
this.message = `mdcui ${eventName}: ${String(result.error ?? "Unknown error")}`;
|
|
4486
|
+
}
|
|
4487
|
+
return event;
|
|
4488
|
+
} catch (error) {
|
|
4489
|
+
this.message = `mdcui ${eventName}: ${String(error?.message || error)}`;
|
|
4490
|
+
return null;
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4016
4494
|
async handleMdcuiCellCallback(buf, y = buf?.cursor?.y ?? 0, x = buf?.cursor?.x ?? 0, trigger = "unknown") {
|
|
4017
4495
|
const payload = mdcuiCellPayload(buf, y, x, trigger);
|
|
4018
4496
|
if (!payload) return false;
|
|
@@ -4039,50 +4517,11 @@ class App {
|
|
|
4039
4517
|
import("./cui/rpc.mjs"),
|
|
4040
4518
|
import(localModuleUrl(`${buf.path}.front.js`)),
|
|
4041
4519
|
]);
|
|
4042
|
-
const
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
const prevPrompt = globalThis.prompt;
|
|
4048
|
-
const nativeAlert = typeof prevAlert === "function" ? prevAlert : null;
|
|
4049
|
-
const withNativePrompt = (nativeFn, fallback, args) => {
|
|
4050
|
-
if (!this._started || typeof nativeFn !== "function") {
|
|
4051
|
-
this.message = String(args[0] ?? "");
|
|
4052
|
-
return fallback;
|
|
4053
|
-
}
|
|
4054
|
-
const tty = this._ttyStream ?? process.stdin;
|
|
4055
|
-
this._alertRunning = true;
|
|
4056
|
-
tty.setRawMode?.(false);
|
|
4057
|
-
this.screen.fini();
|
|
4058
|
-
this.screen.previous = null;
|
|
4059
|
-
try {
|
|
4060
|
-
return nativeFn(...args);
|
|
4061
|
-
} finally {
|
|
4062
|
-
tty.setRawMode?.(true);
|
|
4063
|
-
this.screen.previous = null;
|
|
4064
|
-
this.screen.init();
|
|
4065
|
-
this._alertRunning = false;
|
|
4066
|
-
this.render();
|
|
4067
|
-
}
|
|
4068
|
-
};
|
|
4069
|
-
globalThis.alert = (msg = "") => withNativePrompt(nativeAlert, undefined, [String(msg)]);
|
|
4070
|
-
globalThis.confirm = (msg = "") => withNativePrompt(prevConfirm, false, [String(msg)]);
|
|
4071
|
-
globalThis.prompt = (msg = "", defaultValue = "") => withNativePrompt(prevPrompt, defaultValue, [String(msg), String(defaultValue)]);
|
|
4072
|
-
try {
|
|
4073
|
-
const result = await evalFront(frontMod, payload.link);
|
|
4074
|
-
if (result && typeof result === "object" && result.ok === false) {
|
|
4075
|
-
globalThis.alert(String(result.error ?? "Unknown error"));
|
|
4076
|
-
} else if (result != null) {
|
|
4077
|
-
this.message = String(result);
|
|
4078
|
-
}
|
|
4079
|
-
} finally {
|
|
4080
|
-
if (hadAlert) globalThis.alert = prevAlert;
|
|
4081
|
-
else delete globalThis.alert;
|
|
4082
|
-
if (hadConfirm) globalThis.confirm = prevConfirm;
|
|
4083
|
-
else delete globalThis.confirm;
|
|
4084
|
-
if (hadPrompt) globalThis.prompt = prevPrompt;
|
|
4085
|
-
else delete globalThis.prompt;
|
|
4520
|
+
const result = await evalFront(frontMod, payload.link);
|
|
4521
|
+
if (result && typeof result === "object" && result.ok === false) {
|
|
4522
|
+
this.protectedAlert(String(result.error ?? "Unknown error"));
|
|
4523
|
+
} else if (result != null) {
|
|
4524
|
+
this.message = String(result);
|
|
4086
4525
|
}
|
|
4087
4526
|
return true;
|
|
4088
4527
|
} catch (error) {
|
|
@@ -4094,6 +4533,22 @@ class App {
|
|
|
4094
4533
|
const checkboxResult = toggleTaskCheckboxBeforeColumn(payload.line, x);
|
|
4095
4534
|
if (checkboxResult.toggled) {
|
|
4096
4535
|
buf.lines[y] = checkboxResult.line;
|
|
4536
|
+
const checkboxStyle = checkboxResult.checked ? { fg: "green" } : null;
|
|
4537
|
+
const styleLine = buf._ansiStyleLines?.[y];
|
|
4538
|
+
if (Array.isArray(styleLine)) {
|
|
4539
|
+
styleLine[checkboxResult.checkboxAt] = checkboxStyle;
|
|
4540
|
+
if (checkboxResult.line[checkboxResult.checkboxAt + 1] === " ")
|
|
4541
|
+
styleLine[checkboxResult.checkboxAt + 1] = checkboxStyle;
|
|
4542
|
+
}
|
|
4543
|
+
if (typeof buf._mdcuiAnsiText === "string") {
|
|
4544
|
+
const ansiLines = buf._mdcuiAnsiText.split("\n");
|
|
4545
|
+
ansiLines[y] = updateAnsiTaskCheckbox(
|
|
4546
|
+
ansiLines[y],
|
|
4547
|
+
checkboxResult.checkboxAt,
|
|
4548
|
+
checkboxResult.checked,
|
|
4549
|
+
);
|
|
4550
|
+
buf._mdcuiAnsiText = ansiLines.join("\n");
|
|
4551
|
+
}
|
|
4097
4552
|
buf.modified = true;
|
|
4098
4553
|
}
|
|
4099
4554
|
|
|
@@ -6788,6 +7243,7 @@ async function loadBufferForPath(pathOrUrl, context, command = {}, { interactive
|
|
|
6788
7243
|
if (!name.toLowerCase().endsWith(".md")) name += ".md";
|
|
6789
7244
|
const localPath = resolve(name);
|
|
6790
7245
|
await Bun.write(localPath, await fetchHttpBytes(pathOrUrl));
|
|
7246
|
+
remoteMarkdownSources.set(localPath, new URL(pathOrUrl).href);
|
|
6791
7247
|
return await loadBufferForPath(localPath, loadContext, command, { interactive });
|
|
6792
7248
|
}
|
|
6793
7249
|
let buffer;
|
|
@@ -6807,6 +7263,7 @@ async function loadBufferForPath(pathOrUrl, context, command = {}, { interactive
|
|
|
6807
7263
|
sourceText: decoded.sourceText ?? null,
|
|
6808
7264
|
tuiSourceText: decoded.tuiSourceText ?? null,
|
|
6809
7265
|
mdcuiRenderWidth: decoded.mdcuiRenderWidth ?? 0,
|
|
7266
|
+
mdcuiImages: decoded.mdcuiImages ?? null,
|
|
6810
7267
|
});
|
|
6811
7268
|
buffer._configDir = context?.config?.configDir ?? null;
|
|
6812
7269
|
attachSyntax(buffer, loadContext, urlPath, text);
|
|
@@ -7517,6 +7974,7 @@ async function loadBuffers(files, command) {
|
|
|
7517
7974
|
sourceText: decoded.sourceText ?? null,
|
|
7518
7975
|
tuiSourceText: decoded.tuiSourceText ?? null,
|
|
7519
7976
|
mdcuiRenderWidth: decoded.mdcuiRenderWidth ?? 0,
|
|
7977
|
+
mdcuiImages: decoded.mdcuiImages ?? null,
|
|
7520
7978
|
});
|
|
7521
7979
|
if (loadBuffers.context) attachSyntax(stdinBuf, loadBuffers.context, "", stdinText);
|
|
7522
7980
|
buffers.push(stdinBuf);
|
|
@@ -7558,6 +8016,42 @@ async function bundledMarkdownSource(filename) {
|
|
|
7558
8016
|
return readInternalAssetText(filename) ?? await Bun.file(join(REPO_ROOT, filename)).text();
|
|
7559
8017
|
}
|
|
7560
8018
|
|
|
8019
|
+
function availableDemoAssets() {
|
|
8020
|
+
let paths;
|
|
8021
|
+
if (hasInternalAssets()) {
|
|
8022
|
+
paths = listInternalAssetPaths("demos");
|
|
8023
|
+
} else {
|
|
8024
|
+
try {
|
|
8025
|
+
paths = readdirSync(join(REPO_ROOT, "demos"), { withFileTypes: true })
|
|
8026
|
+
.filter((entry) => entry.isFile())
|
|
8027
|
+
.map((entry) => assetPath("demos", entry.name));
|
|
8028
|
+
} catch {
|
|
8029
|
+
paths = [];
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
8032
|
+
|
|
8033
|
+
return [...new Set(paths)]
|
|
8034
|
+
.filter((path) => /^demos\/[^/]+\.md$/i.test(path))
|
|
8035
|
+
.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
8036
|
+
}
|
|
8037
|
+
|
|
8038
|
+
function printDemoList() {
|
|
8039
|
+
const demos = availableDemoAssets().map((asset) => ({
|
|
8040
|
+
option: `--demo-${asset.slice("demos/".length, -".md".length)}`,
|
|
8041
|
+
asset,
|
|
8042
|
+
}));
|
|
8043
|
+
const entries = [{ option: "--demo", asset: "testapp.md" }, ...demos];
|
|
8044
|
+
const optionWidth = Math.max(...entries.map(({ option }) => option.length));
|
|
8045
|
+
|
|
8046
|
+
console.log("Available demos:\n");
|
|
8047
|
+
for (const { option, asset } of entries) {
|
|
8048
|
+
console.log(` ${option.padEnd(optionWidth)} ${asset}`);
|
|
8049
|
+
}
|
|
8050
|
+
console.log("\nCompatibility aliases:\n");
|
|
8051
|
+
console.log(" --demo-imgtool --demo-image-processor");
|
|
8052
|
+
console.log(" --demo-imgtool-zh --demo-image-processor.zh-TW");
|
|
8053
|
+
}
|
|
8054
|
+
|
|
7561
8055
|
async function main() {
|
|
7562
8056
|
if (process.argv[2] === "--wui") {
|
|
7563
8057
|
process.argv.splice(2, 1);
|
|
@@ -7571,6 +8065,8 @@ async function main() {
|
|
|
7571
8065
|
await buildEarlyExit(null,DEFAULT_BUILD_OUTFILE)
|
|
7572
8066
|
|
|
7573
8067
|
const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
|
|
8068
|
+
kittyImageMode = flags.kittyMode;
|
|
8069
|
+
allowRemoteKittyImages = flags.allowUrl;
|
|
7574
8070
|
|
|
7575
8071
|
if (flags.help) {
|
|
7576
8072
|
console.log(usage());
|
|
@@ -7636,19 +8132,27 @@ async function main() {
|
|
|
7636
8132
|
await printTestappSource();
|
|
7637
8133
|
return;
|
|
7638
8134
|
}
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
8135
|
+
if (flags.demoList) {
|
|
8136
|
+
printDemoList();
|
|
8137
|
+
return;
|
|
8138
|
+
}
|
|
8139
|
+
if (flags.demo) {
|
|
8140
|
+
if (flags.demo.error) {
|
|
8141
|
+
console.error(`Invalid demo option ${flags.demo.option}: ${flags.demo.error}`);
|
|
8142
|
+
process.exitCode = 2;
|
|
8143
|
+
return;
|
|
8144
|
+
}
|
|
8145
|
+
let demoSource;
|
|
8146
|
+
try {
|
|
8147
|
+
demoSource = await bundledMarkdownSource(flags.demo.asset);
|
|
8148
|
+
} catch {
|
|
8149
|
+
console.error(`Unknown demo ${flags.demo.option}: ${flags.demo.asset} was not found`);
|
|
8150
|
+
process.exitCode = 2;
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
8153
|
+
const demoPath = resolve(flags.demo.filename);
|
|
7650
8154
|
if (!(await Bun.file(demoPath).exists())) {
|
|
7651
|
-
await Bun.write(demoPath,
|
|
8155
|
+
await Bun.write(demoPath, demoSource);
|
|
7652
8156
|
}
|
|
7653
8157
|
rawFiles.splice(0, rawFiles.length, demoPath);
|
|
7654
8158
|
}
|
|
@@ -7732,7 +8236,16 @@ async function main() {
|
|
|
7732
8236
|
|
|
7733
8237
|
const { files, command } = parseInput(rawFiles);
|
|
7734
8238
|
const jsPlugins = new JsPluginManager();
|
|
7735
|
-
const context = {
|
|
8239
|
+
const context = {
|
|
8240
|
+
colorscheme,
|
|
8241
|
+
syntaxDefinitions,
|
|
8242
|
+
config,
|
|
8243
|
+
runtime,
|
|
8244
|
+
jsPlugins,
|
|
8245
|
+
encodingExplicit,
|
|
8246
|
+
allowUrl: flags.allowUrl,
|
|
8247
|
+
kittyMode: flags.kittyMode,
|
|
8248
|
+
};
|
|
7736
8249
|
jsPlugins.setContext(context);
|
|
7737
8250
|
buildMicroGlobal(jsPlugins); // sets globalThis.micro
|
|
7738
8251
|
|
|
@@ -7836,6 +8349,7 @@ async function main() {
|
|
|
7836
8349
|
addCheckpoint("App Instantiation");
|
|
7837
8350
|
const app = new App(buffers, context);
|
|
7838
8351
|
jsPlugins.setApp(app);
|
|
8352
|
+
app.installProtectedPrompts();
|
|
7839
8353
|
// if (plugins && !pluginErr && app.buffer) plugins.curPaneAdapter = makePaneAdapter(app.buffer, app);
|
|
7840
8354
|
// Dispatch all JS plugin lifecycle hooks after setApp so TermMessage,
|
|
7841
8355
|
// CurPane, cmd/action proxies, and buffer APIs all work correctly.
|