jsmdcui 0.8.0 → 0.10.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/src/index.js CHANGED
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- let mainPromise = globalThis.assetsLoaderPromise ||
4
- Promise.resolve();
5
-
6
3
  const jsStart = globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6;
7
4
  const checkpoints = [
8
5
  { name: "Bun Engine Boot", time: 0 },
@@ -147,7 +144,12 @@ if(!globalThis.Bun)
147
144
  //console.log(process.argv)
148
145
 
149
146
  console.error('Ran by node, changed to run by bun')
150
- process.execve(bunbinary,process.argv,process.env);
147
+ const launched = child_process.spawnSync(bunbinary, process.argv.slice(1), {
148
+ stdio: "inherit",
149
+ env: process.env,
150
+ });
151
+ if (launched.error) throw launched.error;
152
+ process.exit(launched.status ?? 1);
151
153
  }
152
154
  catch(e){
153
155
  console.log(`
@@ -179,6 +181,11 @@ const VERSION = pkg.version;
179
181
  const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
180
182
  const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
181
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;
182
189
  const decoder = new TextDecoder();
183
190
  let _activeTtyStream = null; // set in App.start() for use by the global error handler
184
191
 
@@ -338,7 +345,7 @@ function normalizeEncodingLabel(encoding = "utf-8") {
338
345
 
339
346
  function encodingForPath(pathOrUrl, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
340
347
  const normalized = normalizeEncodingLabel(encoding);
341
- if (normalized !== "utf-8" || !inferMdcui) return normalized;
348
+ if (normalized !== "utf-8" || !inferMdcui || defaultEdit) return normalized;
342
349
  let pathname = String(pathOrUrl ?? "").replace(/[?#].*$/, "");
343
350
  try {
344
351
  if (isHttpUrl(pathname)) pathname = new URL(pathname).pathname;
@@ -1090,12 +1097,16 @@ function parseArgs(argv) {
1090
1097
  clean: false,
1091
1098
  check: false,
1092
1099
  cat: false,
1100
+ wui: false,
1101
+ printUi: false,
1093
1102
  docs: false,
1094
1103
  exportReadme: false,
1104
+ exportCdpMaze: false,
1095
1105
  changelog: false,
1096
1106
  testapp: false,
1097
1107
  demoList: false,
1098
1108
  demo: null,
1109
+ overwriteDemo: false,
1099
1110
  allowUrl: false,
1100
1111
  buildExe: false,
1101
1112
  buildFor: "",
@@ -1103,6 +1114,7 @@ function parseArgs(argv) {
1103
1114
  debug: false,
1104
1115
  profile: false,
1105
1116
  plugin: "",
1117
+ cdpMaze: false,
1106
1118
  cdpPort: 0,
1107
1119
  cdpAddress: "",
1108
1120
  kittyMode: "off",
@@ -1118,6 +1130,8 @@ function parseArgs(argv) {
1118
1130
  else if (arg === "-clean") flags.clean = true;
1119
1131
  else if (arg === "--check") flags.check = true;
1120
1132
  else if (arg === "--cat" || arg === "-cat" || arg === "--ccat" || arg === "-ccat" || arg === "--bat" || arg === "-bat" || arg === "--glow" || arg === "-glow") flags.cat = true;
1133
+ else if (arg === "--wui") flags.wui = true;
1134
+ else if (arg === "--print-ui") flags.printUi = true;
1121
1135
  else if (arg === "--xxd" || arg === "--hexdump") {
1122
1136
  flags.cat = true;
1123
1137
  flags.settings.set("encoding", "hex3");
@@ -1134,11 +1148,20 @@ function parseArgs(argv) {
1134
1148
  else if (arg === "--edit") {
1135
1149
  flags.settings.set("encoding", "utf-8");
1136
1150
  }
1151
+ else if (arg === "--mdcui" || arg === "--tui") {
1152
+ flags.settings.set("encoding", "mdcui");
1153
+ }
1137
1154
  else if (arg === "--docs" || arg === "--readme") flags.docs = true;
1138
1155
  else if (arg === "--export-readme") flags.exportReadme = true;
1156
+ else if (arg === "--export-cdp-maze") flags.exportCdpMaze = true;
1139
1157
  else if (arg === "--changelog") flags.changelog = true;
1140
1158
  else if (arg === "--testapp.md") flags.testapp = true;
1141
1159
  else if (arg === "--demo-list") flags.demoList = true;
1160
+ else if (arg === "--overwrite-demo") flags.overwriteDemo = true;
1161
+ else if (arg === "--cdp-maze") {
1162
+ flags.cdpMaze = true;
1163
+ flags.demo = { option: arg, filename: "maze.md", asset: "demos/maze.md" };
1164
+ }
1142
1165
  else if (arg === "--demo") {
1143
1166
  flags.demo = { option: arg, filename: "testapp.md", asset: "testapp.md" };
1144
1167
  }
@@ -1178,23 +1201,64 @@ function parseArgs(argv) {
1178
1201
  }
1179
1202
  }
1180
1203
 
1204
+ if (flags.cdpMaze && !flags.cdpPort) flags.cdpPort = 9222;
1205
+
1181
1206
  return { flags, files };
1182
1207
  }
1183
1208
 
1184
1209
  function usage() {
1185
1210
  return [
1186
1211
  `Usage:
1187
- ${pkg.name} [OPTIONS] [FILE.md]
1212
+ ${pkg.name} [OPTIONS] [FILE]
1213
+
1214
+ Execute .md:
1215
+ ${pkg.name} [FILE.md]
1188
1216
  ${pkg.name} --wui [FILE.md]
1217
+ Develop .md:
1218
+ ${pkg.name} --edit [FILE.md]
1219
+ ${pkg.name} --check <FILE.md>
1189
1220
 
1190
1221
  Modes:
1191
- --check FILE.md
1192
- Check heading and fenced-block IDs for collisions, print details, and exit
1193
- Exits 0 when IDs are unique, 1 on collisions, and 2 on usage/read errors
1222
+ --tui, --mdcui, -encoding mdcui
1223
+ .md files use this by default
1224
+ Execute a Markdown App
1225
+ Create a Terminal UI
1226
+ Generate & overwrite
1227
+ .front.js, .back.js,
1228
+ .html, -rpc.js, -server.js
1229
+ beside the .md file
1230
+
1194
1231
  --wui [FILE.md]
1195
- Generate or overwrite Markdown UI files beside FILE.md and start the server
1196
- Without FILE.md, use the existing ./testapp.md without overwriting it
1232
+ Execute a Markdown App
1233
+ Create a Web UI
1234
+ Start a web server
1235
+ Generate & overwrite
1236
+ .front.js, .back.js,
1237
+ .html, -rpc.js, -server.js
1238
+ beside the .md file
1239
+
1240
+ Without FILE.md,
1241
+ default to ./testapp.md
1197
1242
  If ./testapp.md is missing, write the bundled demo there first
1243
+ Combine with any --demo option to start that demo as a Web UI
1244
+
1245
+ --print-ui
1246
+ Must be combined with --wui
1247
+ Print the generated TUI, raw ANSI, and HTML before starting the server
1248
+
1249
+ --kitty
1250
+ Display Markdown images with Kitty graphics and the jsgotty MIME extension
1251
+ --kitty-compat
1252
+ Display Markdown images with Kitty graphics without the non-standard MIME U field
1253
+
1254
+ --edit
1255
+ Open files as editable UTF-8 text
1256
+ Override .md mdcui detection
1257
+
1258
+ --check <FILE.md>
1259
+ Check heading and fenced-block IDs for collisions, print details, and exit
1260
+ Exits 0 when IDs are unique, 1 on collisions, and 2 on usage/read errors
1261
+
1198
1262
  --cat, --ccat, --bat, --glow
1199
1263
  Render file(s) and write to stdout, then exit (.md uses mdcui/createTui)
1200
1264
  A local .md file also writes or overwrites five generated files beside it
@@ -1203,15 +1267,6 @@ Modes:
1203
1267
  --hex3, --hex3gz, --hex3zst
1204
1268
  Set -encoding hex3, hex3gz, or hex3zst for this session
1205
1269
  hex3 shows raw bytes; gz/zst variants compress the same hex3 view
1206
- --edit
1207
- Open files as editable UTF-8 text, overriding .md mdcui detection
1208
- -encoding mdcui
1209
- Render Markdown through runmd.mjs#createTui; .md files use this automatically
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
1215
1270
 
1216
1271
  Settings:
1217
1272
  -SETTING VALUE
@@ -1219,50 +1274,73 @@ Settings:
1219
1274
  -options
1220
1275
  List all setting names and defaults, then exit.
1221
1276
 
1222
- CDP:
1277
+ CDP(Chrome DevTools Protocol):
1278
+ --cdp-maze
1279
+ Open the maze demo, start CDP on localhost:9222, and solve it automatically
1280
+ --export-cdp-maze
1281
+ Write or overwrite ./cdp-maze.js with the bundled CDP maze solver & exit
1282
+
1223
1283
  --remote-debugging-port=PORT
1224
1284
  Start CDP (Chrome DevTools Protocol) server on PORT at launch
1225
1285
  --remote-debugging-address=ADDRESS
1226
1286
  Bind CDP server to ADDRESS (default: 127.0.0.1); use 0.0.0.0 for all interfaces
1227
1287
 
1228
1288
  Information:
1229
- -help, -h, --help
1289
+ --help, -h, -help
1230
1290
  Show this help & exit
1231
- -version, -V, --version
1291
+ --version, -V, -version
1232
1292
  Show version+backend info & exit
1233
- --docs, --readme
1293
+
1294
+ --readme, --docs
1234
1295
  Show ${pkg.name}'s README.md & exit
1235
1296
  --export-readme
1236
1297
  Write or overwrite ./README.md with the bundled README.md & exit
1298
+
1237
1299
  --changelog
1238
1300
  Show CHANGELOG.md & exit
1239
- -profile, --profile
1301
+ --profile, -profile
1240
1302
  Print startup performance profile and exit
1241
1303
 
1242
1304
  Demo:
1243
1305
  --testapp.md
1244
- Write the bundled testapp.md to stdout & exit
1306
+ Print the bundled testapp.md to stdout & exit
1245
1307
  --demo-list
1246
- List the bundled demos and their command-line options, then exit
1308
+ List the bundled demos & exit
1309
+
1247
1310
  --demo
1248
- Use the existing ./testapp.md without overwriting it, or write the bundled demo if missing
1311
+ Execute an existing ./testapp.md
1312
+ Or write the bundled demo if missing
1249
1313
  Open it in the TUI and write 5 generated files beside it
1314
+
1250
1315
  --demo-<filename>
1251
- Load demos/<filename>.md, preserving an existing ./<filename>.md or writing the bundled copy
1316
+ Execute an existing <filename>.md
1317
+ Or write the bundled demos/<filename>.md if missing
1252
1318
  Open it in the TUI and write 5 generated files beside it
1253
1319
  For example: --demo-select, --demo-todo, or --demo-todo-zh
1320
+
1254
1321
  --demo-imgtool
1255
1322
  Alias for --demo-image-processor
1256
1323
  --demo-imgtool-zh
1257
1324
  Alias for --demo-image-processor.zh-TW
1258
1325
 
1326
+ --overwrite-demo
1327
+ Overwrite an existing local demo with the bundled copy; combine with any --demo option
1328
+
1259
1329
  Remote Markdown:
1260
1330
  --allow-url
1261
1331
  Download HTTP(S) Markdown and, with Kitty mode, its HTTP(S) images; allow its code to run
1262
1332
 
1263
- Experimental:
1264
- --build-exe Build a Bun single-file executable and exit
1265
- --build-for <target> Build a Bun single-file executable for target`
1333
+ Experimental single-exe:
1334
+ --build-exe [BUN_ARGS...]
1335
+ Build a Bun single-file executable & exit
1336
+ --build-for <target> [BUN_ARGS...]
1337
+ Build a Bun single-file executable for target & exit
1338
+
1339
+ [BUN_ARGS...]
1340
+ --define MDCUI_XXX=true
1341
+ More info in --readme
1342
+ https://bun.com/docs/bundler/executables
1343
+ `
1266
1344
  ].join("\n");
1267
1345
  }
1268
1346
 
@@ -8003,6 +8081,12 @@ async function exportReadme() {
8003
8081
  console.log(`Wrote ${readmePath}`);
8004
8082
  }
8005
8083
 
8084
+ async function exportCdpMaze() {
8085
+ const outputPath = resolve("cdp-maze.js");
8086
+ await Bun.write(outputPath, await bundledMarkdownSource("cdp-maze.js"));
8087
+ console.log(`Wrote ${outputPath}`);
8088
+ }
8089
+
8006
8090
  async function printChangelogDocs() {
8007
8091
  const changelog = readInternalAssetText("CHANGELOG.md") ?? await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
8008
8092
  process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
@@ -8052,19 +8136,42 @@ function printDemoList() {
8052
8136
  console.log(" --demo-imgtool-zh --demo-image-processor.zh-TW");
8053
8137
  }
8054
8138
 
8055
- async function main() {
8056
- if (process.argv[2] === "--wui") {
8057
- process.argv.splice(2, 1);
8058
- const runmd = await import("../runmd.mjs");
8059
- await runmd.main();
8060
- return;
8139
+ async function prepareDemo(flags, rawFiles) {
8140
+ if (!flags.demo) return true;
8141
+ if (flags.demo.error) {
8142
+ console.error(`Invalid demo option ${flags.demo.option}: ${flags.demo.error}`);
8143
+ process.exitCode = 2;
8144
+ return false;
8061
8145
  }
8146
+ let demoSource;
8147
+ try {
8148
+ demoSource = await bundledMarkdownSource(flags.demo.asset);
8149
+ } catch {
8150
+ console.error(`Unknown demo ${flags.demo.option}: ${flags.demo.asset} was not found`);
8151
+ process.exitCode = 2;
8152
+ return false;
8153
+ }
8154
+ const demoPath = resolve(flags.demo.filename);
8155
+ if (flags.overwriteDemo || !(await Bun.file(demoPath).exists())) {
8156
+ await Bun.write(demoPath, demoSource);
8157
+ }
8158
+ rawFiles.splice(0, rawFiles.length, demoPath);
8159
+ return true;
8160
+ }
8062
8161
 
8162
+ async function main() {
8063
8163
  addCheckpoint("Argument Parsing");
8064
8164
 
8065
8165
  await buildEarlyExit(null,DEFAULT_BUILD_OUTFILE)
8166
+ defaultEdit = await Bun.file(join(REPO_ROOT, "src", "MDCUI_DEFAULT_EDIT")).exists();
8066
8167
 
8067
- const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
8168
+ const runtimeArgs = process.argv.slice(2);
8169
+ const noRuntimeArgs = runtimeArgs.length === 0;
8170
+ if (MDCUI_DEFAULT_EDIT_ENABLED) runtimeArgs.unshift("--edit");
8171
+ if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_ENABLED) runtimeArgs.push("--demo");
8172
+ if (noRuntimeArgs && MDCUI_DEFAULT_DEMO_WUI_ENABLED) runtimeArgs.push("--wui");
8173
+ if (MDCUI_OVERWRITE_DEMO_ENABLED) runtimeArgs.push("--overwrite-demo");
8174
+ const { flags, files: rawFiles } = parseArgs(runtimeArgs);
8068
8175
  kittyImageMode = flags.kittyMode;
8069
8176
  allowRemoteKittyImages = flags.allowUrl;
8070
8177
 
@@ -8075,7 +8182,7 @@ async function main() {
8075
8182
  if (flags.version) {
8076
8183
  const ttsCmd = detectTtsCmd();
8077
8184
  console.log(pkg.name+":",pkg.description)
8078
- console.log(" Rewritten by: Dr. John (醫者小智)")
8185
+ console.log(" Made by: Dr. John (醫者小智)")
8079
8186
  console.log("")
8080
8187
  console.log("Version:", VERSION);
8081
8188
  console.log("Runtime:", `Bun ${Bun.version}`);
@@ -8083,6 +8190,11 @@ async function main() {
8083
8190
  console.log("Http client:",detectHttpBackend());
8084
8191
  console.log("TTS:", ttsCmd ? ttsCmd.cmd[0] : "not found");
8085
8192
  console.log({SUPPORTED_ENCODING_LABELS})
8193
+ console.log("Distribution settings:");
8194
+ console.log(" MDCUI_DEFAULT_EDIT:", (MDCUI_DEFAULT_EDIT_ENABLED || defaultEdit) ? "enabled" : "disabled");
8195
+ console.log(" MDCUI_DEFAULT_DEMO:", MDCUI_DEFAULT_DEMO_ENABLED ? "enabled" : "disabled");
8196
+ console.log(" MDCUI_DEFAULT_DEMO_WUI:", MDCUI_DEFAULT_DEMO_WUI_ENABLED ? "enabled" : "disabled");
8197
+ console.log(" MDCUI_OVERWRITE_DEMO:", MDCUI_OVERWRITE_DEMO_ENABLED ? "enabled" : "disabled");
8086
8198
  const clipboard = new ClipboardManager();
8087
8199
  let osc52Available = false;
8088
8200
  if (process.stdin.isTTY && process.stdout.isTTY) {
@@ -8097,6 +8209,16 @@ async function main() {
8097
8209
  console.log("Clipboard:", backends);
8098
8210
  return;
8099
8211
  }
8212
+ if (flags.wui) {
8213
+ if (!(await prepareDemo(flags, rawFiles))) return;
8214
+ const runmd = await import("../runmd.mjs");
8215
+ await runmd.main(30, {
8216
+ mdpath: rawFiles[0] ?? null,
8217
+ overwriteDemo: flags.overwriteDemo && rawFiles.length === 0,
8218
+ printUi: flags.printUi,
8219
+ });
8220
+ return;
8221
+ }
8100
8222
  if (flags.check) {
8101
8223
  if (rawFiles.length !== 1) {
8102
8224
  console.error("Usage: jsmdcui --check FILE.md");
@@ -8124,6 +8246,10 @@ async function main() {
8124
8246
  await exportReadme();
8125
8247
  return;
8126
8248
  }
8249
+ if (flags.exportCdpMaze) {
8250
+ await exportCdpMaze();
8251
+ return;
8252
+ }
8127
8253
  if (flags.changelog) {
8128
8254
  await printChangelogDocs();
8129
8255
  return;
@@ -8137,24 +8263,7 @@ async function main() {
8137
8263
  return;
8138
8264
  }
8139
8265
  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);
8154
- if (!(await Bun.file(demoPath).exists())) {
8155
- await Bun.write(demoPath, demoSource);
8156
- }
8157
- rawFiles.splice(0, rawFiles.length, demoPath);
8266
+ if (!(await prepareDemo(flags, rawFiles))) return;
8158
8267
  }
8159
8268
  if (flags.options) {
8160
8269
  for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
@@ -8167,6 +8276,7 @@ async function main() {
8167
8276
  const config = await new Config({ configDir: flags.configDir }).init();
8168
8277
  config.applyCliSettings(flags.settings);
8169
8278
  const encodingExplicit = flags.settings.has("encoding") || Object.hasOwn(config.parsedSettings, "encoding");
8279
+ const inputFiletype = flags.settings.has("filetype") ? String(flags.settings.get("filetype")) : null;
8170
8280
  syncEditorSettings(config);
8171
8281
 
8172
8282
  addCheckpoint("Runtime Registry Init");
@@ -8178,7 +8288,14 @@ async function main() {
8178
8288
  const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
8179
8289
 
8180
8290
  if (flags.cat) {
8181
- await catFiles(rawFiles, colorscheme, syntaxDefinitions, config.getGlobalOption("encoding"), !encodingExplicit);
8291
+ await catFiles(
8292
+ rawFiles,
8293
+ colorscheme,
8294
+ syntaxDefinitions,
8295
+ config.getGlobalOption("encoding"),
8296
+ !encodingExplicit,
8297
+ inputFiletype,
8298
+ );
8182
8299
  return;
8183
8300
  }
8184
8301
 
@@ -8243,6 +8360,7 @@ async function main() {
8243
8360
  runtime,
8244
8361
  jsPlugins,
8245
8362
  encodingExplicit,
8363
+ inputFiletype,
8246
8364
  allowUrl: flags.allowUrl,
8247
8365
  kittyMode: flags.kittyMode,
8248
8366
  };
@@ -8370,6 +8488,23 @@ async function main() {
8370
8488
  if (flags.cdpAddress) cdpArgs.push(`--address=${flags.cdpAddress}`);
8371
8489
  await app.handleCommand(`cdp ${cdpArgs.join(" ")}`);
8372
8490
  }
8491
+
8492
+ if (flags.cdpMaze) {
8493
+ const solverHost = !flags.cdpAddress || flags.cdpAddress === "0.0.0.0"
8494
+ ? "127.0.0.1"
8495
+ : flags.cdpAddress;
8496
+ const solverUrl = `ws://${solverHost}:${flags.cdpPort}/devtools/browser/cdp-server`;
8497
+ setTimeout(async () => {
8498
+ try {
8499
+ const { runCdpMaze } = await import("../cdp-maze.js");
8500
+ app.message = await runCdpMaze(solverUrl);
8501
+ app.render?.();
8502
+ } catch (error) {
8503
+ app.message = `CDP maze solver failed: ${error?.message || error}`;
8504
+ app.render?.();
8505
+ }
8506
+ }, 3000);
8507
+ }
8373
8508
 
8374
8509
  if (flags.profile) {
8375
8510
  addCheckpoint("Clipboard Probing");
@@ -8683,9 +8818,12 @@ function getSelectionText(buf, selection) {
8683
8818
 
8684
8819
  function attachSyntax(buffer, context, path, text) {
8685
8820
  buffer._syntaxContext = context;
8686
- const def = detectBufferSyntax(context.syntaxDefinitions, path, text);
8821
+ const explicitFiletype = context.inputFiletype;
8822
+ const def = explicitFiletype == null
8823
+ ? detectBufferSyntax(context.syntaxDefinitions, path, text)
8824
+ : context.syntaxDefinitions?.find((candidate) => candidate.filetype === explicitFiletype) ?? null;
8687
8825
  buffer.syntaxDefinition = def;
8688
- buffer.filetype = def?.filetype ?? "unknown";
8826
+ buffer.filetype = explicitFiletype == null ? (def?.filetype ?? "unknown") : String(explicitFiletype);
8689
8827
  buffer.Settings.filetype = buffer.filetype;
8690
8828
  buffer.highlighter = def ? new Highlighter(def, context.syntaxDefinitions ?? []) : null;
8691
8829
  buffer._highlightCache = null;
@@ -8763,7 +8901,7 @@ function syncEditorSettings(config) {
8763
8901
  }
8764
8902
  }
8765
8903
 
8766
- async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
8904
+ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true, filetype = null) {
8767
8905
  const targets = files.length > 0 ? files.map((f) => ({ path: f, stdin: false })) : [{ path: null, stdin: true }];
8768
8906
  for (const { path: filePath, stdin } of targets) {
8769
8907
  let content;
@@ -8794,7 +8932,7 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
8794
8932
  process.stdout.write(ansiContent ?? content);
8795
8933
  if (!(ansiContent ?? content).endsWith("\n")) process.stdout.write("\n");
8796
8934
  continue;
8797
- } else if (effectivePath && /\.md$/i.test(effectivePath)) {
8935
+ } else if (filetype == null && effectivePath && /\.md$/i.test(effectivePath)) {
8798
8936
  process.stdout.write(
8799
8937
  Bun.markdown.ansi(content,{
8800
8938
  hyperlinks:true
@@ -8803,11 +8941,13 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
8803
8941
  continue;
8804
8942
  }
8805
8943
  const lines = normalizeBufferText(content).split("\n");
8806
- const def = detectSyntax(syntaxDefinitions, {
8807
- path: effectivePath ?? "",
8808
- firstLine: lines[0] ?? "",
8809
- lines: lines.slice(0, 50),
8810
- });
8944
+ const def = filetype == null
8945
+ ? detectSyntax(syntaxDefinitions, {
8946
+ path: effectivePath ?? "",
8947
+ firstLine: lines[0] ?? "",
8948
+ lines: lines.slice(0, 50),
8949
+ })
8950
+ : syntaxDefinitions.find((candidate) => candidate.filetype === filetype) ?? null;
8811
8951
  const highlighter = def ? new Highlighter(def, syntaxDefinitions) : null;
8812
8952
  if (!highlighter) {
8813
8953
  process.stdout.write(content);
@@ -8837,9 +8977,6 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
8837
8977
  }
8838
8978
 
8839
8979
 
8840
- mainPromise.then(r=>{
8841
-
8842
-
8843
8980
  main().catch((error) => {
8844
8981
  try {
8845
8982
  (_activeTtyStream ?? process.stdin).setRawMode?.(false);
@@ -8849,6 +8986,3 @@ main().catch((error) => {
8849
8986
  process.exit(1);
8850
8987
  }
8851
8988
  });
8852
-
8853
-
8854
- })
package/src/lua/engine.js CHANGED
@@ -2,7 +2,6 @@ import { existsSync } from "node:fs";
2
2
  import { readInternalAssetBytes } from "../runtime/assets.js";
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
- import { isCompiledBinary, resolveCompiledBaseDir } from "../runtime/compiled.js";
6
5
  import { REPO_ROOT } from "../../single-exe/compiled.js";
7
6
 
8
7
  export async function createLuaEngine() {
@@ -44,9 +43,7 @@ async function resolveLuaWasmLocation() {
44
43
  }
45
44
  }
46
45
 
47
- const fallbackPath = isCompiledBinary(process.argv)
48
- ? join(resolveCompiledBaseDir({ argv: process.argv }), wasmAssetPath)
49
- : join(REPO_ROOT, wasmAssetPath);
46
+ const fallbackPath = join(REPO_ROOT, wasmAssetPath);
50
47
 
51
48
  return existsSync(fallbackPath) ? fallbackPath : undefined;
52
49
  }
@@ -4,6 +4,7 @@ import { dirname, join, basename, extname } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { tmpdir } from "node:os";
6
6
  import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetBytes } from "../runtime/assets.js";
7
+ import { isMdcuiEncoding } from "../runtime/encodings.js";
7
8
  import { newMessage, newMessageAtLine, MTError, MTWarning, MTInfo } from "../buffer/message.js";
8
9
  import { Loc } from "../buffer/loc.js";
9
10
 
@@ -1405,6 +1406,48 @@ export function buildMicroGlobal(jsManager) {
1405
1406
  return app?.buffer?.lines.join("\n") ?? "";
1406
1407
  },
1407
1408
 
1409
+ // Returns the rendered ANSI document when available, otherwise plain text.
1410
+ getAllAnsiText() {
1411
+ const buffer = getApp()?.buffer;
1412
+ return typeof buffer?._mdcuiAnsiText === "string"
1413
+ ? buffer._mdcuiAnsiText
1414
+ : buffer?.lines.join("\n") ?? "";
1415
+ },
1416
+
1417
+ // Activates a 1-based rendered-buffer cell in MDCUI; otherwise falls back to goto.
1418
+ async clickBufferCell(column = 1, line = 1) {
1419
+ const app = getApp();
1420
+ const buffer = app?.buffer;
1421
+ if (!app || !buffer) return false;
1422
+
1423
+ const targetLine = Math.max(1, Math.trunc(Number(line)) || 1);
1424
+ const targetColumn = Math.max(1, Math.trunc(Number(column)) || 1);
1425
+ if (!isMdcuiEncoding(buffer.encoding ?? buffer.Settings?.encoding)) {
1426
+ await app.handleCommand(`goto ${targetLine}:${targetColumn}`);
1427
+ app.render?.();
1428
+ return true;
1429
+ }
1430
+
1431
+ const y = Math.min(targetLine - 1, Math.max(0, buffer.lines.length - 1));
1432
+ const x = Math.min(targetColumn - 1, String(buffer.lines[y] ?? "").length);
1433
+ buffer.cursor = { x, y };
1434
+ const handled = await app.handleMdcuiCellCallback(buffer, y, x, "mouse");
1435
+ app.render?.();
1436
+ return handled;
1437
+ },
1438
+
1439
+ // Sends terminal input through the App's normal parser and event pipeline.
1440
+ async _dispatchRawInput(raw) {
1441
+ const app = getApp();
1442
+ if (!app) return false;
1443
+ const bytes = typeof raw === "string"
1444
+ ? new TextEncoder().encode(raw)
1445
+ : raw;
1446
+ await app._dispatchInput(bytes);
1447
+ app.render?.();
1448
+ return true;
1449
+ },
1450
+
1408
1451
  // Replaces the entire buffer content with text (may contain newlines).
1409
1452
  putAllText(text) {
1410
1453
  const app = getApp();
@@ -78,3 +78,62 @@ test("--edit overrides the .md mdcui default with utf8", async () => {
78
78
  await rm(dir, { recursive: true, force: true });
79
79
  }
80
80
  });
81
+
82
+ test("--mdcui and --tui are equivalent to -encoding mdcui", async () => {
83
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-cat-mdcui-"));
84
+ const markdownPath = join(dir, "sample.md");
85
+ await writeFile(markdownPath, "# Heading\n\n- one\n- two\n");
86
+
87
+ try {
88
+ const flag = Bun.spawnSync([bunBin, tui, "-cat", "--mdcui", markdownPath], {
89
+ cwd: dir,
90
+ stdout: "pipe",
91
+ stderr: "pipe",
92
+ });
93
+ const encoding = Bun.spawnSync([bunBin, tui, "-cat", "-encoding", "mdcui", markdownPath], {
94
+ cwd: dir,
95
+ stdout: "pipe",
96
+ stderr: "pipe",
97
+ });
98
+ const tuiFlag = Bun.spawnSync([bunBin, tui, "-cat", "--tui", markdownPath], {
99
+ cwd: dir,
100
+ stdout: "pipe",
101
+ stderr: "pipe",
102
+ });
103
+
104
+ expect(flag.exitCode).toBe(0);
105
+ expect(encoding.exitCode).toBe(0);
106
+ expect(tuiFlag.exitCode).toBe(0);
107
+ expect(flag.stdout.toString()).toBe(encoding.stdout.toString());
108
+ expect(tuiFlag.stdout.toString()).toBe(encoding.stdout.toString());
109
+ } finally {
110
+ await rm(dir, { recursive: true, force: true });
111
+ }
112
+ });
113
+
114
+ test("-filetype forces syntax highlighting independently of the filename", async () => {
115
+ const dir = await mkdtemp(join(tmpdir(), "jsmdcui-cat-filetype-"));
116
+ const textPath = join(dir, "sample.txt");
117
+ await writeFile(textPath, "const answer = 42;\n");
118
+
119
+ try {
120
+ const automatic = Bun.spawnSync([bunBin, tui, "-cat", textPath], {
121
+ cwd: dir,
122
+ stdout: "pipe",
123
+ stderr: "pipe",
124
+ });
125
+ const javascript = Bun.spawnSync([bunBin, tui, "-cat", "-filetype", "javascript", textPath], {
126
+ cwd: dir,
127
+ stdout: "pipe",
128
+ stderr: "pipe",
129
+ });
130
+
131
+ expect(automatic.exitCode).toBe(0);
132
+ expect(javascript.exitCode).toBe(0);
133
+ expect(automatic.stdout.toString()).not.toContain("\x1b[");
134
+ expect(javascript.stdout.toString()).toContain("\x1b[");
135
+ expect(Bun.stripANSI(javascript.stdout.toString()).trimEnd()).toBe("const answer = 42;");
136
+ } finally {
137
+ await rm(dir, { recursive: true, force: true });
138
+ }
139
+ });
@@ -0,0 +1,8 @@
1
+ import { expect, test } from "bun:test";
2
+ import { isCompiledBinary as isSingleExeCompiled } from "../single-exe/compiled.js";
3
+
4
+ test("single-exe recognizes Bun compiled virtual paths", () => {
5
+ expect(isSingleExeCompiled(["bun", "/$bunfs/root/app.js"])).toBe(true);
6
+ expect(isSingleExeCompiled(["bun", "B:/~BUN/root/app.js"])).toBe(true);
7
+ expect(isSingleExeCompiled(["bun", "/project/src/index.js"])).toBe(false);
8
+ });