jsmdcui 0.3.0 → 0.5.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 +137 -0
- package/README.md +56 -2
- package/package.json +1 -1
- package/runmd.mjs +71 -7
- package/runtime/help/help.md +56 -2
- package/runtime/syntax/markdown.yaml +12 -0
- package/src/buffer/backup.js +8 -3
- package/src/config/config.js +3 -0
- package/src/config/defaults.js +2 -2
- package/src/cui/rpc.mjs +137 -2
- package/src/index.js +301 -47
- package/src/plugins/js-bridge.js +168 -0
- package/testapp.md +23 -0
- package/tests/backup.test.js +25 -0
- package/tests/cat-markdown.test.js +79 -0
- package/tests/config-settings.test.js +52 -0
- package/tests/demo.test.js +24 -0
package/src/index.js
CHANGED
|
@@ -195,7 +195,7 @@ const DEFAULT_SETTINGS = {
|
|
|
195
195
|
backup: true,
|
|
196
196
|
backupdir: "",
|
|
197
197
|
permbackup: false,
|
|
198
|
-
softwrap:
|
|
198
|
+
softwrap: true,
|
|
199
199
|
wordwrap: false,
|
|
200
200
|
pageoverlap: 2,
|
|
201
201
|
scrollmargin: 3,
|
|
@@ -304,14 +304,14 @@ function encodeHex3Text(text) {
|
|
|
304
304
|
return decodeBinaryBytes(Buffer.from(text, "latin1"));
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
async function readTextFileWithEncoding(path, encoding = "utf-8") {
|
|
307
|
+
async function readTextFileWithEncoding(path, encoding = "utf-8", inferMdcui = true) {
|
|
308
308
|
const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
|
|
309
|
-
return decodeAndRenderTextBytes(bytes, encodingForPath(path, encoding), process.stdout.columns || 80, path);
|
|
309
|
+
return decodeAndRenderTextBytes(bytes, encodingForPath(path, encoding, inferMdcui), process.stdout.columns || 80, path);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
async function fetchTextWithEncoding(url, encoding = "utf-8") {
|
|
312
|
+
async function fetchTextWithEncoding(url, encoding = "utf-8", inferMdcui = true) {
|
|
313
313
|
const bytes = await fetchHttpBytes(url);
|
|
314
|
-
return decodeAndRenderTextBytes(new Uint8Array(bytes), encodingForPath(url, encoding));
|
|
314
|
+
return decodeAndRenderTextBytes(new Uint8Array(bytes), encodingForPath(url, encoding, inferMdcui));
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
function normalizeEncodingLabel(encoding = "utf-8") {
|
|
@@ -321,9 +321,9 @@ function normalizeEncodingLabel(encoding = "utf-8") {
|
|
|
321
321
|
return new TextDecoder(s).encoding;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
function encodingForPath(pathOrUrl, encoding = DEFAULT_SETTINGS.encoding) {
|
|
324
|
+
function encodingForPath(pathOrUrl, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
|
|
325
325
|
const normalized = normalizeEncodingLabel(encoding);
|
|
326
|
-
if (normalized !== "utf-8") return normalized;
|
|
326
|
+
if (normalized !== "utf-8" || !inferMdcui) return normalized;
|
|
327
327
|
let pathname = String(pathOrUrl ?? "").replace(/[?#].*$/, "");
|
|
328
328
|
try {
|
|
329
329
|
if (isHttpUrl(pathname)) pathname = new URL(pathname).pathname;
|
|
@@ -538,6 +538,70 @@ function mdcuiCellPayload(buf, y, x, trigger = "unknown") {
|
|
|
538
538
|
};
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
+
function resizeMdcuiTextBlock(buf, y, x) {
|
|
542
|
+
if (!buf || !isMdcuiEncoding(buf.encoding) || x !== 0) return null;
|
|
543
|
+
const line = String(buf.lines?.[y] ?? "");
|
|
544
|
+
const top = line.match(/^(\s*)(┌─|╭─|\+-)\s*text(?:[#.][A-Za-z_][\w:.-]*)*\s*$/);
|
|
545
|
+
const bottom = line.match(/^(\s*)(└─|╰─|\+-)\s*$/);
|
|
546
|
+
if (!top && !bottom) return null;
|
|
547
|
+
|
|
548
|
+
let insertAt = -1;
|
|
549
|
+
let removeAt = -1;
|
|
550
|
+
let bodyLine = "";
|
|
551
|
+
|
|
552
|
+
if (bottom) {
|
|
553
|
+
for (let row = y - 1; row >= 0; row--) {
|
|
554
|
+
const header = String(buf.lines[row] ?? "").match(/^(\s*)(┌─|╭─|\+-)\s*text(?:[#.][A-Za-z_][\w:.-]*)*\s*$/);
|
|
555
|
+
if (!header || header[1] !== bottom[1]) continue;
|
|
556
|
+
const marker = header[2] === "+-" ? "|" : "│";
|
|
557
|
+
insertAt = y;
|
|
558
|
+
bodyLine = header[1] + marker + " ";
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
} else if (top) {
|
|
562
|
+
const marker = top[2] === "+-" ? "|" : "│";
|
|
563
|
+
for (let row = y + 1; row < buf.lines.length; row++) {
|
|
564
|
+
const rest = String(buf.lines[row] ?? "").slice(top[1].length);
|
|
565
|
+
if (/^(?:└─|╰─|\+-)\s*$/.test(rest)) {
|
|
566
|
+
const candidate = row - 1;
|
|
567
|
+
if (candidate > y && String(buf.lines[candidate] ?? "") === top[1] + marker + " ")
|
|
568
|
+
removeAt = candidate;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (insertAt < 0 && removeAt < 0) return "unchanged";
|
|
575
|
+
buf.pushUndo?.(true);
|
|
576
|
+
|
|
577
|
+
const row = insertAt >= 0 ? insertAt : removeAt;
|
|
578
|
+
const deleteCount = removeAt >= 0 ? 1 : 0;
|
|
579
|
+
const replacement = insertAt >= 0 ? [bodyLine] : [];
|
|
580
|
+
buf.lines.splice(row, deleteCount, ...replacement);
|
|
581
|
+
|
|
582
|
+
if (Array.isArray(buf._ansiStyleLines)) {
|
|
583
|
+
const template = buf._ansiStyleLines[Math.max(0, row - 1)] ?? null;
|
|
584
|
+
buf._ansiStyleLines.splice(row, deleteCount, ...replacement.map(() => template));
|
|
585
|
+
}
|
|
586
|
+
if (typeof buf._mdcuiAnsiText === "string") {
|
|
587
|
+
const ansiLines = buf._mdcuiAnsiText.split("\n");
|
|
588
|
+
ansiLines.splice(row, deleteCount, ...replacement);
|
|
589
|
+
buf._mdcuiAnsiText = ansiLines.join("\n");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (insertAt >= 0) {
|
|
593
|
+
if (buf.cursor.y >= insertAt) buf.cursor.y++;
|
|
594
|
+
} else if (buf.cursor.y > removeAt) {
|
|
595
|
+
buf.cursor.y--;
|
|
596
|
+
} else if (buf.cursor.y === removeAt) {
|
|
597
|
+
buf.cursor.y = Math.max(0, removeAt - 1);
|
|
598
|
+
}
|
|
599
|
+
buf.invalidateHighlightFrom?.(row, { force: true });
|
|
600
|
+
buf.modified = true;
|
|
601
|
+
buf.ensureCursor?.();
|
|
602
|
+
return insertAt >= 0 ? "added" : "removed";
|
|
603
|
+
}
|
|
604
|
+
|
|
541
605
|
function detectFileFormat(text, fallback = DEFAULT_SETTINGS.fileformat) {
|
|
542
606
|
if (text.length === 0) return fallback === "dos" ? "dos" : "unix";
|
|
543
607
|
const newlineIdx = text.indexOf("\n");
|
|
@@ -557,6 +621,24 @@ function isReadonlyBuffer(buf) {
|
|
|
557
621
|
return Boolean(buf?.readonly || buf?.Settings?.readonly || buf?.Type?.Readonly);
|
|
558
622
|
}
|
|
559
623
|
|
|
624
|
+
function mdcuiEditablePrefixLength(buf, y = buf?.cursor?.y ?? 0) {
|
|
625
|
+
if (!isMdcuiEncoding(buf?.encoding ?? buf?.Settings?.encoding)) return 0;
|
|
626
|
+
const line = String(buf?.lines?.[y] ?? "");
|
|
627
|
+
return /^(?:│|\|) /.test(line) ? 2 : 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function canEditMdcuiAtCursor(buf) {
|
|
631
|
+
const prefixLength = mdcuiEditablePrefixLength(buf);
|
|
632
|
+
return prefixLength > 0 && (buf?.cursor?.x ?? 0) >= prefixLength;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function canEditMdcuiSelection(buf, selection) {
|
|
636
|
+
if (!selection) return true;
|
|
637
|
+
const { first, last } = selectionBounds(selection);
|
|
638
|
+
const prefixLength = mdcuiEditablePrefixLength(buf, first.y);
|
|
639
|
+
return first.y === last.y && prefixLength > 0 && first.x >= prefixLength;
|
|
640
|
+
}
|
|
641
|
+
|
|
560
642
|
function isEditLockedBuffer(buf) {
|
|
561
643
|
return isMdcuiEncoding(buf?.encoding ?? buf?.Settings?.encoding);
|
|
562
644
|
}
|
|
@@ -869,6 +951,8 @@ function parseArgs(argv) {
|
|
|
869
951
|
docs: false,
|
|
870
952
|
changelog: false,
|
|
871
953
|
testapp: false,
|
|
954
|
+
demo: false,
|
|
955
|
+
allowUrl: false,
|
|
872
956
|
buildExe: false,
|
|
873
957
|
buildFor: "",
|
|
874
958
|
configDir: "",
|
|
@@ -901,9 +985,14 @@ function parseArgs(argv) {
|
|
|
901
985
|
else if (arg === "--hex3zst") {
|
|
902
986
|
flags.settings.set("encoding", "hex3zst");
|
|
903
987
|
}
|
|
988
|
+
else if (arg === "--edit") {
|
|
989
|
+
flags.settings.set("encoding", "utf-8");
|
|
990
|
+
}
|
|
904
991
|
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
905
992
|
else if (arg === "--changelog") flags.changelog = true;
|
|
906
993
|
else if (arg === "--testapp.md") flags.testapp = true;
|
|
994
|
+
else if (arg === "--demo") flags.demo = true;
|
|
995
|
+
else if (arg === "--allow-url") flags.allowUrl = true;
|
|
907
996
|
else if (arg === "--build-exe") flags.buildExe = true;
|
|
908
997
|
else if (arg === "--build-for") flags.buildFor = argv[++i] ?? "";
|
|
909
998
|
else if (arg === "-debug") flags.debug = true;
|
|
@@ -946,6 +1035,8 @@ Modes:
|
|
|
946
1035
|
--hex3, --hex3gz, --hex3zst
|
|
947
1036
|
Set -encoding hex3, hex3gz, or hex3zst for this session
|
|
948
1037
|
hex3 shows raw bytes; gz/zst variants compress the same hex3 view
|
|
1038
|
+
--edit
|
|
1039
|
+
Open files as editable UTF-8 text, overriding .md mdcui detection
|
|
949
1040
|
-encoding mdcui
|
|
950
1041
|
Render Markdown through runmd.mjs#createTui; .md files use this automatically
|
|
951
1042
|
Writes .front.js, .back.js, .html, -rpc.js, and -server.js beside the .md file
|
|
@@ -971,11 +1062,19 @@ Information:
|
|
|
971
1062
|
Show ${pkg.name}'s README.md & exit
|
|
972
1063
|
--changelog
|
|
973
1064
|
Show CHANGELOG.md & exit
|
|
974
|
-
--testapp.md
|
|
975
|
-
Write testapp.md to stdout & exit
|
|
976
1065
|
-profile, --profile
|
|
977
1066
|
Print startup performance profile and exit
|
|
978
1067
|
|
|
1068
|
+
Demo:
|
|
1069
|
+
--testapp.md
|
|
1070
|
+
Write the bundled testapp.md to stdout & exit
|
|
1071
|
+
--demo
|
|
1072
|
+
Outputs & overwrites ./testapp.md, opens it in the TUI, and writes 5 generated files
|
|
1073
|
+
|
|
1074
|
+
Remote Markdown:
|
|
1075
|
+
--allow-url
|
|
1076
|
+
Download HTTP(S) Markdown to cwd, write 5 generated files, and allow its code to run
|
|
1077
|
+
|
|
979
1078
|
Experimental:
|
|
980
1079
|
--build-exe Build a Bun single-file executable and exit
|
|
981
1080
|
--build-for <target> Build a Bun single-file executable for target`
|
|
@@ -1129,7 +1228,7 @@ class BufferModel {
|
|
|
1129
1228
|
let text = "";
|
|
1130
1229
|
let readonly = false;
|
|
1131
1230
|
let modTimeMs = null;
|
|
1132
|
-
let encoding = context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
1231
|
+
let encoding = context.inputEncoding ?? context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
1133
1232
|
let ansiStyleLines = null;
|
|
1134
1233
|
let ansiText = null;
|
|
1135
1234
|
let sourceText = null;
|
|
@@ -1140,7 +1239,7 @@ class BufferModel {
|
|
|
1140
1239
|
if (info.isDirectory()) throw new Error(`${path} is a directory`);
|
|
1141
1240
|
readonly = !canWritePath(path);
|
|
1142
1241
|
modTimeMs = info.mtimeMs;
|
|
1143
|
-
const decoded = await readTextFileWithEncoding(path, encoding);
|
|
1242
|
+
const decoded = await readTextFileWithEncoding(path, encoding, !context.encodingExplicit);
|
|
1144
1243
|
text = decoded.text;
|
|
1145
1244
|
encoding = decoded.encoding;
|
|
1146
1245
|
ansiStyleLines = decoded.ansiStyleLines ?? null;
|
|
@@ -1181,7 +1280,8 @@ class BufferModel {
|
|
|
1181
1280
|
}
|
|
1182
1281
|
|
|
1183
1282
|
insert(text) {
|
|
1184
|
-
if (this.isEditLocked()) return false;
|
|
1283
|
+
if (this.isEditLocked() && !canEditMdcuiAtCursor(this)) return false;
|
|
1284
|
+
if (isMdcuiEncoding(this.encoding) && /[\r\n]/.test(String(text))) return false;
|
|
1185
1285
|
for (const ch of text) {
|
|
1186
1286
|
if (ch === "\r" || ch === "\n") this.newline(false);
|
|
1187
1287
|
else if (ch >= " " || ch === "\t") this.insertChar(ch);
|
|
@@ -1190,7 +1290,7 @@ class BufferModel {
|
|
|
1190
1290
|
}
|
|
1191
1291
|
|
|
1192
1292
|
insertChar(ch) {
|
|
1193
|
-
if (this.isEditLocked()) return false;
|
|
1293
|
+
if (this.isEditLocked() && !canEditMdcuiAtCursor(this)) return false;
|
|
1194
1294
|
if (ch === "\t" && DEFAULT_SETTINGS.tabstospaces) ch = " ".repeat(DEFAULT_SETTINGS.tabsize);
|
|
1195
1295
|
const line = this.line();
|
|
1196
1296
|
this.lines[this.cursor.y] = line.slice(0, this.cursor.x) + ch + line.slice(this.cursor.x);
|
|
@@ -1201,6 +1301,7 @@ class BufferModel {
|
|
|
1201
1301
|
}
|
|
1202
1302
|
|
|
1203
1303
|
newline(autoindent = true) {
|
|
1304
|
+
if (isMdcuiEncoding(this.encoding)) return false;
|
|
1204
1305
|
if (this.isEditLocked()) return false;
|
|
1205
1306
|
const line = this.line();
|
|
1206
1307
|
const left = line.slice(0, this.cursor.x);
|
|
@@ -1219,7 +1320,9 @@ class BufferModel {
|
|
|
1219
1320
|
}
|
|
1220
1321
|
|
|
1221
1322
|
backspace() {
|
|
1222
|
-
if (this.isEditLocked()) return false;
|
|
1323
|
+
if (this.isEditLocked() && !canEditMdcuiAtCursor(this)) return false;
|
|
1324
|
+
const mdcuiPrefixLength = mdcuiEditablePrefixLength(this);
|
|
1325
|
+
if (mdcuiPrefixLength > 0 && this.cursor.x <= mdcuiPrefixLength) return false;
|
|
1223
1326
|
if (this.cursor.x > 0) {
|
|
1224
1327
|
const line = this.line();
|
|
1225
1328
|
let start = this.cursor.x - 1;
|
|
@@ -1246,7 +1349,7 @@ class BufferModel {
|
|
|
1246
1349
|
}
|
|
1247
1350
|
|
|
1248
1351
|
deleteForward() {
|
|
1249
|
-
if (this.isEditLocked()) return false;
|
|
1352
|
+
if (this.isEditLocked() && !canEditMdcuiAtCursor(this)) return false;
|
|
1250
1353
|
const line = this.line();
|
|
1251
1354
|
if (this.cursor.x < line.length) {
|
|
1252
1355
|
const cp = line.codePointAt(this.cursor.x);
|
|
@@ -1255,6 +1358,8 @@ class BufferModel {
|
|
|
1255
1358
|
this.invalidateHighlightFrom(this.cursor.y);
|
|
1256
1359
|
this.modified = true;
|
|
1257
1360
|
return true;
|
|
1361
|
+
} else if (isMdcuiEncoding(this.encoding)) {
|
|
1362
|
+
return false;
|
|
1258
1363
|
} else if (this.cursor.y < this.lines.length - 1) {
|
|
1259
1364
|
this.lines[this.cursor.y] += this.lines[this.cursor.y + 1];
|
|
1260
1365
|
this.lines.splice(this.cursor.y + 1, 1);
|
|
@@ -1384,8 +1489,8 @@ class BufferModel {
|
|
|
1384
1489
|
this.cursor.x = x;
|
|
1385
1490
|
}
|
|
1386
1491
|
|
|
1387
|
-
pushUndo() {
|
|
1388
|
-
if (this.isEditLocked()) return;
|
|
1492
|
+
pushUndo(force = false) {
|
|
1493
|
+
if (!force && this.isEditLocked() && !canEditMdcuiAtCursor(this)) return;
|
|
1389
1494
|
this.undoStack.push({ lines: this.lines.slice(), cursor: { ...this.cursor }, serial: this._undoSerial });
|
|
1390
1495
|
this._undoSerial = (this._undoSerial ?? 0) + 1;
|
|
1391
1496
|
if (this.undoStack.length > 500) this.undoStack.shift();
|
|
@@ -1393,7 +1498,7 @@ class BufferModel {
|
|
|
1393
1498
|
}
|
|
1394
1499
|
|
|
1395
1500
|
undo() {
|
|
1396
|
-
if (this.isEditLocked()) return false;
|
|
1501
|
+
if (this.isEditLocked() && !isMdcuiEncoding(this.encoding)) return false;
|
|
1397
1502
|
if (!this.undoStack.length) return false;
|
|
1398
1503
|
this.redoStack.push({ lines: this.lines.slice(), cursor: { ...this.cursor }, serial: this._undoSerial });
|
|
1399
1504
|
const s = this.undoStack.pop();
|
|
@@ -1406,7 +1511,7 @@ class BufferModel {
|
|
|
1406
1511
|
}
|
|
1407
1512
|
|
|
1408
1513
|
redo() {
|
|
1409
|
-
if (this.isEditLocked()) return false;
|
|
1514
|
+
if (this.isEditLocked() && !isMdcuiEncoding(this.encoding)) return false;
|
|
1410
1515
|
if (!this.redoStack.length) return false;
|
|
1411
1516
|
this.undoStack.push({ lines: this.lines.slice(), cursor: { ...this.cursor }, serial: this._undoSerial });
|
|
1412
1517
|
const s = this.redoStack.pop();
|
|
@@ -1536,7 +1641,7 @@ class BufferModel {
|
|
|
1536
1641
|
async reopen(context = {}) {
|
|
1537
1642
|
if (!this.path) return;
|
|
1538
1643
|
if (isHttpUrl(this.path)) {
|
|
1539
|
-
const decoded = await fetchTextWithEncoding(this.path, this.Settings.encoding ?? this.encoding);
|
|
1644
|
+
const decoded = await fetchTextWithEncoding(this.path, this.Settings.encoding ?? this.encoding, false);
|
|
1540
1645
|
const text = decoded.text;
|
|
1541
1646
|
this.encoding = decoded.encoding;
|
|
1542
1647
|
this.Settings.encoding = decoded.encoding;
|
|
@@ -1568,7 +1673,7 @@ class BufferModel {
|
|
|
1568
1673
|
}
|
|
1569
1674
|
const info = statSync(this.path);
|
|
1570
1675
|
if (info.isDirectory()) throw new Error(`${this.path} is a directory`);
|
|
1571
|
-
const decoded = await readTextFileWithEncoding(this.path, this.Settings.encoding ?? this.encoding);
|
|
1676
|
+
const decoded = await readTextFileWithEncoding(this.path, this.Settings.encoding ?? this.encoding, false);
|
|
1572
1677
|
const text = decoded.text;
|
|
1573
1678
|
this.encoding = decoded.encoding;
|
|
1574
1679
|
this.Settings.encoding = decoded.encoding;
|
|
@@ -2285,6 +2390,7 @@ class App {
|
|
|
2285
2390
|
this._messageRowY = null;
|
|
2286
2391
|
this._messageRowClickZone = null;
|
|
2287
2392
|
this._resizeRenderSeq = 0;
|
|
2393
|
+
this._mdcuiExitNotified = new WeakSet();
|
|
2288
2394
|
}
|
|
2289
2395
|
|
|
2290
2396
|
get tab() { return this.tabs[this.activeTabIdx]; }
|
|
@@ -2428,6 +2534,8 @@ class App {
|
|
|
2428
2534
|
}
|
|
2429
2535
|
|
|
2430
2536
|
async stop(code = 0) {
|
|
2537
|
+
for (const buf of this.buffers)
|
|
2538
|
+
await this.notifyMdcuiExit(buf, "exit");
|
|
2431
2539
|
this.running = false;
|
|
2432
2540
|
if (this._backupTimer) { clearInterval(this._backupTimer); this._backupTimer = null; }
|
|
2433
2541
|
await Promise.allSettled(this.buffers.map((buf) => buf._backupWritePromise).filter(Boolean));
|
|
@@ -3471,7 +3579,15 @@ class App {
|
|
|
3471
3579
|
}
|
|
3472
3580
|
|
|
3473
3581
|
if (event.type === "paste") {
|
|
3474
|
-
if (
|
|
3582
|
+
if (
|
|
3583
|
+
(isEditLockedBuffer(buf) && !isMdcuiEncoding(buf?.encoding))
|
|
3584
|
+
|| (isMdcuiEncoding(buf?.encoding) && (
|
|
3585
|
+
!canEditMdcuiAtCursor(buf)
|
|
3586
|
+
||
|
|
3587
|
+
/[\r\n]/.test(event.text)
|
|
3588
|
+
|| !canEditMdcuiSelection(buf, this.pane?.selection)
|
|
3589
|
+
))
|
|
3590
|
+
) {
|
|
3475
3591
|
this.message = "Buffer is read-only";
|
|
3476
3592
|
this.render();
|
|
3477
3593
|
return;
|
|
@@ -3849,13 +3965,17 @@ class App {
|
|
|
3849
3965
|
break;
|
|
3850
3966
|
default:
|
|
3851
3967
|
if (text >= " " || text.includes("\n")) {
|
|
3852
|
-
if (isMdcuiEncoding(buf?.encoding) && text === " ") {
|
|
3968
|
+
if (isMdcuiEncoding(buf?.encoding) && text === " " && !canEditMdcuiAtCursor(buf)) {
|
|
3853
3969
|
await this.handleMdcuiCellCallback(buf, buf.cursor.y, buf.cursor.x, "space");
|
|
3854
3970
|
break;
|
|
3855
3971
|
}
|
|
3856
3972
|
if (!this._undoInsertChain || this.pane?.selection) {
|
|
3857
3973
|
buf.pushUndo();
|
|
3858
3974
|
}
|
|
3975
|
+
if (isMdcuiEncoding(buf?.encoding) && !canEditMdcuiSelection(buf, this.pane?.selection)) {
|
|
3976
|
+
this.message = "Protected mdcui prefix";
|
|
3977
|
+
break;
|
|
3978
|
+
}
|
|
3859
3979
|
this._undoInsertChain = true;
|
|
3860
3980
|
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
3861
3981
|
await this.insertTextWithHooks(text);
|
|
@@ -3868,6 +3988,12 @@ class App {
|
|
|
3868
3988
|
async handleMdcuiCellCallback(buf, y = buf?.cursor?.y ?? 0, x = buf?.cursor?.x ?? 0, trigger = "unknown") {
|
|
3869
3989
|
const payload = mdcuiCellPayload(buf, y, x, trigger);
|
|
3870
3990
|
if (!payload) return false;
|
|
3991
|
+
const resizeResult = resizeMdcuiTextBlock(buf, y, x);
|
|
3992
|
+
if (resizeResult) {
|
|
3993
|
+
if (resizeResult === "added") this.message = "Added text row";
|
|
3994
|
+
else if (resizeResult === "removed") this.message = "Removed empty text row";
|
|
3995
|
+
return true;
|
|
3996
|
+
}
|
|
3871
3997
|
const callback = this.context?.mdcuiCallback ?? globalThis.mdcuiCallback;
|
|
3872
3998
|
if (typeof callback === "function") {
|
|
3873
3999
|
try {
|
|
@@ -4624,7 +4750,7 @@ class App {
|
|
|
4624
4750
|
async openInPane(path) {
|
|
4625
4751
|
try {
|
|
4626
4752
|
const previous = this.pane.buffer;
|
|
4627
|
-
const buffer = await loadBufferForPath(path, this.context);
|
|
4753
|
+
const buffer = await loadBufferForPath(path, this.context, {}, { interactive: true });
|
|
4628
4754
|
this.pane.buffer = buffer;
|
|
4629
4755
|
this.pane.selection = null;
|
|
4630
4756
|
if (previous !== buffer) this._closeBufferIfUnused(previous);
|
|
@@ -4637,7 +4763,7 @@ class App {
|
|
|
4637
4763
|
|
|
4638
4764
|
async openFile(path) {
|
|
4639
4765
|
try {
|
|
4640
|
-
const buffer = await loadBufferForPath(path, this.context);
|
|
4766
|
+
const buffer = await loadBufferForPath(path, this.context, {}, { interactive: true });
|
|
4641
4767
|
if (isEmptyUntitledBuffer(this.buffer)) {
|
|
4642
4768
|
const previous = this.pane.buffer;
|
|
4643
4769
|
this.pane.buffer = buffer;
|
|
@@ -4655,6 +4781,34 @@ class App {
|
|
|
4655
4781
|
}
|
|
4656
4782
|
}
|
|
4657
4783
|
|
|
4784
|
+
reconcileReopenedBuffer(buffer) {
|
|
4785
|
+
if (!buffer || isHttpUrl(buffer.path)) return buffer;
|
|
4786
|
+
const map = this.context?._openBuffers;
|
|
4787
|
+
if (!map) return buffer;
|
|
4788
|
+
const absPath = resolve(buffer.AbsPath || buffer.path);
|
|
4789
|
+
|
|
4790
|
+
if (isMdcuiEncoding(buffer.encoding)) {
|
|
4791
|
+
if (map.get(absPath) === buffer) map.delete(absPath);
|
|
4792
|
+
buffer._openBufferMap = null;
|
|
4793
|
+
return buffer;
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
const existing = map.get(absPath);
|
|
4797
|
+
if (existing && existing !== buffer && !isMdcuiEncoding(existing.encoding)) {
|
|
4798
|
+
for (const tab of this.tabs) {
|
|
4799
|
+
for (const pane of tab.panes()) {
|
|
4800
|
+
if (pane.buffer === buffer) pane.buffer = existing;
|
|
4801
|
+
if (pane.prevBuffer === buffer) pane.prevBuffer = existing;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
return existing;
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
buffer._openBufferMap = map;
|
|
4808
|
+
map.set(absPath, buffer);
|
|
4809
|
+
return buffer;
|
|
4810
|
+
}
|
|
4811
|
+
|
|
4658
4812
|
async save({ force = false } = {}) {
|
|
4659
4813
|
if (isEditLockedBuffer(this.buffer)) { this.message = "Can't save under readonly mode"; return; }
|
|
4660
4814
|
if (!force && this.buffer?.readonly) { this.message = "Can't save under readonly mode"; return; }
|
|
@@ -4706,6 +4860,11 @@ class App {
|
|
|
4706
4860
|
|
|
4707
4861
|
async quit() {
|
|
4708
4862
|
const buffer = this.buffer;
|
|
4863
|
+
if (isMdcuiEncoding(buffer?.encoding)) {
|
|
4864
|
+
await this.notifyMdcuiExit(buffer, "quit");
|
|
4865
|
+
await this._doCloseCurrentPane();
|
|
4866
|
+
return;
|
|
4867
|
+
}
|
|
4709
4868
|
if (buffer.modified) {
|
|
4710
4869
|
if (!this.prompt) {
|
|
4711
4870
|
this.openYNPrompt(
|
|
@@ -4726,6 +4885,7 @@ class App {
|
|
|
4726
4885
|
// Close only the current pane; if it's the last pane in the tab, close the tab.
|
|
4727
4886
|
async _doCloseCurrentPane() {
|
|
4728
4887
|
if (this.tab.panes().length > 1) {
|
|
4888
|
+
await this.notifyMdcuiExit(this.buffer, "close-pane");
|
|
4729
4889
|
this.closePane(this.pane);
|
|
4730
4890
|
this.render();
|
|
4731
4891
|
} else {
|
|
@@ -4743,6 +4903,32 @@ class App {
|
|
|
4743
4903
|
if (!this.prompt && !this.buffer.modified) await this.closeCurrentTab({ force: true });
|
|
4744
4904
|
}
|
|
4745
4905
|
|
|
4906
|
+
async notifyMdcuiExit(buffer, reason = "exit") {
|
|
4907
|
+
if (
|
|
4908
|
+
!buffer
|
|
4909
|
+
|| !isMdcuiEncoding(buffer.encoding)
|
|
4910
|
+
|| this._mdcuiExitNotified.has(buffer)
|
|
4911
|
+
) return;
|
|
4912
|
+
this._mdcuiExitNotified.add(buffer);
|
|
4913
|
+
|
|
4914
|
+
const frontPath = buffer.path && !isHttpUrl(buffer.path)
|
|
4915
|
+
? `${buffer.path}.front.js`
|
|
4916
|
+
: "";
|
|
4917
|
+
if (!frontPath || !existsSync(frontPath)) return;
|
|
4918
|
+
|
|
4919
|
+
try {
|
|
4920
|
+
const frontMod = await import(localModuleUrl(frontPath));
|
|
4921
|
+
if (typeof frontMod.onMdcuiExit !== "function") return;
|
|
4922
|
+
await frontMod.onMdcuiExit({
|
|
4923
|
+
reason,
|
|
4924
|
+
path: buffer.path,
|
|
4925
|
+
$: globalThis.$,
|
|
4926
|
+
});
|
|
4927
|
+
} catch (error) {
|
|
4928
|
+
console.error(`[mdcui] onMdcuiExit: ${error?.message || error}`);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4746
4932
|
async toggleHelp() {
|
|
4747
4933
|
const cur = this.pane;
|
|
4748
4934
|
if (cur?.isHelp) {
|
|
@@ -4841,11 +5027,14 @@ class App {
|
|
|
4841
5027
|
async closeCurrentTab({ force = false } = {}) {
|
|
4842
5028
|
if (!force && this.buffer?.modified) return await this.quit();
|
|
4843
5029
|
if (this.tabs.length <= 1) {
|
|
5030
|
+
await this.notifyMdcuiExit(this.buffer, "close-tab");
|
|
4844
5031
|
await this.stop(0);
|
|
4845
5032
|
return;
|
|
4846
5033
|
}
|
|
4847
5034
|
const closingBuffers = [...new Set(this.tab.panes().flatMap((pane) => [pane.buffer, pane.prevBuffer]).filter(Boolean))];
|
|
4848
5035
|
const closing = this.buffer;
|
|
5036
|
+
for (const buffer of closingBuffers)
|
|
5037
|
+
await this.notifyMdcuiExit(buffer, "close-tab");
|
|
4849
5038
|
this.tabs.splice(this.activeTabIdx, 1);
|
|
4850
5039
|
this.activeTabIdx = Math.min(this.activeTabIdx, this.tabs.length - 1);
|
|
4851
5040
|
this.message = "";
|
|
@@ -5110,9 +5299,10 @@ class App {
|
|
|
5110
5299
|
}
|
|
5111
5300
|
case "reopen": {
|
|
5112
5301
|
if (!buf?.path) { this.message = "No file to reopen"; break; }
|
|
5302
|
+
let requestedEncoding = null;
|
|
5113
5303
|
if (cmdArgs[0]) {
|
|
5114
5304
|
try {
|
|
5115
|
-
|
|
5305
|
+
requestedEncoding = normalizeEncodingLabel(cmdArgs[0]);
|
|
5116
5306
|
} catch (error) {
|
|
5117
5307
|
this.message = String(error.message || error);
|
|
5118
5308
|
break;
|
|
@@ -5120,9 +5310,24 @@ class App {
|
|
|
5120
5310
|
}
|
|
5121
5311
|
const doReopen = async () => {
|
|
5122
5312
|
try {
|
|
5313
|
+
if (isMdcuiEncoding(requestedEncoding)) {
|
|
5314
|
+
const detachedContext = { ...this.context, inputEncoding: "mdcui", encodingExplicit: true };
|
|
5315
|
+
const reopened = await loadBufferForPath(buf.path, detachedContext);
|
|
5316
|
+
const previous = this.pane.buffer;
|
|
5317
|
+
this.pane.buffer = reopened;
|
|
5318
|
+
this.pane.selection = null;
|
|
5319
|
+
if (previous !== reopened) this._closeBufferIfUnused(previous);
|
|
5320
|
+
await this.context.plugins?.run("onBufferOpen", reopened);
|
|
5321
|
+
await this.context.jsPlugins?.run("onBufferOpen", reopened);
|
|
5322
|
+
this.message = `Reopened ${reopened.name} as ${reopened.encoding}`;
|
|
5323
|
+
this.render();
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
if (requestedEncoding) buf.SetOption("encoding", requestedEncoding);
|
|
5123
5327
|
await buf.reopen(this.context);
|
|
5124
|
-
|
|
5125
|
-
this.
|
|
5328
|
+
const reopened = this.reconcileReopenedBuffer(buf);
|
|
5329
|
+
if (this.pane?.buffer === reopened) this.pane.selection = null;
|
|
5330
|
+
this.message = `Reopened ${reopened.name} as ${reopened.encoding}`;
|
|
5126
5331
|
} catch (error) {
|
|
5127
5332
|
this.message = String(error.message || error);
|
|
5128
5333
|
}
|
|
@@ -5228,7 +5433,7 @@ class App {
|
|
|
5228
5433
|
case "vsplit": {
|
|
5229
5434
|
let newBuf;
|
|
5230
5435
|
if (cmdArgs.length > 0) {
|
|
5231
|
-
try { newBuf = await loadBufferForPath(resolve(expandHome(cmdArgs[0])), this.context); }
|
|
5436
|
+
try { newBuf = await loadBufferForPath(resolve(expandHome(cmdArgs[0])), this.context, {}, { interactive: true }); }
|
|
5232
5437
|
catch (err) { this.message = err.message; break; }
|
|
5233
5438
|
} else {
|
|
5234
5439
|
newBuf = new BufferModel({ command: {} });
|
|
@@ -5241,7 +5446,7 @@ class App {
|
|
|
5241
5446
|
case "hsplit": {
|
|
5242
5447
|
let newBuf;
|
|
5243
5448
|
if (cmdArgs.length > 0) {
|
|
5244
|
-
try { newBuf = await loadBufferForPath(resolve(expandHome(cmdArgs[0])), this.context); }
|
|
5449
|
+
try { newBuf = await loadBufferForPath(resolve(expandHome(cmdArgs[0])), this.context, {}, { interactive: true }); }
|
|
5245
5450
|
catch (err) { this.message = err.message; break; }
|
|
5246
5451
|
} else {
|
|
5247
5452
|
newBuf = new BufferModel({ command: {} });
|
|
@@ -5613,6 +5818,14 @@ class App {
|
|
|
5613
5818
|
this._freshClip = false;
|
|
5614
5819
|
const pasted = this.clipboard.read();
|
|
5615
5820
|
if (pasted) {
|
|
5821
|
+
if (isMdcuiEncoding(buf?.encoding) && (
|
|
5822
|
+
/[\r\n]/.test(pasted)
|
|
5823
|
+
|| !canEditMdcuiAtCursor(buf)
|
|
5824
|
+
|| !canEditMdcuiSelection(buf, this.pane?.selection)
|
|
5825
|
+
)) {
|
|
5826
|
+
this.message = "Protected mdcui content";
|
|
5827
|
+
break;
|
|
5828
|
+
}
|
|
5616
5829
|
buf.pushUndo();
|
|
5617
5830
|
if (this.pane?.selection) deleteSelection(buf, this.pane);
|
|
5618
5831
|
buf.insert(pasted);
|
|
@@ -6528,11 +6741,25 @@ function commandHasStartupJump(command = {}) {
|
|
|
6528
6741
|
return commandHasStartCursor(command) || Boolean(command.searchRegex);
|
|
6529
6742
|
}
|
|
6530
6743
|
|
|
6531
|
-
async function loadBufferForPath(pathOrUrl, context, command = {}) {
|
|
6744
|
+
async function loadBufferForPath(pathOrUrl, context, command = {}, { interactive = false } = {}) {
|
|
6745
|
+
const loadContext = interactive
|
|
6746
|
+
? { ...context, inputEncoding: defaultAllSettings().encoding, encodingExplicit: false }
|
|
6747
|
+
: context;
|
|
6748
|
+
if (isHttpUrl(pathOrUrl) && loadContext.allowUrl) {
|
|
6749
|
+
const url = new URL(pathOrUrl);
|
|
6750
|
+
let name;
|
|
6751
|
+
try { name = decodeURIComponent(basename(url.pathname)); }
|
|
6752
|
+
catch { name = basename(url.pathname); }
|
|
6753
|
+
if (!name) name = "remote.md";
|
|
6754
|
+
if (!name.toLowerCase().endsWith(".md")) name += ".md";
|
|
6755
|
+
const localPath = resolve(name);
|
|
6756
|
+
await Bun.write(localPath, await fetchHttpBytes(pathOrUrl));
|
|
6757
|
+
return await loadBufferForPath(localPath, loadContext, command, { interactive });
|
|
6758
|
+
}
|
|
6532
6759
|
let buffer;
|
|
6533
6760
|
if (isHttpUrl(pathOrUrl)) {
|
|
6534
|
-
let encoding =
|
|
6535
|
-
const decoded = await fetchTextWithEncoding(pathOrUrl, encoding);
|
|
6761
|
+
let encoding = loadContext.inputEncoding ?? loadContext.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
6762
|
+
const decoded = await fetchTextWithEncoding(pathOrUrl, encoding, !loadContext.encodingExplicit);
|
|
6536
6763
|
const text = decoded.text;
|
|
6537
6764
|
encoding = decoded.encoding;
|
|
6538
6765
|
const urlPath = pathOrUrl.replace(/[?#].*$/, "");
|
|
@@ -6548,13 +6775,18 @@ async function loadBufferForPath(pathOrUrl, context, command = {}) {
|
|
|
6548
6775
|
mdcuiRenderWidth: decoded.mdcuiRenderWidth ?? 0,
|
|
6549
6776
|
});
|
|
6550
6777
|
buffer._configDir = context?.config?.configDir ?? null;
|
|
6551
|
-
attachSyntax(buffer,
|
|
6778
|
+
attachSyntax(buffer, loadContext, urlPath, text);
|
|
6552
6779
|
} else {
|
|
6553
6780
|
if (!context._openBuffers) context._openBuffers = new Map();
|
|
6554
6781
|
const absPath = resolve(pathOrUrl);
|
|
6555
6782
|
const existing = context._openBuffers.get(absPath);
|
|
6556
|
-
|
|
6557
|
-
|
|
6783
|
+
const requestedEncoding = encodingForPath(
|
|
6784
|
+
absPath,
|
|
6785
|
+
loadContext.inputEncoding ?? loadContext.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding,
|
|
6786
|
+
!loadContext.encodingExplicit,
|
|
6787
|
+
);
|
|
6788
|
+
if (existing && !isMdcuiEncoding(existing.encoding) && !isMdcuiEncoding(requestedEncoding)) return existing;
|
|
6789
|
+
buffer = await BufferModel.fromFile(absPath, command, loadContext);
|
|
6558
6790
|
// Check for crash-recovery backup before returning the buffer.
|
|
6559
6791
|
const promptFn = context._termPrompt;
|
|
6560
6792
|
if (promptFn && buffer._configDir) {
|
|
@@ -6565,8 +6797,12 @@ async function loadBufferForPath(pathOrUrl, context, command = {}) {
|
|
|
6565
6797
|
attachSyntax(buffer, context, absPath, buffer.lines.join("\n"));
|
|
6566
6798
|
}
|
|
6567
6799
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6800
|
+
// mdcui is a derived, read-only view. Keep every view independent and out
|
|
6801
|
+
// of the same-path cache used by normal editable buffers.
|
|
6802
|
+
if (!isMdcuiEncoding(buffer.encoding)) {
|
|
6803
|
+
buffer._openBufferMap = context._openBuffers;
|
|
6804
|
+
context._openBuffers.set(absPath, buffer);
|
|
6805
|
+
}
|
|
6570
6806
|
}
|
|
6571
6807
|
if (DEFAULT_SETTINGS.savecursor && !commandHasStartupJump(command) && context?.cursorStates?.[pathOrUrl]) {
|
|
6572
6808
|
const saved = context.cursorStates[pathOrUrl];
|
|
@@ -7272,8 +7508,11 @@ async function printChangelogDocs() {
|
|
|
7272
7508
|
}
|
|
7273
7509
|
|
|
7274
7510
|
async function printTestappSource() {
|
|
7275
|
-
|
|
7276
|
-
|
|
7511
|
+
process.stdout.write(await bundledTestappSource());
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
async function bundledTestappSource() {
|
|
7515
|
+
return readInternalAssetText("testapp.md") ?? await Bun.file(join(REPO_ROOT, "testapp.md")).text();
|
|
7277
7516
|
}
|
|
7278
7517
|
|
|
7279
7518
|
async function main() {
|
|
@@ -7331,6 +7570,11 @@ async function main() {
|
|
|
7331
7570
|
await printTestappSource();
|
|
7332
7571
|
return;
|
|
7333
7572
|
}
|
|
7573
|
+
if (flags.demo) {
|
|
7574
|
+
const demoPath = resolve("testapp.md");
|
|
7575
|
+
await Bun.write(demoPath, await bundledTestappSource());
|
|
7576
|
+
rawFiles.splice(0, rawFiles.length, demoPath);
|
|
7577
|
+
}
|
|
7334
7578
|
if (flags.options) {
|
|
7335
7579
|
for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
7336
7580
|
console.log(`-${key} value`);
|
|
@@ -7341,6 +7585,7 @@ async function main() {
|
|
|
7341
7585
|
addCheckpoint("Config Initialization");
|
|
7342
7586
|
const config = await new Config({ configDir: flags.configDir }).init();
|
|
7343
7587
|
config.applyCliSettings(flags.settings);
|
|
7588
|
+
const encodingExplicit = flags.settings.has("encoding") || Object.hasOwn(config.parsedSettings, "encoding");
|
|
7344
7589
|
syncEditorSettings(config);
|
|
7345
7590
|
|
|
7346
7591
|
addCheckpoint("Runtime Registry Init");
|
|
@@ -7352,7 +7597,7 @@ async function main() {
|
|
|
7352
7597
|
const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
|
|
7353
7598
|
|
|
7354
7599
|
if (flags.cat) {
|
|
7355
|
-
await catFiles(rawFiles, colorscheme, syntaxDefinitions, config.getGlobalOption("encoding"));
|
|
7600
|
+
await catFiles(rawFiles, colorscheme, syntaxDefinitions, config.getGlobalOption("encoding"), !encodingExplicit);
|
|
7356
7601
|
return;
|
|
7357
7602
|
}
|
|
7358
7603
|
|
|
@@ -7410,7 +7655,7 @@ async function main() {
|
|
|
7410
7655
|
|
|
7411
7656
|
const { files, command } = parseInput(rawFiles);
|
|
7412
7657
|
const jsPlugins = new JsPluginManager();
|
|
7413
|
-
const context = { colorscheme, syntaxDefinitions, config, runtime, jsPlugins };
|
|
7658
|
+
const context = { colorscheme, syntaxDefinitions, config, runtime, jsPlugins, encodingExplicit, allowUrl: flags.allowUrl };
|
|
7414
7659
|
jsPlugins.setContext(context);
|
|
7415
7660
|
buildMicroGlobal(jsPlugins); // sets globalThis.micro
|
|
7416
7661
|
|
|
@@ -7809,11 +8054,16 @@ function outdentLine(buf, _ctx) {
|
|
|
7809
8054
|
}
|
|
7810
8055
|
|
|
7811
8056
|
function deleteSelection(buf, pane) {
|
|
7812
|
-
if (isEditLockedBuffer(buf)) return "";
|
|
7813
8057
|
const selection = pane?.selection;
|
|
7814
8058
|
if (!selection || !buf) return "";
|
|
7815
8059
|
const text = getSelectionText(buf, selection);
|
|
7816
8060
|
const { first, last } = selectionBounds(selection);
|
|
8061
|
+
if (isMdcuiEncoding(buf?.encoding)) {
|
|
8062
|
+
const prefixLength = mdcuiEditablePrefixLength(buf, first.y);
|
|
8063
|
+
if (first.y !== last.y || prefixLength === 0 || first.x < prefixLength) return "";
|
|
8064
|
+
} else if (isEditLockedBuffer(buf)) {
|
|
8065
|
+
return "";
|
|
8066
|
+
}
|
|
7817
8067
|
if (first.y === last.y) {
|
|
7818
8068
|
const line = buf.lines[first.y] ?? "";
|
|
7819
8069
|
buf.lines[first.y] = line.slice(0, first.x) + line.slice(last.x);
|
|
@@ -7922,30 +8172,34 @@ function syncEditorSettings(config) {
|
|
|
7922
8172
|
}
|
|
7923
8173
|
}
|
|
7924
8174
|
|
|
7925
|
-
async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding) {
|
|
8175
|
+
async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAULT_SETTINGS.encoding, inferMdcui = true) {
|
|
7926
8176
|
const targets = files.length > 0 ? files.map((f) => ({ path: f, stdin: false })) : [{ path: null, stdin: true }];
|
|
7927
8177
|
for (const { path: filePath, stdin } of targets) {
|
|
7928
8178
|
let content;
|
|
7929
8179
|
let ansiContent = null;
|
|
7930
8180
|
let effectivePath = filePath;
|
|
8181
|
+
let effectiveEncoding = normalizeEncodingLabel(encoding);
|
|
7931
8182
|
if (stdin) {
|
|
7932
8183
|
const chunks = [];
|
|
7933
8184
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
7934
8185
|
const decoded = await decodeAndRenderTextBytes(Buffer.concat(chunks), encoding);
|
|
7935
8186
|
content = decoded.text;
|
|
7936
8187
|
ansiContent = decoded.ansiText ?? null;
|
|
8188
|
+
effectiveEncoding = decoded.encoding;
|
|
7937
8189
|
} else if (isHttpUrl(filePath)) {
|
|
7938
|
-
const decoded = await fetchTextWithEncoding(filePath, encoding);
|
|
8190
|
+
const decoded = await fetchTextWithEncoding(filePath, encoding, inferMdcui);
|
|
7939
8191
|
content = decoded.text;
|
|
7940
8192
|
ansiContent = decoded.ansiText ?? null;
|
|
8193
|
+
effectiveEncoding = decoded.encoding;
|
|
7941
8194
|
// Use the URL pathname for syntax/md detection (strip query/hash)
|
|
7942
8195
|
try { effectivePath = new URL(filePath).pathname; } catch { effectivePath = filePath; }
|
|
7943
8196
|
} else {
|
|
7944
|
-
const decoded = await readTextFileWithEncoding(filePath, encoding);
|
|
8197
|
+
const decoded = await readTextFileWithEncoding(filePath, encoding, inferMdcui);
|
|
7945
8198
|
content = decoded.text;
|
|
7946
8199
|
ansiContent = decoded.ansiText ?? null;
|
|
8200
|
+
effectiveEncoding = decoded.encoding;
|
|
7947
8201
|
}
|
|
7948
|
-
if (isMdcuiEncoding(
|
|
8202
|
+
if (isMdcuiEncoding(effectiveEncoding)) {
|
|
7949
8203
|
process.stdout.write(ansiContent ?? content);
|
|
7950
8204
|
if (!(ansiContent ?? content).endsWith("\n")) process.stdout.write("\n");
|
|
7951
8205
|
continue;
|