jsmdcui 0.9.0 → 0.11.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/CHANGELOG.md +119 -0
- package/README.md +104 -0
- package/package.json +7 -5
- package/runmd.mjs +71 -18
- package/runtime/help/help.md +104 -0
- package/runtime/jsplugins/example/example.js +3 -1
- package/single-exe/README.md +102 -0
- package/single-exe/README.md-rpc.js +623 -0
- package/single-exe/README.md-server.js +127 -0
- package/single-exe/README.md.back.js +2 -0
- package/single-exe/README.md.front.js +27 -0
- package/single-exe/README.md.html +325 -0
- package/single-exe/compiled.js +57 -3
- package/src/index.js +381 -76
- package/src/plugins/js-bridge.js +5 -0
- package/tests/cat-markdown.test.js +59 -0
- package/tests/compiled-runtime.test.js +68 -1
- package/tests/demo.test.js +177 -3
- package/tests/js-plugin-prompts.test.js +30 -0
- package/tests/wui-responsive-images.test.js +21 -2
- package/tests/wui.test.js +31 -0
package/src/index.js
CHANGED
|
@@ -128,7 +128,7 @@ import { isHex3Encoding, isMdcuiEncoding } from "./runtime/encodings.js";
|
|
|
128
128
|
import { createInterface } from "node:readline/promises";
|
|
129
129
|
|
|
130
130
|
import pkg from "../package.json" with { type: "json" };
|
|
131
|
-
import { REPO_ROOT,IS_COMPILED, buildExecutable,buildEarlyExit } from "../single-exe/compiled.js";
|
|
131
|
+
import { REPO_ROOT,IS_COMPILED, buildExecutable,buildEarlyExit,stringifyNonPrimitiveDefineValues } from "../single-exe/compiled.js";
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -181,6 +181,12 @@ const VERSION = pkg.version;
|
|
|
181
181
|
const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
|
|
182
182
|
const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
|
|
183
183
|
const DEFAULT_BUILD_OUTFILE = "mdcui";
|
|
184
|
+
const MDCUI_DEFAULT_EDIT_ENABLED = typeof MDCUI_DEFAULT_EDIT !== "undefined";
|
|
185
|
+
const MDCUI_DEFAULT_DEMO_ENABLED = typeof MDCUI_DEFAULT_DEMO !== "undefined";
|
|
186
|
+
const MDCUI_DEFAULT_DEMO_WUI_ENABLED = typeof MDCUI_DEFAULT_DEMO_WUI !== "undefined";
|
|
187
|
+
const MDCUI_OVERWRITE_DEMO_ENABLED = typeof MDCUI_OVERWRITE_DEMO !== "undefined";
|
|
188
|
+
let defaultEdit = false;
|
|
189
|
+
let noRuntimeArgs = false;
|
|
184
190
|
const decoder = new TextDecoder();
|
|
185
191
|
let _activeTtyStream = null; // set in App.start() for use by the global error handler
|
|
186
192
|
|
|
@@ -340,7 +346,7 @@ function normalizeEncodingLabel(encoding = "utf-8") {
|
|
|
340
346
|
|
|
341
347
|
function encodingForPath(pathOrUrl, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
|
|
342
348
|
const normalized = normalizeEncodingLabel(encoding);
|
|
343
|
-
if (normalized !== "utf-8" || !inferMdcui) return normalized;
|
|
349
|
+
if (normalized !== "utf-8" || !inferMdcui || defaultEdit) return normalized;
|
|
344
350
|
let pathname = String(pathOrUrl ?? "").replace(/[?#].*$/, "");
|
|
345
351
|
try {
|
|
346
352
|
if (isHttpUrl(pathname)) pathname = new URL(pathname).pathname;
|
|
@@ -1092,6 +1098,8 @@ function parseArgs(argv) {
|
|
|
1092
1098
|
clean: false,
|
|
1093
1099
|
check: false,
|
|
1094
1100
|
cat: false,
|
|
1101
|
+
wui: false,
|
|
1102
|
+
printUi: false,
|
|
1095
1103
|
docs: false,
|
|
1096
1104
|
exportReadme: false,
|
|
1097
1105
|
exportCdpMaze: false,
|
|
@@ -1099,6 +1107,7 @@ function parseArgs(argv) {
|
|
|
1099
1107
|
testapp: false,
|
|
1100
1108
|
demoList: false,
|
|
1101
1109
|
demo: null,
|
|
1110
|
+
overwriteDemo: false,
|
|
1102
1111
|
allowUrl: false,
|
|
1103
1112
|
buildExe: false,
|
|
1104
1113
|
buildFor: "",
|
|
@@ -1122,6 +1131,8 @@ function parseArgs(argv) {
|
|
|
1122
1131
|
else if (arg === "-clean") flags.clean = true;
|
|
1123
1132
|
else if (arg === "--check") flags.check = true;
|
|
1124
1133
|
else if (arg === "--cat" || arg === "-cat" || arg === "--ccat" || arg === "-ccat" || arg === "--bat" || arg === "-bat" || arg === "--glow" || arg === "-glow") flags.cat = true;
|
|
1134
|
+
else if (arg === "--wui") flags.wui = true;
|
|
1135
|
+
else if (arg === "--print-ui") flags.printUi = true;
|
|
1125
1136
|
else if (arg === "--xxd" || arg === "--hexdump") {
|
|
1126
1137
|
flags.cat = true;
|
|
1127
1138
|
flags.settings.set("encoding", "hex3");
|
|
@@ -1138,12 +1149,16 @@ function parseArgs(argv) {
|
|
|
1138
1149
|
else if (arg === "--edit") {
|
|
1139
1150
|
flags.settings.set("encoding", "utf-8");
|
|
1140
1151
|
}
|
|
1152
|
+
else if (arg === "--mdcui" || arg === "--tui") {
|
|
1153
|
+
flags.settings.set("encoding", "mdcui");
|
|
1154
|
+
}
|
|
1141
1155
|
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
1142
1156
|
else if (arg === "--export-readme") flags.exportReadme = true;
|
|
1143
1157
|
else if (arg === "--export-cdp-maze") flags.exportCdpMaze = true;
|
|
1144
1158
|
else if (arg === "--changelog") flags.changelog = true;
|
|
1145
1159
|
else if (arg === "--testapp.md") flags.testapp = true;
|
|
1146
1160
|
else if (arg === "--demo-list") flags.demoList = true;
|
|
1161
|
+
else if (arg === "--overwrite-demo") flags.overwriteDemo = true;
|
|
1147
1162
|
else if (arg === "--cdp-maze") {
|
|
1148
1163
|
flags.cdpMaze = true;
|
|
1149
1164
|
flags.demo = { option: arg, filename: "maze.md", asset: "demos/maze.md" };
|
|
@@ -1159,9 +1174,15 @@ function parseArgs(argv) {
|
|
|
1159
1174
|
}
|
|
1160
1175
|
else if (arg.startsWith("--demo-")) {
|
|
1161
1176
|
const name = arg.slice("--demo-".length);
|
|
1162
|
-
flags.demo =
|
|
1177
|
+
flags.demo = isValidDemoName(name)
|
|
1163
1178
|
? { option: arg, filename: `${name}.md`, asset: `demos/${name}.md` }
|
|
1164
|
-
: {
|
|
1179
|
+
: {
|
|
1180
|
+
option: arg,
|
|
1181
|
+
error:
|
|
1182
|
+
"demo filename must start with a Unicode letter or number "
|
|
1183
|
+
+ "and contain only Unicode letters, numbers, dots, underscores, "
|
|
1184
|
+
+ "or hyphens; whitespace is not allowed",
|
|
1185
|
+
};
|
|
1165
1186
|
}
|
|
1166
1187
|
else if (arg === "--allow-url") flags.allowUrl = true;
|
|
1167
1188
|
else if (arg === "--kitty") flags.kittyMode = "extended";
|
|
@@ -1192,20 +1213,70 @@ function parseArgs(argv) {
|
|
|
1192
1213
|
return { flags, files };
|
|
1193
1214
|
}
|
|
1194
1215
|
|
|
1216
|
+
function isValidDemoName(name) {
|
|
1217
|
+
return /^[\p{L}\p{N}][\p{L}\p{N}\p{M}._-]*$/u.test(String(name));
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function demoNameFromMarkdownBasename(filename) {
|
|
1221
|
+
const value = String(filename);
|
|
1222
|
+
if (!value.endsWith(".md")) return "";
|
|
1223
|
+
const name = value.slice(0, -".md".length);
|
|
1224
|
+
return isValidDemoName(name) ? name : "";
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1195
1227
|
function usage() {
|
|
1196
1228
|
return [
|
|
1197
1229
|
`Usage:
|
|
1198
|
-
${pkg.name} [OPTIONS] [FILE
|
|
1230
|
+
${pkg.name} [OPTIONS] [FILE]
|
|
1231
|
+
|
|
1232
|
+
Execute .md:
|
|
1233
|
+
${pkg.name} [FILE.md]
|
|
1199
1234
|
${pkg.name} --wui [FILE.md]
|
|
1235
|
+
Develop .md:
|
|
1236
|
+
${pkg.name} --edit [FILE.md]
|
|
1237
|
+
${pkg.name} --check <FILE.md>
|
|
1200
1238
|
|
|
1201
1239
|
Modes:
|
|
1202
|
-
--
|
|
1203
|
-
|
|
1204
|
-
|
|
1240
|
+
--tui, --mdcui, -encoding mdcui
|
|
1241
|
+
.md files use this by default
|
|
1242
|
+
Execute a Markdown App
|
|
1243
|
+
Create a Terminal UI
|
|
1244
|
+
Generate & overwrite
|
|
1245
|
+
.front.js, .back.js,
|
|
1246
|
+
.html, -rpc.js, -server.js
|
|
1247
|
+
beside the .md file
|
|
1248
|
+
|
|
1205
1249
|
--wui [FILE.md]
|
|
1206
|
-
|
|
1207
|
-
|
|
1250
|
+
Execute a Markdown App
|
|
1251
|
+
Create a Web UI
|
|
1252
|
+
Start a web server
|
|
1253
|
+
Generate & overwrite
|
|
1254
|
+
.front.js, .back.js,
|
|
1255
|
+
.html, -rpc.js, -server.js
|
|
1256
|
+
beside the .md file
|
|
1257
|
+
|
|
1258
|
+
Without FILE.md,
|
|
1259
|
+
default to ./testapp.md
|
|
1208
1260
|
If ./testapp.md is missing, write the bundled demo there first
|
|
1261
|
+
Combine with any --demo option to start that demo as a Web UI
|
|
1262
|
+
|
|
1263
|
+
--print-ui
|
|
1264
|
+
Must be combined with --wui
|
|
1265
|
+
Print the generated TUI, raw ANSI, and HTML before starting the server
|
|
1266
|
+
|
|
1267
|
+
--kitty
|
|
1268
|
+
Display Markdown images with Kitty graphics and the jsgotty MIME extension
|
|
1269
|
+
--kitty-compat
|
|
1270
|
+
Display Markdown images with Kitty graphics without the non-standard MIME U field
|
|
1271
|
+
|
|
1272
|
+
--edit
|
|
1273
|
+
Open files as editable UTF-8 text
|
|
1274
|
+
Override .md mdcui detection
|
|
1275
|
+
|
|
1276
|
+
--check <FILE.md>
|
|
1277
|
+
Check heading and fenced-block IDs for collisions, print details, and exit
|
|
1278
|
+
Exits 0 when IDs are unique, 1 on collisions, and 2 on usage/read errors
|
|
1279
|
+
|
|
1209
1280
|
--cat, --ccat, --bat, --glow
|
|
1210
1281
|
Render file(s) and write to stdout, then exit (.md uses mdcui/createTui)
|
|
1211
1282
|
A local .md file also writes or overwrites five generated files beside it
|
|
@@ -1214,15 +1285,6 @@ Modes:
|
|
|
1214
1285
|
--hex3, --hex3gz, --hex3zst
|
|
1215
1286
|
Set -encoding hex3, hex3gz, or hex3zst for this session
|
|
1216
1287
|
hex3 shows raw bytes; gz/zst variants compress the same hex3 view
|
|
1217
|
-
--edit
|
|
1218
|
-
Open files as editable UTF-8 text, overriding .md mdcui detection
|
|
1219
|
-
-encoding mdcui
|
|
1220
|
-
Render Markdown through runmd.mjs#createTui; .md files use this automatically
|
|
1221
|
-
Writes .front.js, .back.js, .html, -rpc.js, and -server.js beside the .md file
|
|
1222
|
-
--kitty
|
|
1223
|
-
Display Markdown images with Kitty graphics and the jsgotty MIME extension
|
|
1224
|
-
--kitty-compat
|
|
1225
|
-
Display Markdown images with Kitty graphics without the non-standard MIME U field
|
|
1226
1288
|
|
|
1227
1289
|
Settings:
|
|
1228
1290
|
-SETTING VALUE
|
|
@@ -1230,54 +1292,73 @@ Settings:
|
|
|
1230
1292
|
-options
|
|
1231
1293
|
List all setting names and defaults, then exit.
|
|
1232
1294
|
|
|
1233
|
-
CDP:
|
|
1295
|
+
CDP(Chrome DevTools Protocol):
|
|
1234
1296
|
--cdp-maze
|
|
1235
1297
|
Open the maze demo, start CDP on localhost:9222, and solve it automatically
|
|
1298
|
+
--export-cdp-maze
|
|
1299
|
+
Write or overwrite ./cdp-maze.js with the bundled CDP maze solver & exit
|
|
1300
|
+
|
|
1236
1301
|
--remote-debugging-port=PORT
|
|
1237
1302
|
Start CDP (Chrome DevTools Protocol) server on PORT at launch
|
|
1238
1303
|
--remote-debugging-address=ADDRESS
|
|
1239
1304
|
Bind CDP server to ADDRESS (default: 127.0.0.1); use 0.0.0.0 for all interfaces
|
|
1240
1305
|
|
|
1241
1306
|
Information:
|
|
1242
|
-
|
|
1307
|
+
--help, -h, -help
|
|
1243
1308
|
Show this help & exit
|
|
1244
|
-
|
|
1309
|
+
--version, -V, -version
|
|
1245
1310
|
Show version+backend info & exit
|
|
1246
|
-
|
|
1311
|
+
|
|
1312
|
+
--readme, --docs
|
|
1247
1313
|
Show ${pkg.name}'s README.md & exit
|
|
1248
1314
|
--export-readme
|
|
1249
1315
|
Write or overwrite ./README.md with the bundled README.md & exit
|
|
1250
|
-
|
|
1251
|
-
Write or overwrite ./cdp-maze.js with the bundled CDP maze solver & exit
|
|
1316
|
+
|
|
1252
1317
|
--changelog
|
|
1253
1318
|
Show CHANGELOG.md & exit
|
|
1254
|
-
|
|
1319
|
+
--profile, -profile
|
|
1255
1320
|
Print startup performance profile and exit
|
|
1256
1321
|
|
|
1257
1322
|
Demo:
|
|
1258
1323
|
--testapp.md
|
|
1259
|
-
|
|
1324
|
+
Print the bundled testapp.md to stdout & exit
|
|
1260
1325
|
--demo-list
|
|
1261
|
-
List the bundled demos
|
|
1326
|
+
List the bundled demos & exit
|
|
1327
|
+
|
|
1262
1328
|
--demo
|
|
1263
|
-
|
|
1329
|
+
Execute an existing ./testapp.md
|
|
1330
|
+
Or write the bundled demo if missing
|
|
1264
1331
|
Open it in the TUI and write 5 generated files beside it
|
|
1332
|
+
|
|
1265
1333
|
--demo-<filename>
|
|
1266
|
-
|
|
1334
|
+
Execute an existing <filename>.md
|
|
1335
|
+
Or write the bundled demos/<filename>.md if missing
|
|
1267
1336
|
Open it in the TUI and write 5 generated files beside it
|
|
1268
1337
|
For example: --demo-select, --demo-todo, or --demo-todo-zh
|
|
1338
|
+
|
|
1269
1339
|
--demo-imgtool
|
|
1270
1340
|
Alias for --demo-image-processor
|
|
1271
1341
|
--demo-imgtool-zh
|
|
1272
1342
|
Alias for --demo-image-processor.zh-TW
|
|
1273
1343
|
|
|
1344
|
+
--overwrite-demo
|
|
1345
|
+
Overwrite an existing local demo with the bundled copy; combine with any --demo option
|
|
1346
|
+
|
|
1274
1347
|
Remote Markdown:
|
|
1275
1348
|
--allow-url
|
|
1276
1349
|
Download HTTP(S) Markdown and, with Kitty mode, its HTTP(S) images; allow its code to run
|
|
1277
1350
|
|
|
1278
|
-
Experimental:
|
|
1279
|
-
--build-exe
|
|
1280
|
-
|
|
1351
|
+
Experimental single-exe:
|
|
1352
|
+
--build-exe [BUN_ARGS...]
|
|
1353
|
+
Build a Bun single-file executable & exit
|
|
1354
|
+
--build-for <target> [BUN_ARGS...]
|
|
1355
|
+
Build a Bun single-file executable for target & exit
|
|
1356
|
+
|
|
1357
|
+
[BUN_ARGS...]
|
|
1358
|
+
--define MDCUI_XXX=1
|
|
1359
|
+
More info in --readme
|
|
1360
|
+
https://bun.com/docs/bundler/executables
|
|
1361
|
+
`
|
|
1281
1362
|
].join("\n");
|
|
1282
1363
|
}
|
|
1283
1364
|
|
|
@@ -1337,6 +1418,7 @@ class BufferModel {
|
|
|
1337
1418
|
this._mdcuiFenceEvents = isMdcuiEncoding(this.encoding) ? fenceEventMap(sourceText ?? tuiSourceText ?? text) : new Map();
|
|
1338
1419
|
this._mdcuiImages = isMdcuiEncoding(this.encoding) ? (mdcuiImages ?? []) : [];
|
|
1339
1420
|
this._mdcuiRenderWidth = Math.trunc(Number(mdcuiRenderWidth) || 0);
|
|
1421
|
+
this._useBundledMdcuiModules = false;
|
|
1340
1422
|
this.cursor = { x: 0, y: 0 };
|
|
1341
1423
|
this.scroll = { x: 0, y: 0, row: 0 };
|
|
1342
1424
|
this._modified = false;
|
|
@@ -4477,7 +4559,10 @@ class App {
|
|
|
4477
4559
|
) return false;
|
|
4478
4560
|
|
|
4479
4561
|
const frontPath = `${buf.path}.front.js`;
|
|
4480
|
-
|
|
4562
|
+
const useBundledMdcuiModules = Boolean(
|
|
4563
|
+
global.MDCUI_MAIN && buf._useBundledMdcuiModules,
|
|
4564
|
+
);
|
|
4565
|
+
if (!useBundledMdcuiModules && !existsSync(frontPath)) return false;
|
|
4481
4566
|
try {
|
|
4482
4567
|
const selection = globalThis.$?.(`${declaration.tag}#${id}`);
|
|
4483
4568
|
const target = {
|
|
@@ -4492,8 +4577,12 @@ class App {
|
|
|
4492
4577
|
});
|
|
4493
4578
|
const event = tuiKeyEventForFront(inputEvent, target, eventName);
|
|
4494
4579
|
const [{ evalFront }, frontMod] = await Promise.all([
|
|
4495
|
-
|
|
4496
|
-
|
|
4580
|
+
useBundledMdcuiModules
|
|
4581
|
+
? import(global.MDCUI_MAIN + "-rpc.js")
|
|
4582
|
+
: import("./cui/rpc.mjs"),
|
|
4583
|
+
useBundledMdcuiModules
|
|
4584
|
+
? import(global.MDCUI_MAIN + ".front.js")
|
|
4585
|
+
: import(localModuleUrl(frontPath)),
|
|
4497
4586
|
]);
|
|
4498
4587
|
const result = await evalFront(frontMod, code, { event });
|
|
4499
4588
|
if (result && typeof result === "object" && result.ok === false) {
|
|
@@ -4528,9 +4617,16 @@ class App {
|
|
|
4528
4617
|
{ // defaultCallback
|
|
4529
4618
|
if (payload.link && /^javascript:/i.test(payload.link) && buf?.path && !isHttpUrl(buf.path)) {
|
|
4530
4619
|
try {
|
|
4620
|
+
const useBundledMdcuiModules = Boolean(
|
|
4621
|
+
global.MDCUI_MAIN && buf._useBundledMdcuiModules,
|
|
4622
|
+
);
|
|
4531
4623
|
const [{ evalFront }, frontMod] = await Promise.all([
|
|
4532
|
-
|
|
4533
|
-
|
|
4624
|
+
useBundledMdcuiModules
|
|
4625
|
+
? import(global.MDCUI_MAIN + "-rpc.js")
|
|
4626
|
+
: import("./cui/rpc.mjs"),
|
|
4627
|
+
useBundledMdcuiModules
|
|
4628
|
+
? import(global.MDCUI_MAIN + ".front.js")
|
|
4629
|
+
: import(localModuleUrl(`${buf.path}.front.js`)),
|
|
4534
4630
|
]);
|
|
4535
4631
|
const result = await evalFront(frontMod, payload.link);
|
|
4536
4632
|
if (result && typeof result === "object" && result.ok === false) {
|
|
@@ -5418,10 +5514,18 @@ class App {
|
|
|
5418
5514
|
const frontPath = buffer.path && !isHttpUrl(buffer.path)
|
|
5419
5515
|
? `${buffer.path}.front.js`
|
|
5420
5516
|
: "";
|
|
5421
|
-
|
|
5517
|
+
const useBundledMdcuiModules = Boolean(
|
|
5518
|
+
global.MDCUI_MAIN && buffer._useBundledMdcuiModules,
|
|
5519
|
+
);
|
|
5520
|
+
if (
|
|
5521
|
+
!useBundledMdcuiModules
|
|
5522
|
+
&& (!frontPath || !existsSync(frontPath))
|
|
5523
|
+
) return;
|
|
5422
5524
|
|
|
5423
5525
|
try {
|
|
5424
|
-
const frontMod =
|
|
5526
|
+
const frontMod = useBundledMdcuiModules
|
|
5527
|
+
? await import(global.MDCUI_MAIN + ".front.js")
|
|
5528
|
+
: await import(localModuleUrl(frontPath));
|
|
5425
5529
|
if (typeof frontMod.onMdcuiExit !== "function") return;
|
|
5426
5530
|
await frontMod.onMdcuiExit({
|
|
5427
5531
|
reason,
|
|
@@ -6958,6 +7062,11 @@ function makeBufferAdapter(buffer) {
|
|
|
6958
7062
|
get Name() { return buffer.Name ?? buffer.name ?? ""; },
|
|
6959
7063
|
get Modified() { return buffer.modified ?? false; },
|
|
6960
7064
|
get Settings() { return buffer.Settings; },
|
|
7065
|
+
get MdcuiModuleSource() {
|
|
7066
|
+
return global.MDCUI_MAIN && buffer._useBundledMdcuiModules
|
|
7067
|
+
? "embedded"
|
|
7068
|
+
: "external";
|
|
7069
|
+
},
|
|
6961
7070
|
Line: (...args) => buffer.Line(Number(lastArg(args))),
|
|
6962
7071
|
Insert: (...args) => insertAtLoc(buffer, decodeLoc(args.at(-2)), String(args.at(-1))),
|
|
6963
7072
|
Replace: (...args) => replaceAtLocs(buffer, decodeLoc(args.at(-3)), decodeLoc(args.at(-2)), String(args.at(-1))),
|
|
@@ -8073,19 +8182,182 @@ function printDemoList() {
|
|
|
8073
8182
|
console.log(" --demo-imgtool-zh --demo-image-processor.zh-TW");
|
|
8074
8183
|
}
|
|
8075
8184
|
|
|
8185
|
+
async function prepareDemo(flags, rawFiles) {
|
|
8186
|
+
if (!flags.demo) return { ok: true };
|
|
8187
|
+
if (flags.demo.error) {
|
|
8188
|
+
console.error(`Invalid demo option ${flags.demo.option}: ${flags.demo.error}`);
|
|
8189
|
+
process.exitCode = 2;
|
|
8190
|
+
return { ok: false };
|
|
8191
|
+
}
|
|
8192
|
+
let demoSource;
|
|
8193
|
+
try {
|
|
8194
|
+
demoSource = await bundledMarkdownSource(flags.demo.asset);
|
|
8195
|
+
} catch {
|
|
8196
|
+
console.error(`Unknown demo ${flags.demo.option}: ${flags.demo.asset} was not found`);
|
|
8197
|
+
process.exitCode = 2;
|
|
8198
|
+
return { ok: false };
|
|
8199
|
+
}
|
|
8200
|
+
|
|
8201
|
+
const demoPath = resolve(flags.demo.filename);
|
|
8202
|
+
const demoFile = Bun.file(demoPath);
|
|
8203
|
+
const demoExists = await demoFile.exists();
|
|
8204
|
+
const writeBundledContent = flags.overwriteDemo || !demoExists;
|
|
8205
|
+
const bundledByteLength = Buffer.byteLength(demoSource);
|
|
8206
|
+
const demoByteLength = writeBundledContent
|
|
8207
|
+
? bundledByteLength
|
|
8208
|
+
: demoFile.size;
|
|
8209
|
+
const configuredMainBase = String(
|
|
8210
|
+
global.MDCUI_MAIN_BASE
|
|
8211
|
+
|| (global.MDCUI_MAIN ? basename(String(global.MDCUI_MAIN)) : ""),
|
|
8212
|
+
);
|
|
8213
|
+
const isConfiguredMainDemo = Boolean(
|
|
8214
|
+
global.MDCUI_MAIN
|
|
8215
|
+
&& configuredMainBase
|
|
8216
|
+
&& basename(demoPath) === configuredMainBase,
|
|
8217
|
+
);
|
|
8218
|
+
|
|
8219
|
+
if (writeBundledContent) {
|
|
8220
|
+
await Bun.write(demoPath, demoSource);
|
|
8221
|
+
} else if (isConfiguredMainDemo && demoByteLength !== bundledByteLength) {
|
|
8222
|
+
console.error(
|
|
8223
|
+
`[mdcui] ${demoPath} differs from the bundled demo `
|
|
8224
|
+
+ `(${demoByteLength} bytes vs ${bundledByteLength} bytes). `
|
|
8225
|
+
+ "The edited demo may not work correctly if it imports companion modules "
|
|
8226
|
+
+ "instead of keeping all application code in the Markdown file.",
|
|
8227
|
+
);
|
|
8228
|
+
}
|
|
8229
|
+
|
|
8230
|
+
rawFiles.splice(0, rawFiles.length, demoPath);
|
|
8231
|
+
return {
|
|
8232
|
+
ok: true,
|
|
8233
|
+
path: demoPath,
|
|
8234
|
+
useBundledModules: isConfiguredMainDemo
|
|
8235
|
+
&& demoByteLength === bundledByteLength,
|
|
8236
|
+
};
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
function stringDefineFromArgv(argv, name) {
|
|
8240
|
+
const prefix = `${name}=`;
|
|
8241
|
+
for (let i = 0; i < argv.length; i++) {
|
|
8242
|
+
let definition = "";
|
|
8243
|
+
if (argv[i] === "--define") definition = argv[i + 1] ?? "";
|
|
8244
|
+
else if (argv[i].startsWith("--define="))
|
|
8245
|
+
definition = argv[i].slice("--define=".length);
|
|
8246
|
+
if (!definition.startsWith(prefix)) continue;
|
|
8247
|
+
try {
|
|
8248
|
+
const value = JSON.parse(definition.slice(prefix.length));
|
|
8249
|
+
return typeof value === "string" ? value : "";
|
|
8250
|
+
} catch {
|
|
8251
|
+
return "";
|
|
8252
|
+
}
|
|
8253
|
+
}
|
|
8254
|
+
return "";
|
|
8255
|
+
}
|
|
8256
|
+
|
|
8076
8257
|
async function main() {
|
|
8077
|
-
|
|
8078
|
-
|
|
8258
|
+
addCheckpoint("Argument Parsing");
|
|
8259
|
+
|
|
8260
|
+
stringifyNonPrimitiveDefineValues(process.argv, "global.MDCUI_MAIN");
|
|
8261
|
+
const argvMdcuiMain = stringDefineFromArgv(
|
|
8262
|
+
process.argv,
|
|
8263
|
+
"global.MDCUI_MAIN",
|
|
8264
|
+
);
|
|
8265
|
+
let sourceMdcuiMain = "";
|
|
8266
|
+
if (IS_COMPILED) {
|
|
8267
|
+
sourceMdcuiMain = "";
|
|
8268
|
+
} else {
|
|
8269
|
+
sourceMdcuiMain = String(argvMdcuiMain || global.MDCUI_MAIN || "");
|
|
8270
|
+
}
|
|
8271
|
+
|
|
8272
|
+
if (sourceMdcuiMain) {
|
|
8273
|
+
const mdcuiMain = resolve(sourceMdcuiMain);
|
|
8274
|
+
const mdcuiMainBase = basename(mdcuiMain);
|
|
8275
|
+
const mdcuiMainDemoName = demoNameFromMarkdownBasename(mdcuiMainBase);
|
|
8276
|
+
if (!mdcuiMainDemoName) {
|
|
8277
|
+
throw new Error(
|
|
8278
|
+
"MDCUI_MAIN basename must end with lowercase .md, start with a "
|
|
8279
|
+
+ "Unicode letter or number, and contain no whitespace; only Unicode "
|
|
8280
|
+
+ "letters, numbers, dots, underscores, and hyphens are allowed",
|
|
8281
|
+
);
|
|
8282
|
+
}
|
|
8079
8283
|
const runmd = await import("../runmd.mjs");
|
|
8080
|
-
|
|
8081
|
-
|
|
8284
|
+
const mdcuiMainFile = Bun.file(mdcuiMain);
|
|
8285
|
+
if (!(await mdcuiMainFile.exists())) {
|
|
8286
|
+
throw new Error(`MDCUI_MAIN file not found: ${mdcuiMain}`);
|
|
8287
|
+
}
|
|
8288
|
+
let markdown = await mdcuiMainFile.text();
|
|
8289
|
+
const demoPath = join(REPO_ROOT, "demos", mdcuiMainBase);
|
|
8290
|
+
if (resolve(mdcuiMain) !== resolve(demoPath)) {
|
|
8291
|
+
await Bun.write(demoPath, markdown);
|
|
8292
|
+
}
|
|
8293
|
+
markdown = await runmd.extractJs(markdown, mdcuiMain, {
|
|
8294
|
+
bundling: true,
|
|
8295
|
+
});
|
|
8296
|
+
await runmd.createWui(markdown, mdcuiMain, {
|
|
8297
|
+
bundling: true,
|
|
8298
|
+
});
|
|
8299
|
+
|
|
8300
|
+
if (
|
|
8301
|
+
process.argv.includes("--build-for")
|
|
8302
|
+
|| process.argv.includes("--build-exe")
|
|
8303
|
+
) {
|
|
8304
|
+
if (argvMdcuiMain) {
|
|
8305
|
+
const normalizedDefine =
|
|
8306
|
+
`global.MDCUI_MAIN=${JSON.stringify(mdcuiMain)}`;
|
|
8307
|
+
for (let i = 0; i < process.argv.length; i++) {
|
|
8308
|
+
if (
|
|
8309
|
+
process.argv[i] === "--define"
|
|
8310
|
+
&& process.argv[i + 1]?.startsWith("global.MDCUI_MAIN=")
|
|
8311
|
+
) {
|
|
8312
|
+
process.argv[i + 1] = normalizedDefine;
|
|
8313
|
+
} else if (
|
|
8314
|
+
process.argv[i].startsWith("--define=global.MDCUI_MAIN=")
|
|
8315
|
+
) {
|
|
8316
|
+
process.argv[i] = `--define=${normalizedDefine}`;
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
} else {
|
|
8320
|
+
process.argv.push(
|
|
8321
|
+
"--define",
|
|
8322
|
+
`global.MDCUI_MAIN=${JSON.stringify(mdcuiMain)}`,
|
|
8323
|
+
);
|
|
8324
|
+
}
|
|
8325
|
+
process.argv.push(
|
|
8326
|
+
"--define",
|
|
8327
|
+
`global.MDCUI_MAIN_BASE=${JSON.stringify(mdcuiMainBase)}`,
|
|
8328
|
+
);
|
|
8329
|
+
}
|
|
8082
8330
|
}
|
|
8083
8331
|
|
|
8084
|
-
addCheckpoint("Argument Parsing");
|
|
8085
|
-
|
|
8086
8332
|
await buildEarlyExit(null,DEFAULT_BUILD_OUTFILE)
|
|
8333
|
+
defaultEdit = await Bun.file(join(REPO_ROOT, "src", "MDCUI_DEFAULT_EDIT")).exists();
|
|
8087
8334
|
|
|
8088
|
-
const
|
|
8335
|
+
const runtimeArgs = process.argv.slice(2);
|
|
8336
|
+
noRuntimeArgs = runtimeArgs.length === 0;
|
|
8337
|
+
if (MDCUI_DEFAULT_EDIT_ENABLED) runtimeArgs.unshift("--edit");
|
|
8338
|
+
if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_ENABLED) runtimeArgs.push("--demo");
|
|
8339
|
+
if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_WUI_ENABLED) runtimeArgs.push("--wui");
|
|
8340
|
+
if (
|
|
8341
|
+
noRuntimeArgs
|
|
8342
|
+
&& (global.MDCUI_MAIN || global.MDCUI_MAIN_BASE)
|
|
8343
|
+
) {
|
|
8344
|
+
const mdcuiMainBase = String(
|
|
8345
|
+
global.MDCUI_MAIN_BASE || basename(String(global.MDCUI_MAIN)),
|
|
8346
|
+
);
|
|
8347
|
+
const demoName = demoNameFromMarkdownBasename(mdcuiMainBase);
|
|
8348
|
+
if (!demoName) {
|
|
8349
|
+
console.error(
|
|
8350
|
+
"Invalid MDCUI_MAIN_BASE: expected a lowercase .md basename with "
|
|
8351
|
+
+ "Unicode letters or numbers and no whitespace",
|
|
8352
|
+
);
|
|
8353
|
+
process.exitCode = 2;
|
|
8354
|
+
return;
|
|
8355
|
+
}
|
|
8356
|
+
runtimeArgs.push(`--demo-${demoName}`);
|
|
8357
|
+
}
|
|
8358
|
+
if (MDCUI_OVERWRITE_DEMO_ENABLED) runtimeArgs.push("--overwrite-demo");
|
|
8359
|
+
const { flags, files: rawFiles } = parseArgs(runtimeArgs);
|
|
8360
|
+
let demoPreparation = null;
|
|
8089
8361
|
kittyImageMode = flags.kittyMode;
|
|
8090
8362
|
allowRemoteKittyImages = flags.allowUrl;
|
|
8091
8363
|
|
|
@@ -8096,7 +8368,7 @@ async function main() {
|
|
|
8096
8368
|
if (flags.version) {
|
|
8097
8369
|
const ttsCmd = detectTtsCmd();
|
|
8098
8370
|
console.log(pkg.name+":",pkg.description)
|
|
8099
|
-
console.log("
|
|
8371
|
+
console.log(" Made by: Dr. John (醫者小智)")
|
|
8100
8372
|
console.log("")
|
|
8101
8373
|
console.log("Version:", VERSION);
|
|
8102
8374
|
console.log("Runtime:", `Bun ${Bun.version}`);
|
|
@@ -8104,6 +8376,19 @@ async function main() {
|
|
|
8104
8376
|
console.log("Http client:",detectHttpBackend());
|
|
8105
8377
|
console.log("TTS:", ttsCmd ? ttsCmd.cmd[0] : "not found");
|
|
8106
8378
|
console.log({SUPPORTED_ENCODING_LABELS})
|
|
8379
|
+
console.log("Distribution settings:");
|
|
8380
|
+
console.log(" MDCUI_DEFAULT_EDIT:", (MDCUI_DEFAULT_EDIT_ENABLED || defaultEdit) ? "enabled" : "disabled");
|
|
8381
|
+
console.log(" MDCUI_DEFAULT_DEMO:", MDCUI_DEFAULT_DEMO_ENABLED ? "enabled" : "disabled");
|
|
8382
|
+
console.log(" MDCUI_DEFAULT_DEMO_WUI:", MDCUI_DEFAULT_DEMO_WUI_ENABLED ? "enabled" : "disabled");
|
|
8383
|
+
console.log(" MDCUI_OVERWRITE_DEMO:", MDCUI_OVERWRITE_DEMO_ENABLED ? "enabled" : "disabled");
|
|
8384
|
+
console.log(
|
|
8385
|
+
" global.MDCUI_MAIN:",
|
|
8386
|
+
String(global.MDCUI_MAIN || "") || "not set",
|
|
8387
|
+
);
|
|
8388
|
+
console.log(
|
|
8389
|
+
" global.MDCUI_MAIN_BASE:",
|
|
8390
|
+
String(global.MDCUI_MAIN_BASE || "") || "not set",
|
|
8391
|
+
);
|
|
8107
8392
|
const clipboard = new ClipboardManager();
|
|
8108
8393
|
let osc52Available = false;
|
|
8109
8394
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -8118,6 +8403,18 @@ async function main() {
|
|
|
8118
8403
|
console.log("Clipboard:", backends);
|
|
8119
8404
|
return;
|
|
8120
8405
|
}
|
|
8406
|
+
if (flags.wui) {
|
|
8407
|
+
demoPreparation = await prepareDemo(flags, rawFiles);
|
|
8408
|
+
if (!demoPreparation.ok) return;
|
|
8409
|
+
const runmd = await import("../runmd.mjs");
|
|
8410
|
+
await runmd.main(30, {
|
|
8411
|
+
mdpath: rawFiles[0] ?? null,
|
|
8412
|
+
overwriteDemo: flags.overwriteDemo && rawFiles.length === 0,
|
|
8413
|
+
printUi: flags.printUi,
|
|
8414
|
+
useBundledMdcuiServer: demoPreparation.useBundledModules,
|
|
8415
|
+
});
|
|
8416
|
+
return;
|
|
8417
|
+
}
|
|
8121
8418
|
if (flags.check) {
|
|
8122
8419
|
if (rawFiles.length !== 1) {
|
|
8123
8420
|
console.error("Usage: jsmdcui --check FILE.md");
|
|
@@ -8162,24 +8459,8 @@ async function main() {
|
|
|
8162
8459
|
return;
|
|
8163
8460
|
}
|
|
8164
8461
|
if (flags.demo) {
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
process.exitCode = 2;
|
|
8168
|
-
return;
|
|
8169
|
-
}
|
|
8170
|
-
let demoSource;
|
|
8171
|
-
try {
|
|
8172
|
-
demoSource = await bundledMarkdownSource(flags.demo.asset);
|
|
8173
|
-
} catch {
|
|
8174
|
-
console.error(`Unknown demo ${flags.demo.option}: ${flags.demo.asset} was not found`);
|
|
8175
|
-
process.exitCode = 2;
|
|
8176
|
-
return;
|
|
8177
|
-
}
|
|
8178
|
-
const demoPath = resolve(flags.demo.filename);
|
|
8179
|
-
if (!(await Bun.file(demoPath).exists())) {
|
|
8180
|
-
await Bun.write(demoPath, demoSource);
|
|
8181
|
-
}
|
|
8182
|
-
rawFiles.splice(0, rawFiles.length, demoPath);
|
|
8462
|
+
demoPreparation = await prepareDemo(flags, rawFiles);
|
|
8463
|
+
if (!demoPreparation.ok) return;
|
|
8183
8464
|
}
|
|
8184
8465
|
if (flags.options) {
|
|
8185
8466
|
for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
@@ -8192,6 +8473,7 @@ async function main() {
|
|
|
8192
8473
|
const config = await new Config({ configDir: flags.configDir }).init();
|
|
8193
8474
|
config.applyCliSettings(flags.settings);
|
|
8194
8475
|
const encodingExplicit = flags.settings.has("encoding") || Object.hasOwn(config.parsedSettings, "encoding");
|
|
8476
|
+
const inputFiletype = flags.settings.has("filetype") ? String(flags.settings.get("filetype")) : null;
|
|
8195
8477
|
syncEditorSettings(config);
|
|
8196
8478
|
|
|
8197
8479
|
addCheckpoint("Runtime Registry Init");
|
|
@@ -8203,7 +8485,14 @@ async function main() {
|
|
|
8203
8485
|
const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
|
|
8204
8486
|
|
|
8205
8487
|
if (flags.cat) {
|
|
8206
|
-
await catFiles(
|
|
8488
|
+
await catFiles(
|
|
8489
|
+
rawFiles,
|
|
8490
|
+
colorscheme,
|
|
8491
|
+
syntaxDefinitions,
|
|
8492
|
+
config.getGlobalOption("encoding"),
|
|
8493
|
+
!encodingExplicit,
|
|
8494
|
+
inputFiletype,
|
|
8495
|
+
);
|
|
8207
8496
|
return;
|
|
8208
8497
|
}
|
|
8209
8498
|
|
|
@@ -8268,6 +8557,7 @@ async function main() {
|
|
|
8268
8557
|
runtime,
|
|
8269
8558
|
jsPlugins,
|
|
8270
8559
|
encodingExplicit,
|
|
8560
|
+
inputFiletype,
|
|
8271
8561
|
allowUrl: flags.allowUrl,
|
|
8272
8562
|
kittyMode: flags.kittyMode,
|
|
8273
8563
|
};
|
|
@@ -8318,6 +8608,16 @@ async function main() {
|
|
|
8318
8608
|
const buffers = await loadBuffers(files.map((file) =>
|
|
8319
8609
|
isHttpUrl(file) ? file : resolve(file)
|
|
8320
8610
|
), command);
|
|
8611
|
+
if (demoPreparation?.useBundledModules) {
|
|
8612
|
+
const bundledDemoBuffer = buffers.find((buffer) =>
|
|
8613
|
+
buffer.path
|
|
8614
|
+
&& !isHttpUrl(buffer.path)
|
|
8615
|
+
&& resolve(buffer.path) === demoPreparation.path
|
|
8616
|
+
);
|
|
8617
|
+
if (bundledDemoBuffer) {
|
|
8618
|
+
bundledDemoBuffer._useBundledMdcuiModules = true;
|
|
8619
|
+
}
|
|
8620
|
+
}
|
|
8321
8621
|
|
|
8322
8622
|
let historyPromise = Promise.resolve();
|
|
8323
8623
|
if (config.getGlobalOption("savehistory") !== false) {
|
|
@@ -8725,9 +9025,12 @@ function getSelectionText(buf, selection) {
|
|
|
8725
9025
|
|
|
8726
9026
|
function attachSyntax(buffer, context, path, text) {
|
|
8727
9027
|
buffer._syntaxContext = context;
|
|
8728
|
-
const
|
|
9028
|
+
const explicitFiletype = context.inputFiletype;
|
|
9029
|
+
const def = explicitFiletype == null
|
|
9030
|
+
? detectBufferSyntax(context.syntaxDefinitions, path, text)
|
|
9031
|
+
: context.syntaxDefinitions?.find((candidate) => candidate.filetype === explicitFiletype) ?? null;
|
|
8729
9032
|
buffer.syntaxDefinition = def;
|
|
8730
|
-
buffer.filetype = def?.filetype ?? "unknown";
|
|
9033
|
+
buffer.filetype = explicitFiletype == null ? (def?.filetype ?? "unknown") : String(explicitFiletype);
|
|
8731
9034
|
buffer.Settings.filetype = buffer.filetype;
|
|
8732
9035
|
buffer.highlighter = def ? new Highlighter(def, context.syntaxDefinitions ?? []) : null;
|
|
8733
9036
|
buffer._highlightCache = null;
|
|
@@ -8805,7 +9108,7 @@ function syncEditorSettings(config) {
|
|
|
8805
9108
|
}
|
|
8806
9109
|
}
|
|
8807
9110
|
|
|
8808
|
-
async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
|
|
9111
|
+
async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true, filetype = null) {
|
|
8809
9112
|
const targets = files.length > 0 ? files.map((f) => ({ path: f, stdin: false })) : [{ path: null, stdin: true }];
|
|
8810
9113
|
for (const { path: filePath, stdin } of targets) {
|
|
8811
9114
|
let content;
|
|
@@ -8836,7 +9139,7 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
|
|
|
8836
9139
|
process.stdout.write(ansiContent ?? content);
|
|
8837
9140
|
if (!(ansiContent ?? content).endsWith("\n")) process.stdout.write("\n");
|
|
8838
9141
|
continue;
|
|
8839
|
-
} else if (effectivePath && /\.md$/i.test(effectivePath)) {
|
|
9142
|
+
} else if (filetype == null && effectivePath && /\.md$/i.test(effectivePath)) {
|
|
8840
9143
|
process.stdout.write(
|
|
8841
9144
|
Bun.markdown.ansi(content,{
|
|
8842
9145
|
hyperlinks:true
|
|
@@ -8845,11 +9148,13 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
|
|
|
8845
9148
|
continue;
|
|
8846
9149
|
}
|
|
8847
9150
|
const lines = normalizeBufferText(content).split("\n");
|
|
8848
|
-
const def =
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
9151
|
+
const def = filetype == null
|
|
9152
|
+
? detectSyntax(syntaxDefinitions, {
|
|
9153
|
+
path: effectivePath ?? "",
|
|
9154
|
+
firstLine: lines[0] ?? "",
|
|
9155
|
+
lines: lines.slice(0, 50),
|
|
9156
|
+
})
|
|
9157
|
+
: syntaxDefinitions.find((candidate) => candidate.filetype === filetype) ?? null;
|
|
8853
9158
|
const highlighter = def ? new Highlighter(def, syntaxDefinitions) : null;
|
|
8854
9159
|
if (!highlighter) {
|
|
8855
9160
|
process.stdout.write(content);
|