mellos-mapping 0.20.2 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -88
- package/README.zh-CN.md +122 -84
- package/dist/hook-session-start.mjs +25 -19
- package/dist/mmap.mjs +302 -170
- package/dist/preview.mjs +1418 -0
- package/dist/server.mjs +1198 -400
- package/dist/store-paths.mjs +40 -21
- package/dist/terminal-worker.mjs +3188 -0
- package/dist/watch.mjs +523 -280
- package/dist/web/TERMINAL-LICENSES.txt +70 -0
- package/dist/web/app.css +967 -0
- package/dist/web/app.js +1356 -0
- package/dist/web/index.html +9 -0
- package/dist/web/terminal.css +9 -0
- package/dist/web/terminal.html +7 -0
- package/dist/web/terminal.js +9293 -0
- package/dist/web/xterm.css +285 -0
- package/dist/web.mjs +5535 -0
- package/docs/codex.md +183 -0
- package/lib/domain/text.d.ts +9 -0
- package/lib/domain/text.js +43 -0
- package/lib/domain/types.js +10 -1
- package/lib/preview/index.d.ts +3 -0
- package/lib/preview/index.js +3 -0
- package/lib/preview/markdown.d.ts +8 -0
- package/lib/preview/markdown.js +54 -0
- package/lib/preview/presentation.d.ts +6 -0
- package/lib/preview/presentation.js +14 -0
- package/lib/preview/publisher.d.ts +23 -0
- package/lib/preview/publisher.js +143 -0
- package/lib/preview/svg.d.ts +3 -0
- package/lib/preview/svg.js +74 -0
- package/lib/preview/text.d.ts +4 -0
- package/lib/preview/text.js +13 -0
- package/lib/render/canvas.d.ts +1 -1
- package/lib/render/canvas.js +4 -2
- package/lib/render/draw.js +9 -6
- package/lib/render/render.d.ts +6 -0
- package/lib/render/render.js +50 -18
- package/lib/render/width.js +3 -1
- package/lib/store/atomic.d.ts +18 -0
- package/lib/store/atomic.js +86 -0
- package/lib/store/channels.d.ts +40 -0
- package/lib/store/channels.js +135 -0
- package/lib/store/format.js +3 -1
- package/lib/store/json-text.d.ts +9 -0
- package/lib/store/json-text.js +16 -0
- package/lib/store/maps.d.ts +12 -0
- package/lib/store/maps.js +42 -0
- package/lib/store/migration.d.ts +12 -0
- package/lib/store/migration.js +46 -0
- package/lib/store/pages.d.ts +46 -0
- package/lib/store/pages.js +89 -0
- package/lib/store/policy.d.ts +74 -0
- package/lib/store/policy.js +144 -0
- package/lib/store/store.d.ts +9 -256
- package/lib/store/store.js +10 -694
- package/lib/store/viewers.d.ts +81 -0
- package/lib/store/viewers.js +186 -0
- package/package.json +25 -6
- package/scripts/codex-cli.mjs +42 -0
- package/scripts/codex-register.mjs +27 -94
- package/scripts/mmap.mjs +26 -16
- package/scripts/open-pane.mjs +37 -18
- package/scripts/pane-core.mjs +57 -220
- package/scripts/terminal-session.mjs +137 -0
- package/scripts/tmux-session.mjs +90 -0
- package/scripts/watcher-command.mjs +16 -0
package/dist/watch.mjs
CHANGED
|
@@ -3,8 +3,7 @@ import { createRequire } from 'node:module'; const require = createRequire(impor
|
|
|
3
3
|
|
|
4
4
|
// src/watch/watch.ts
|
|
5
5
|
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
6
|
-
import { dirname as
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { dirname as dirname7, join as join6 } from "node:path";
|
|
8
7
|
|
|
9
8
|
// src/domain/types.ts
|
|
10
9
|
var ok = (value) => ({ ok: true, value });
|
|
@@ -87,7 +86,7 @@ function describeMapError(e) {
|
|
|
87
86
|
case "layer-holds-group":
|
|
88
87
|
return `layer "${e.id}" still holds group "${e.occupant}"; remove its groups (removeGroup) first`;
|
|
89
88
|
case "edge-not-downward":
|
|
90
|
-
return `edge ${e.from} (rank ${e.fromRank}) -> ${e.to} (rank ${e.toRank}) is not strictly downward;
|
|
89
|
+
return `edge ${e.from} (rank ${e.fromRank}) -> ${e.to} (rank ${e.toRank}) is not strictly downward; ` + (e.fromRank === e.toRank ? `same-band siblings may not depend on each other \u2014 either "${e.to}" is really a lower concept (declare it on a lower band) or "${e.from}" and "${e.to}" are one node (merge them)` : `"${e.from}" would USE "${e.to}" from a lower band \u2014 reverse the edge if "${e.to}" is the user, otherwise move or re-rank so "${e.from}" sits above "${e.to}"`);
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -479,6 +478,41 @@ function flipForSequence(map) {
|
|
|
479
478
|
};
|
|
480
479
|
}
|
|
481
480
|
|
|
481
|
+
// src/domain/text.ts
|
|
482
|
+
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
483
|
+
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
484
|
+
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
485
|
+
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
486
|
+
function mapTextError(map) {
|
|
487
|
+
const check = (field, value, multiline = false) => value === void 0 || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value) ? void 0 : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
488
|
+
let error = check("title", map.title);
|
|
489
|
+
if (error) return error;
|
|
490
|
+
for (const [i, layer] of map.layers.entries()) {
|
|
491
|
+
error = check(`layers[${i}].name`, layer.name);
|
|
492
|
+
if (error) return error;
|
|
493
|
+
}
|
|
494
|
+
for (const name of ["lanes", "groups"]) {
|
|
495
|
+
for (const [i, item] of map[name].entries()) {
|
|
496
|
+
error = check(`${name}[${i}].label`, item.label);
|
|
497
|
+
if (error) return error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const [i, node] of map.nodes.entries()) {
|
|
501
|
+
for (const name of ["label", "evidence", "detail"]) {
|
|
502
|
+
error = check(`nodes[${i}].${name}`, node[name], name !== "label");
|
|
503
|
+
if (error) return error;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const [i, edge] of map.edges.entries()) {
|
|
507
|
+
error = check(`edges[${i}].label`, edge.label);
|
|
508
|
+
if (error) return error;
|
|
509
|
+
}
|
|
510
|
+
return void 0;
|
|
511
|
+
}
|
|
512
|
+
function terminalText(text, multiline = false) {
|
|
513
|
+
return text.replace(multiline ? /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g : /[\u0000-\u001f\u007f-\u009f]/g, "?");
|
|
514
|
+
}
|
|
515
|
+
|
|
482
516
|
// src/render/width.ts
|
|
483
517
|
var WIDE_RANGES = [
|
|
484
518
|
[4352, 4447],
|
|
@@ -573,6 +607,7 @@ function displayWidth(text) {
|
|
|
573
607
|
return w;
|
|
574
608
|
}
|
|
575
609
|
function fitWidth(s, width) {
|
|
610
|
+
s = terminalText(s);
|
|
576
611
|
if (displayWidth(s) <= width) return s;
|
|
577
612
|
let out = "";
|
|
578
613
|
let w = 0;
|
|
@@ -588,7 +623,7 @@ function wrapWidth(s, width) {
|
|
|
588
623
|
const lines = [];
|
|
589
624
|
let line = "";
|
|
590
625
|
let w = 0;
|
|
591
|
-
for (const ch of s.replace(/\r/g, "")) {
|
|
626
|
+
for (const ch of terminalText(s.replace(/\r/g, "").replace(/\t/g, " "), true)) {
|
|
592
627
|
if (ch === "\n") {
|
|
593
628
|
lines.push(line);
|
|
594
629
|
line = "";
|
|
@@ -617,7 +652,9 @@ var SGR = {
|
|
|
617
652
|
greenDim: "32;2",
|
|
618
653
|
// done, but nothing behind the claim: green, not fully lit
|
|
619
654
|
red: "31",
|
|
620
|
-
faint: "90"
|
|
655
|
+
faint: "90",
|
|
656
|
+
focus: "97"
|
|
657
|
+
// bright foreground without changing font weight
|
|
621
658
|
};
|
|
622
659
|
var ANSI_RESET = "\x1B[0m";
|
|
623
660
|
var UP = 1;
|
|
@@ -678,7 +715,7 @@ var Canvas = class {
|
|
|
678
715
|
/** Write literal text starting at (x, y). Returns the column just past it. */
|
|
679
716
|
text(x, y, s, style, bold = false) {
|
|
680
717
|
let cx = x;
|
|
681
|
-
for (const ch of s) {
|
|
718
|
+
for (const ch of terminalText(s)) {
|
|
682
719
|
const w = charWidth(ch.codePointAt(0));
|
|
683
720
|
if (w === 0) {
|
|
684
721
|
const base = this.cell(Math.max(0, cx - 1), y);
|
|
@@ -737,7 +774,7 @@ var Canvas = class {
|
|
|
737
774
|
} else if (charWidth(ch.codePointAt(0)) === 2 && x + 1 >= vp.x + vp.width) {
|
|
738
775
|
ch = " ";
|
|
739
776
|
}
|
|
740
|
-
const params = ch === " " ? "" : isWire ? c.bright ?
|
|
777
|
+
const params = ch === " " ? "" : isWire ? c.bright ? SGR.focus : SGR.faint : [SGR[c.style], c.bold ? "1" : ""].filter(Boolean).join(";");
|
|
741
778
|
if (opts.color && params !== open) {
|
|
742
779
|
line += (open !== "" ? ANSI_RESET : "") + (params !== "" ? `\x1B[${params}m` : "");
|
|
743
780
|
open = params;
|
|
@@ -1086,6 +1123,7 @@ function drawBands(canvas, columns, rows, wiredWidth, totalWidth) {
|
|
|
1086
1123
|
function drawBox(canvas, box, opts, neutral, face, focused = false) {
|
|
1087
1124
|
const { node, x, y, w } = box;
|
|
1088
1125
|
const skin = neutral ? neutralSkin(opts.unicode) : skinFor(face, opts.unicode);
|
|
1126
|
+
const borderStyle = focused ? "focus" : skin.style;
|
|
1089
1127
|
const slotGlyph = neutral ? neutralGlyph(node, opts.unicode) : glyphFor(face, opts);
|
|
1090
1128
|
if (box.borderless) {
|
|
1091
1129
|
canvas.text(x + 1, y, slotGlyph, skin.style, true);
|
|
@@ -1093,18 +1131,18 @@ function drawBox(canvas, box, opts, neutral, face, focused = false) {
|
|
|
1093
1131
|
}
|
|
1094
1132
|
const inner = w - 2;
|
|
1095
1133
|
const pad = box.pad === 1 ? " " : "";
|
|
1096
|
-
canvas.text(x, y, skin.corners[0] + skin.h.repeat(inner) + skin.corners[1],
|
|
1097
|
-
canvas.text(x, y + 1, skin.v,
|
|
1134
|
+
canvas.text(x, y, skin.corners[0] + skin.h.repeat(inner) + skin.corners[1], borderStyle);
|
|
1135
|
+
canvas.text(x, y + 1, skin.v, borderStyle);
|
|
1098
1136
|
canvas.text(x + 1, y + 1, `${pad}${slotGlyph} ${box.label}${pad}`, skin.style, true);
|
|
1099
|
-
canvas.text(x + w - 1, y + 1, skin.v,
|
|
1137
|
+
canvas.text(x + w - 1, y + 1, skin.v, borderStyle);
|
|
1100
1138
|
for (let i = 0; i < box.extra.length; i++) {
|
|
1101
1139
|
const row = box.extra[i];
|
|
1102
1140
|
const yy = y + 2 + i;
|
|
1103
|
-
canvas.text(x, yy, skin.v,
|
|
1141
|
+
canvas.text(x, yy, skin.v, borderStyle);
|
|
1104
1142
|
canvas.text(x + 1, yy, row.text, row.style);
|
|
1105
|
-
canvas.text(x + w - 1, yy, skin.v,
|
|
1143
|
+
canvas.text(x + w - 1, yy, skin.v, borderStyle);
|
|
1106
1144
|
}
|
|
1107
|
-
canvas.text(x, y + box.h - 1, skin.corners[2] + skin.h.repeat(inner) + skin.corners[3],
|
|
1145
|
+
canvas.text(x, y + box.h - 1, skin.corners[2] + skin.h.repeat(inner) + skin.corners[3], borderStyle);
|
|
1108
1146
|
}
|
|
1109
1147
|
function drawEdges(canvas, edges, rows, opts) {
|
|
1110
1148
|
for (const edge of edges) {
|
|
@@ -1283,7 +1321,27 @@ function layoutRows(columns, geo, gapRowCount, hasTitle, hasLanes) {
|
|
|
1283
1321
|
|
|
1284
1322
|
// src/render/render.ts
|
|
1285
1323
|
function renderMapWindow(map, opts, viewport) {
|
|
1286
|
-
|
|
1324
|
+
return renderSceneWindow(prepareScene(map, opts), opts, viewport);
|
|
1325
|
+
}
|
|
1326
|
+
function createWindowRenderer() {
|
|
1327
|
+
let previous;
|
|
1328
|
+
let frame;
|
|
1329
|
+
return (map, opts, viewport) => {
|
|
1330
|
+
const zoom = opts.zoom ?? ZOOM_DEFAULT;
|
|
1331
|
+
if (previous?.map !== map || previous.unicode !== opts.unicode || previous.zoom !== zoom) {
|
|
1332
|
+
previous = { map, unicode: opts.unicode, zoom, scene: prepareScene(map, opts) };
|
|
1333
|
+
}
|
|
1334
|
+
const scene = previous.scene;
|
|
1335
|
+
if (frame?.scene !== scene || frame.spinner !== opts.spinnerFrame || frame.focus !== opts.focus || frame.color !== opts.color) {
|
|
1336
|
+
frame = { scene, spinner: opts.spinnerFrame, focus: opts.focus, color: opts.color, built: paint(scene, opts) };
|
|
1337
|
+
}
|
|
1338
|
+
return emitWindow(frame.built, opts, viewport);
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
function renderSceneWindow(scene, opts, viewport) {
|
|
1342
|
+
return emitWindow(paint(scene, opts), opts, viewport);
|
|
1343
|
+
}
|
|
1344
|
+
function emitWindow(built, opts, viewport) {
|
|
1287
1345
|
return {
|
|
1288
1346
|
lines: built.canvas.emit(opts, viewport),
|
|
1289
1347
|
contentWidth: built.canvas.width,
|
|
@@ -1291,26 +1349,40 @@ function renderMapWindow(map, opts, viewport) {
|
|
|
1291
1349
|
hits: built.hits
|
|
1292
1350
|
};
|
|
1293
1351
|
}
|
|
1294
|
-
function
|
|
1352
|
+
function prepareScene(map, opts) {
|
|
1295
1353
|
const oriented = flipForSequence(map);
|
|
1296
1354
|
const plainGeo = zoomGeometry(opts.zoom ?? ZOOM_DEFAULT);
|
|
1297
1355
|
const aggregated = plainGeo.mode === "constellation" ? aggregateMap(oriented) : void 0;
|
|
1298
1356
|
const drawn = aggregated ?? oriented;
|
|
1299
|
-
|
|
1357
|
+
const unverified = unverifiedDoneIds(oriented, drawn);
|
|
1358
|
+
if (drawn.layers.length === 0) return { map: drawn, unverified };
|
|
1359
|
+
return { map: drawn, unverified, geometry: prepareGeometry(drawn, opts, aggregated !== void 0 ? AGGREGATE_GEO : plainGeo) };
|
|
1300
1360
|
}
|
|
1301
|
-
function
|
|
1302
|
-
const canvas = new Canvas();
|
|
1303
|
-
if (map.layers.length === 0) {
|
|
1304
|
-
canvas.text(0, 0, map.title ?? "mellos mapping", "none", true);
|
|
1305
|
-
canvas.text(0, 2, "(empty map \u2014 declare layers and nodes to begin)", "dim");
|
|
1306
|
-
return { canvas, hits: [] };
|
|
1307
|
-
}
|
|
1361
|
+
function prepareGeometry(map, opts, geo) {
|
|
1308
1362
|
const neutral = isNeutralKind(map);
|
|
1309
1363
|
const columns = layoutColumns(map, geo, opts.unicode, neutral);
|
|
1310
1364
|
const routing = routeEdges(map, columns);
|
|
1311
1365
|
const rows = layoutRows(columns, geo, routing.gapRowCount, map.title !== void 0, map.lanes.length > 0);
|
|
1312
1366
|
const wiredWidth = routing.fallbackCount > 0 ? columns.contentWidth + 2 + routing.fallbackCount * 2 : columns.contentWidth;
|
|
1313
1367
|
const totalWidth = wiredWidth + Math.max(...columns.bandLabel.map(displayWidth));
|
|
1368
|
+
const hits = [...rows.boxOf.values()].map((b) => ({
|
|
1369
|
+
id: b.node.id,
|
|
1370
|
+
x: b.x,
|
|
1371
|
+
y: b.y,
|
|
1372
|
+
w: b.w,
|
|
1373
|
+
h: b.h
|
|
1374
|
+
}));
|
|
1375
|
+
return { neutral, columns, routing, rows, wiredWidth, totalWidth, hits };
|
|
1376
|
+
}
|
|
1377
|
+
function paint(scene, opts) {
|
|
1378
|
+
const { map, unverified, geometry } = scene;
|
|
1379
|
+
const canvas = new Canvas();
|
|
1380
|
+
if (geometry === void 0) {
|
|
1381
|
+
canvas.text(0, 0, map.title ?? "mellos mapping", "none", true);
|
|
1382
|
+
canvas.text(0, 2, "(empty map \u2014 declare layers and nodes to begin)", "dim");
|
|
1383
|
+
return { canvas, hits: [] };
|
|
1384
|
+
}
|
|
1385
|
+
const { neutral, columns, routing, rows, wiredWidth, totalWidth, hits } = geometry;
|
|
1314
1386
|
if (map.title !== void 0) drawTitle(canvas, map.title);
|
|
1315
1387
|
drawLaneHeaders(canvas, map, columns, rows);
|
|
1316
1388
|
drawBands(canvas, columns, rows, wiredWidth, totalWidth);
|
|
@@ -1321,20 +1393,9 @@ function paint(map, opts, geo, unverified) {
|
|
|
1321
1393
|
}
|
|
1322
1394
|
drawEdges(canvas, routing.edges, rows, opts);
|
|
1323
1395
|
drawLegend(canvas, map, opts, rows.legendY, neutral, unverified.size > 0);
|
|
1324
|
-
const hits = [...rows.boxOf.values()].map((b) => ({
|
|
1325
|
-
id: b.node.id,
|
|
1326
|
-
x: b.x,
|
|
1327
|
-
y: b.y,
|
|
1328
|
-
w: b.w,
|
|
1329
|
-
h: b.h
|
|
1330
|
-
}));
|
|
1331
1396
|
return { canvas, hits };
|
|
1332
1397
|
}
|
|
1333
1398
|
|
|
1334
|
-
// src/store/store.ts
|
|
1335
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
1336
|
-
import { basename, dirname, join } from "node:path";
|
|
1337
|
-
|
|
1338
1399
|
// src/store/format.ts
|
|
1339
1400
|
var STATE_FILE_VERSION = 1;
|
|
1340
1401
|
function makePageId(raw) {
|
|
@@ -1545,11 +1606,13 @@ function parseMap(raw, path) {
|
|
|
1545
1606
|
if (!linked.ok) return err({ kind: "invariant-violation", path, violation: linked.error });
|
|
1546
1607
|
map = linked.value;
|
|
1547
1608
|
}
|
|
1548
|
-
|
|
1609
|
+
const textError = mapTextError(map);
|
|
1610
|
+
return textError ? err({ kind: "bad-shape", path, detail: textError }) : ok(map);
|
|
1549
1611
|
}
|
|
1550
1612
|
|
|
1551
|
-
// src/store/
|
|
1552
|
-
|
|
1613
|
+
// src/store/atomic.ts
|
|
1614
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1615
|
+
import { dirname } from "node:path";
|
|
1553
1616
|
var RENAME_BACKOFF_STEP_MS = 10;
|
|
1554
1617
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOENT"]);
|
|
1555
1618
|
function sleepSync(ms) {
|
|
@@ -1564,7 +1627,7 @@ function discardTemp(tmp) {
|
|
|
1564
1627
|
function errnoOf(e) {
|
|
1565
1628
|
return e.code ?? e.message;
|
|
1566
1629
|
}
|
|
1567
|
-
function
|
|
1630
|
+
function writeAtomic(path, contents, maxAttempts) {
|
|
1568
1631
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
1569
1632
|
try {
|
|
1570
1633
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -1580,7 +1643,7 @@ function writeFileAtomic(path, contents) {
|
|
|
1580
1643
|
return ok(void 0);
|
|
1581
1644
|
} catch (e) {
|
|
1582
1645
|
const code = errnoOf(e);
|
|
1583
|
-
if (!TRANSIENT_RENAME_CODES.has(code) || attempt >=
|
|
1646
|
+
if (!TRANSIENT_RENAME_CODES.has(code) || attempt >= maxAttempts) {
|
|
1584
1647
|
discardTemp(tmp);
|
|
1585
1648
|
return err({ kind: "save-failed", path, detail: `${code} after ${attempt} attempt(s)` });
|
|
1586
1649
|
}
|
|
@@ -1589,17 +1652,15 @@ function writeFileAtomic(path, contents) {
|
|
|
1589
1652
|
}
|
|
1590
1653
|
}
|
|
1591
1654
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1597
|
-
}
|
|
1655
|
+
|
|
1656
|
+
// src/store/pages.ts
|
|
1657
|
+
import { existsSync, readdirSync, rmSync as rmSync2 } from "node:fs";
|
|
1658
|
+
import { basename, dirname as dirname2, join } from "node:path";
|
|
1598
1659
|
var STORE_DIR_NAME = ".mellos";
|
|
1599
1660
|
var STATE_FILE_RELATIVE_PATH = join(STORE_DIR_NAME, "map.json");
|
|
1600
1661
|
var PAGES_DIR_NAME = "pages";
|
|
1601
1662
|
function pageFilePath(defaultFile, page) {
|
|
1602
|
-
return page === void 0 ? defaultFile : join(
|
|
1663
|
+
return page === void 0 ? defaultFile : join(dirname2(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
1603
1664
|
}
|
|
1604
1665
|
function pageIdOfFile(defaultFile, path) {
|
|
1605
1666
|
if (path === defaultFile) return void 0;
|
|
@@ -1611,123 +1672,158 @@ function listPageFiles(defaultFile) {
|
|
|
1611
1672
|
if (existsSync(defaultFile)) out.push(defaultFile);
|
|
1612
1673
|
let entries = [];
|
|
1613
1674
|
try {
|
|
1614
|
-
entries = readdirSync(join(
|
|
1675
|
+
entries = readdirSync(join(dirname2(defaultFile), PAGES_DIR_NAME));
|
|
1615
1676
|
} catch {
|
|
1616
1677
|
}
|
|
1617
1678
|
for (const e of entries.sort()) {
|
|
1618
|
-
if (e.endsWith(".json")) out.push(join(
|
|
1679
|
+
if (e.endsWith(".json")) out.push(join(dirname2(defaultFile), PAGES_DIR_NAME, e));
|
|
1619
1680
|
}
|
|
1620
1681
|
return out;
|
|
1621
1682
|
}
|
|
1622
1683
|
function deletePageFile(path) {
|
|
1623
1684
|
try {
|
|
1624
|
-
|
|
1685
|
+
rmSync2(path, { force: true });
|
|
1625
1686
|
return ok(void 0);
|
|
1626
1687
|
} catch (e) {
|
|
1627
1688
|
return err({ kind: "delete-failed", path, detail: errnoOf(e) });
|
|
1628
1689
|
}
|
|
1629
1690
|
}
|
|
1691
|
+
|
|
1692
|
+
// src/store/channels.ts
|
|
1693
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync4 } from "node:fs";
|
|
1694
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
1695
|
+
|
|
1696
|
+
// src/store/json-text.ts
|
|
1697
|
+
function isRecord2(v) {
|
|
1698
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1699
|
+
}
|
|
1700
|
+
function stripBom(text) {
|
|
1701
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/store/viewers.ts
|
|
1705
|
+
import { readdirSync as readdirSync2, readFileSync, statSync, rmSync as rmSync3 } from "node:fs";
|
|
1706
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
1707
|
+
var VIEWERS_DIR_NAME = "viewers";
|
|
1708
|
+
var VIEWER_FILE_VERSION = 1;
|
|
1709
|
+
var VIEWER_HEARTBEAT_MS = 1e3;
|
|
1710
|
+
function viewersDirPath(defaultFile) {
|
|
1711
|
+
return join2(dirname3(defaultFile), VIEWERS_DIR_NAME);
|
|
1712
|
+
}
|
|
1713
|
+
function viewerFilePath(defaultFile, pid) {
|
|
1714
|
+
return join2(viewersDirPath(defaultFile), `${pid}.json`);
|
|
1715
|
+
}
|
|
1716
|
+
function publishViewer(defaultFile, pid, report) {
|
|
1717
|
+
const body = { version: VIEWER_FILE_VERSION, page: report.page ?? null, follow: report.follow, owner: report.owner };
|
|
1718
|
+
return writeAtomic(viewerFilePath(defaultFile, pid), `${JSON.stringify(body, null, 2)}
|
|
1719
|
+
`, 1);
|
|
1720
|
+
}
|
|
1721
|
+
function retireViewer(defaultFile, pid) {
|
|
1722
|
+
try {
|
|
1723
|
+
rmSync3(viewerFilePath(defaultFile, pid), { force: true });
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// src/store/channels.ts
|
|
1630
1729
|
var FOCUS_FILE_NAME = "focus";
|
|
1631
|
-
function focusFilePath(defaultFile) {
|
|
1632
|
-
return
|
|
1730
|
+
function focusFilePath(defaultFile, pid) {
|
|
1731
|
+
return paneChannelPath(defaultFile, FOCUS_FILE_NAME, pid);
|
|
1732
|
+
}
|
|
1733
|
+
function paneChannelPath(defaultFile, channel, pid) {
|
|
1734
|
+
if (pid === void 0) return join3(dirname4(defaultFile), channel);
|
|
1735
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error("Invalid pane process id");
|
|
1736
|
+
return join3(viewersDirPath(defaultFile), `${pid}.${channel}`);
|
|
1633
1737
|
}
|
|
1634
|
-
function takeFocusRequest(defaultFile) {
|
|
1635
|
-
const
|
|
1738
|
+
function takeFocusRequest(defaultFile, pid) {
|
|
1739
|
+
const targeted = pid === void 0 ? void 0 : focusFilePath(defaultFile, pid);
|
|
1740
|
+
const path = targeted !== void 0 && existsSync2(targeted) ? targeted : focusFilePath(defaultFile);
|
|
1636
1741
|
let raw;
|
|
1637
1742
|
try {
|
|
1638
|
-
raw =
|
|
1743
|
+
raw = readFileSync2(path, "utf8");
|
|
1639
1744
|
} catch {
|
|
1640
1745
|
return void 0;
|
|
1641
1746
|
}
|
|
1642
1747
|
try {
|
|
1643
|
-
|
|
1748
|
+
rmSync4(path, { force: true });
|
|
1644
1749
|
} catch {
|
|
1645
1750
|
}
|
|
1646
|
-
let
|
|
1751
|
+
let parsed2;
|
|
1647
1752
|
try {
|
|
1648
|
-
|
|
1753
|
+
parsed2 = JSON.parse(raw);
|
|
1649
1754
|
} catch {
|
|
1650
1755
|
return void 0;
|
|
1651
1756
|
}
|
|
1652
|
-
if (typeof
|
|
1653
|
-
const page =
|
|
1757
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return void 0;
|
|
1758
|
+
const page = parsed2.page;
|
|
1654
1759
|
if (page === void 0 || page === null) return { page: void 0 };
|
|
1655
1760
|
if (typeof page !== "string") return void 0;
|
|
1656
1761
|
const id = makePageId(page);
|
|
1657
1762
|
return id.ok ? { page: id.value } : void 0;
|
|
1658
1763
|
}
|
|
1659
1764
|
var QUIT_FILE_NAME = "quit";
|
|
1660
|
-
function quitFilePath(defaultFile) {
|
|
1661
|
-
return
|
|
1765
|
+
function quitFilePath(defaultFile, pid) {
|
|
1766
|
+
return paneChannelPath(defaultFile, QUIT_FILE_NAME, pid);
|
|
1662
1767
|
}
|
|
1663
|
-
function takeQuitRequest(defaultFile) {
|
|
1664
|
-
const
|
|
1768
|
+
function takeQuitRequest(defaultFile, pid) {
|
|
1769
|
+
const targeted = pid === void 0 ? void 0 : quitFilePath(defaultFile, pid);
|
|
1770
|
+
const path = targeted !== void 0 && existsSync2(targeted) ? targeted : quitFilePath(defaultFile);
|
|
1665
1771
|
let raw;
|
|
1666
1772
|
try {
|
|
1667
|
-
raw =
|
|
1773
|
+
raw = readFileSync2(path, "utf8");
|
|
1668
1774
|
} catch {
|
|
1669
1775
|
return false;
|
|
1670
1776
|
}
|
|
1671
|
-
sweepQuitRequest(defaultFile);
|
|
1672
|
-
let
|
|
1777
|
+
sweepQuitRequest(defaultFile, path === targeted ? pid : void 0);
|
|
1778
|
+
let parsed2;
|
|
1673
1779
|
try {
|
|
1674
|
-
|
|
1780
|
+
parsed2 = JSON.parse(stripBom(raw));
|
|
1675
1781
|
} catch {
|
|
1676
1782
|
return false;
|
|
1677
1783
|
}
|
|
1678
|
-
return isRecord2(
|
|
1679
|
-
}
|
|
1680
|
-
function sweepQuitRequest(defaultFile) {
|
|
1681
|
-
try {
|
|
1682
|
-
rmSync(quitFilePath(defaultFile), { force: true });
|
|
1683
|
-
} catch {
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
var VIEWERS_DIR_NAME = "viewers";
|
|
1687
|
-
var VIEWER_FILE_VERSION = 1;
|
|
1688
|
-
var VIEWER_HEARTBEAT_MS = 1e3;
|
|
1689
|
-
function viewersDirPath(defaultFile) {
|
|
1690
|
-
return join(dirname(defaultFile), VIEWERS_DIR_NAME);
|
|
1691
|
-
}
|
|
1692
|
-
function viewerFilePath(defaultFile, pid) {
|
|
1693
|
-
return join(viewersDirPath(defaultFile), `${pid}.json`);
|
|
1694
|
-
}
|
|
1695
|
-
function publishViewer(defaultFile, pid, report) {
|
|
1696
|
-
const body = { version: VIEWER_FILE_VERSION, page: report.page ?? null, follow: report.follow };
|
|
1697
|
-
return writeFileAtomic(viewerFilePath(defaultFile, pid), `${JSON.stringify(body, null, 2)}
|
|
1698
|
-
`);
|
|
1784
|
+
return isRecord2(parsed2);
|
|
1699
1785
|
}
|
|
1700
|
-
function
|
|
1786
|
+
function sweepQuitRequest(defaultFile, pid) {
|
|
1701
1787
|
try {
|
|
1702
|
-
|
|
1788
|
+
rmSync4(quitFilePath(defaultFile, pid), { force: true });
|
|
1703
1789
|
} catch {
|
|
1704
1790
|
}
|
|
1705
1791
|
}
|
|
1792
|
+
|
|
1793
|
+
// src/store/policy.ts
|
|
1794
|
+
import { dirname as dirname5, join as join4 } from "node:path";
|
|
1706
1795
|
var CONFIG_FILE_NAME = "config.json";
|
|
1707
1796
|
function configFilePath(defaultFile) {
|
|
1708
|
-
return
|
|
1797
|
+
return join4(dirname5(defaultFile), CONFIG_FILE_NAME);
|
|
1709
1798
|
}
|
|
1710
|
-
|
|
1799
|
+
|
|
1800
|
+
// src/store/migration.ts
|
|
1801
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "node:fs";
|
|
1802
|
+
import { dirname as dirname6, join as join5 } from "node:path";
|
|
1803
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join5(".claude", "mellos-mapping.json");
|
|
1711
1804
|
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
1712
1805
|
var LEGACY_CONFIG_FILE_NAME = "mellos-mapping.config.json";
|
|
1713
1806
|
function migrateLegacyStore(defaultFile) {
|
|
1714
|
-
const projectRoot =
|
|
1715
|
-
const legacyDefault =
|
|
1716
|
-
const legacyPages =
|
|
1717
|
-
const legacyConfig =
|
|
1718
|
-
const hasLegacy =
|
|
1719
|
-
const hasCurrent =
|
|
1807
|
+
const projectRoot = dirname6(dirname6(defaultFile));
|
|
1808
|
+
const legacyDefault = join5(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
1809
|
+
const legacyPages = join5(dirname6(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
1810
|
+
const legacyConfig = join5(dirname6(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
1811
|
+
const hasLegacy = existsSync3(legacyDefault) || existsSync3(legacyPages) || existsSync3(legacyConfig);
|
|
1812
|
+
const hasCurrent = existsSync3(defaultFile) || existsSync3(join5(dirname6(defaultFile), PAGES_DIR_NAME)) || existsSync3(configFilePath(defaultFile));
|
|
1720
1813
|
if (!hasLegacy || hasCurrent) return false;
|
|
1721
|
-
|
|
1722
|
-
if (
|
|
1723
|
-
if (
|
|
1724
|
-
if (
|
|
1814
|
+
mkdirSync2(dirname6(defaultFile), { recursive: true });
|
|
1815
|
+
if (existsSync3(legacyDefault)) renameSync2(legacyDefault, defaultFile);
|
|
1816
|
+
if (existsSync3(legacyPages)) renameSync2(legacyPages, join5(dirname6(defaultFile), PAGES_DIR_NAME));
|
|
1817
|
+
if (existsSync3(legacyConfig)) renameSync2(legacyConfig, configFilePath(defaultFile));
|
|
1725
1818
|
return true;
|
|
1726
1819
|
}
|
|
1820
|
+
|
|
1821
|
+
// src/store/maps.ts
|
|
1822
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1727
1823
|
function loadMapFile(path) {
|
|
1728
1824
|
let text;
|
|
1729
1825
|
try {
|
|
1730
|
-
text =
|
|
1826
|
+
text = readFileSync3(path, "utf8");
|
|
1731
1827
|
} catch (e) {
|
|
1732
1828
|
const code = e.code;
|
|
1733
1829
|
if (code === "ENOENT") return err({ kind: "not-found", path });
|
|
@@ -1843,6 +1939,175 @@ function parseInput(chunk) {
|
|
|
1843
1939
|
return { events, rest: "" };
|
|
1844
1940
|
}
|
|
1845
1941
|
|
|
1942
|
+
// src/watch/view-state.ts
|
|
1943
|
+
var defaultPage = () => ({ offsetX: 0, offsetY: 0, zoom: ZOOM_DEFAULT, selectedId: void 0 });
|
|
1944
|
+
function initialViewState() {
|
|
1945
|
+
return {
|
|
1946
|
+
...defaultPage(),
|
|
1947
|
+
pageViews: /* @__PURE__ */ new Map(),
|
|
1948
|
+
hoverId: void 0,
|
|
1949
|
+
dragAnchor: void 0,
|
|
1950
|
+
press: void 0,
|
|
1951
|
+
dividerDrag: false,
|
|
1952
|
+
lastClick: void 0
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
function reduceView(state, event) {
|
|
1956
|
+
switch (event.kind) {
|
|
1957
|
+
case "reset":
|
|
1958
|
+
return { ...state, offsetX: 0, offsetY: 0, zoom: ZOOM_DEFAULT };
|
|
1959
|
+
case "pan":
|
|
1960
|
+
return { ...state, offsetX: state.offsetX + event.dx, offsetY: state.offsetY + event.dy };
|
|
1961
|
+
case "position":
|
|
1962
|
+
return { ...state, offsetX: event.x, offsetY: event.y };
|
|
1963
|
+
case "zoom":
|
|
1964
|
+
return { ...state, zoom: event.value };
|
|
1965
|
+
case "hover":
|
|
1966
|
+
return event.id === state.hoverId ? state : { ...state, hoverId: event.id };
|
|
1967
|
+
case "select":
|
|
1968
|
+
return { ...state, selectedId: event.id };
|
|
1969
|
+
case "down":
|
|
1970
|
+
return {
|
|
1971
|
+
...state,
|
|
1972
|
+
dividerDrag: event.divider,
|
|
1973
|
+
dragAnchor: event.divider ? void 0 : { x: event.x, y: event.y, ox: state.offsetX, oy: state.offsetY },
|
|
1974
|
+
press: event.divider ? void 0 : { moved: false }
|
|
1975
|
+
};
|
|
1976
|
+
case "drag": {
|
|
1977
|
+
if (state.dividerDrag || !state.dragAnchor) return state;
|
|
1978
|
+
const { x, y, ox, oy } = state.dragAnchor;
|
|
1979
|
+
const offsetX = ox - (event.x - x);
|
|
1980
|
+
const offsetY = oy - (event.y - y);
|
|
1981
|
+
return offsetX === state.offsetX && offsetY === state.offsetY ? state : { ...state, offsetX, offsetY, press: { moved: true } };
|
|
1982
|
+
}
|
|
1983
|
+
case "up":
|
|
1984
|
+
return { ...state, dragAnchor: void 0, press: void 0, dividerDrag: false };
|
|
1985
|
+
case "pages": {
|
|
1986
|
+
const pageViews = new Map([...state.pageViews].filter(([file]) => event.files.includes(file)));
|
|
1987
|
+
if (event.previous === event.active || event.active === void 0) return { ...state, pageViews };
|
|
1988
|
+
if (event.previous !== void 0 && event.files.includes(event.previous)) pageViews.set(event.previous, {
|
|
1989
|
+
offsetX: state.offsetX,
|
|
1990
|
+
offsetY: state.offsetY,
|
|
1991
|
+
zoom: state.zoom,
|
|
1992
|
+
selectedId: state.selectedId
|
|
1993
|
+
});
|
|
1994
|
+
return {
|
|
1995
|
+
...state,
|
|
1996
|
+
...pageViews.get(event.active) ?? defaultPage(),
|
|
1997
|
+
pageViews,
|
|
1998
|
+
hoverId: void 0,
|
|
1999
|
+
dragAnchor: void 0,
|
|
2000
|
+
press: void 0,
|
|
2001
|
+
dividerDrag: false,
|
|
2002
|
+
lastClick: void 0
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function isPointerClick(state) {
|
|
2008
|
+
return !state.dividerDrag && state.press !== void 0 && !state.press.moved;
|
|
2009
|
+
}
|
|
2010
|
+
function clickNode(state, id, now, doubleClickMs) {
|
|
2011
|
+
const double = id !== void 0 && state.lastClick?.id === id && now - state.lastClick.at <= doubleClickMs;
|
|
2012
|
+
return {
|
|
2013
|
+
state: { ...state, selectedId: id, lastClick: double || id === void 0 ? void 0 : { id, at: now } },
|
|
2014
|
+
diveId: double ? id : void 0
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// src/watch/io.ts
|
|
2019
|
+
var nativeWatcherIO = () => ({
|
|
2020
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
2021
|
+
input: process.stdin,
|
|
2022
|
+
output: process.stdout
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
// src/watch/terminal-session.ts
|
|
2026
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
2027
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
2028
|
+
var ENTER_SCREEN = "\x1B[?1049h";
|
|
2029
|
+
var LEAVE_SCREEN = "\x1B[?1049l";
|
|
2030
|
+
var CLEAR_SCREEN = "\x1B[H\x1B[2J";
|
|
2031
|
+
var MOUSE_ON = "\x1B[?1003h\x1B[?1006h";
|
|
2032
|
+
var MOUSE_OFF = "\x1B[?1003l\x1B[?1006l";
|
|
2033
|
+
var RESET = "\x1B[0m";
|
|
2034
|
+
function terminalRestoreSequence(mouseActive, alternateScreen = false) {
|
|
2035
|
+
return (mouseActive ? MOUSE_OFF : "") + RESET + (alternateScreen ? LEAVE_SCREEN : "") + SHOW_CURSOR + (alternateScreen ? "" : "\n");
|
|
2036
|
+
}
|
|
2037
|
+
function openTerminalSession(options) {
|
|
2038
|
+
const { interactive, write } = options;
|
|
2039
|
+
const mouseActive = interactive && options.mouse;
|
|
2040
|
+
write((interactive ? ENTER_SCREEN : "") + HIDE_CURSOR + CLEAR_SCREEN + (mouseActive ? MOUSE_ON : ""));
|
|
2041
|
+
const timer = mouseActive ? setInterval(() => write(HIDE_CURSOR + MOUSE_ON), 1e3) : void 0;
|
|
2042
|
+
timer?.unref();
|
|
2043
|
+
let closed = false;
|
|
2044
|
+
return {
|
|
2045
|
+
close() {
|
|
2046
|
+
if (closed) return;
|
|
2047
|
+
closed = true;
|
|
2048
|
+
clearInterval(timer);
|
|
2049
|
+
write(terminalRestoreSequence(mouseActive, interactive));
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/watch/frame-output.ts
|
|
2055
|
+
function frameDifference(previous, next) {
|
|
2056
|
+
const compatible = previous?.columns === next.columns;
|
|
2057
|
+
let output = "";
|
|
2058
|
+
for (let row = 0; row < Math.max(next.rows.length, previous?.rows.length ?? 0); row++) {
|
|
2059
|
+
const text = next.rows[row] ?? "";
|
|
2060
|
+
if (compatible && text === previous?.rows[row]) continue;
|
|
2061
|
+
output += `\x1B[${row + 1};1H${text}\x1B[0m\x1B[K`;
|
|
2062
|
+
}
|
|
2063
|
+
return output;
|
|
2064
|
+
}
|
|
2065
|
+
function createFrameOutput(port) {
|
|
2066
|
+
let previous;
|
|
2067
|
+
let latest;
|
|
2068
|
+
let pending;
|
|
2069
|
+
let unsubscribe;
|
|
2070
|
+
let closed = false;
|
|
2071
|
+
const flush = () => {
|
|
2072
|
+
if (closed || unsubscribe !== void 0 || pending === void 0) return;
|
|
2073
|
+
const frame = pending;
|
|
2074
|
+
pending = void 0;
|
|
2075
|
+
const output = frameDifference(previous, frame);
|
|
2076
|
+
if (output === "") return;
|
|
2077
|
+
previous = frame;
|
|
2078
|
+
if (!port.write(output)) {
|
|
2079
|
+
unsubscribe = port.onDrain(() => {
|
|
2080
|
+
unsubscribe?.();
|
|
2081
|
+
unsubscribe = void 0;
|
|
2082
|
+
flush();
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
const refresh = setInterval(() => {
|
|
2087
|
+
previous = void 0;
|
|
2088
|
+
pending = latest;
|
|
2089
|
+
flush();
|
|
2090
|
+
}, 1e3);
|
|
2091
|
+
refresh.unref();
|
|
2092
|
+
return {
|
|
2093
|
+
present(frame) {
|
|
2094
|
+
if (closed) return;
|
|
2095
|
+
latest = pending = frame;
|
|
2096
|
+
flush();
|
|
2097
|
+
},
|
|
2098
|
+
invalidate() {
|
|
2099
|
+
previous = void 0;
|
|
2100
|
+
},
|
|
2101
|
+
close() {
|
|
2102
|
+
closed = true;
|
|
2103
|
+
clearInterval(refresh);
|
|
2104
|
+
unsubscribe?.();
|
|
2105
|
+
unsubscribe = void 0;
|
|
2106
|
+
pending = latest = previous = void 0;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1846
2111
|
// src/watch/pane-state.ts
|
|
1847
2112
|
function describePageFault(fault) {
|
|
1848
2113
|
return fault.kind === "unreadable" ? `cannot read ${fault.path}: ${fault.detail}` : describeStoreError(fault);
|
|
@@ -2011,13 +2276,14 @@ function describeArgsError(e) {
|
|
|
2011
2276
|
}
|
|
2012
2277
|
var USAGE = "usage: mellos-mapping-watch [--file <map.json>] [--page <slug>] [--interval <ms>] [--ascii] [--no-color] [--no-mouse] [--no-follow]";
|
|
2013
2278
|
function parseArgs(argv, cwd) {
|
|
2014
|
-
let file =
|
|
2279
|
+
let file = join6(cwd, STATE_FILE_RELATIVE_PATH);
|
|
2015
2280
|
let intervalMs = POLL_INTERVAL_DEFAULT_MS;
|
|
2016
2281
|
let unicode = true;
|
|
2017
2282
|
let color = true;
|
|
2018
2283
|
let mouse = true;
|
|
2019
2284
|
let page;
|
|
2020
2285
|
let follow = true;
|
|
2286
|
+
let owner;
|
|
2021
2287
|
const valueOf = (flag, raw) => raw === void 0 || raw.startsWith("--") ? err({ kind: "missing-value", flag }) : ok(raw);
|
|
2022
2288
|
for (let i = 0; i < argv.length; i++) {
|
|
2023
2289
|
const flag = argv[i];
|
|
@@ -2031,9 +2297,17 @@ function parseArgs(argv, cwd) {
|
|
|
2031
2297
|
case "--page": {
|
|
2032
2298
|
const value = valueOf(flag, argv[++i]);
|
|
2033
2299
|
if (!value.ok) return value;
|
|
2034
|
-
const
|
|
2035
|
-
if (!
|
|
2036
|
-
page =
|
|
2300
|
+
const parsed2 = makePageId(value.value);
|
|
2301
|
+
if (!parsed2.ok) return err({ kind: "invalid-value", flag, raw: value.value, rule: parsed2.error.rule });
|
|
2302
|
+
page = parsed2.value;
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
2305
|
+
case "--owner": {
|
|
2306
|
+
const value = valueOf(flag, argv[++i]);
|
|
2307
|
+
if (!value.ok) return value;
|
|
2308
|
+
const parsed2 = makePageId(value.value);
|
|
2309
|
+
if (!parsed2.ok) return err({ kind: "invalid-value", flag, raw: value.value, rule: parsed2.error.rule });
|
|
2310
|
+
owner = parsed2.value;
|
|
2037
2311
|
break;
|
|
2038
2312
|
}
|
|
2039
2313
|
case "--interval": {
|
|
@@ -2062,7 +2336,7 @@ function parseArgs(argv, cwd) {
|
|
|
2062
2336
|
return err({ kind: "unknown-flag", flag });
|
|
2063
2337
|
}
|
|
2064
2338
|
}
|
|
2065
|
-
return ok({ file, intervalMs, unicode, color, mouse, page, follow });
|
|
2339
|
+
return ok({ file, intervalMs, unicode, color, mouse, page, follow, ...owner === void 0 ? {} : { owner } });
|
|
2066
2340
|
}
|
|
2067
2341
|
function readPage(file) {
|
|
2068
2342
|
try {
|
|
@@ -2071,9 +2345,9 @@ function readPage(file) {
|
|
|
2071
2345
|
return err({ kind: "unreadable", path: file, detail: e.code ?? e.message });
|
|
2072
2346
|
}
|
|
2073
2347
|
}
|
|
2074
|
-
function renderWindow(map, opts, viewport) {
|
|
2348
|
+
function renderWindow(map, opts, viewport, render = renderMapWindow) {
|
|
2075
2349
|
try {
|
|
2076
|
-
return ok(
|
|
2350
|
+
return ok(render(map, opts, viewport));
|
|
2077
2351
|
} catch (e) {
|
|
2078
2352
|
return err(`this map could not be drawn: ${e.message}`);
|
|
2079
2353
|
}
|
|
@@ -2090,17 +2364,10 @@ function dividerRow(width, unicode, follow) {
|
|
|
2090
2364
|
}
|
|
2091
2365
|
return bar;
|
|
2092
2366
|
}
|
|
2093
|
-
var HIDE_CURSOR = "\x1B[?25l";
|
|
2094
|
-
var SHOW_CURSOR = "\x1B[?25h";
|
|
2095
2367
|
var CLEAR_ALL = "\x1B[H\x1B[2J";
|
|
2096
2368
|
var HOME = "\x1B[H";
|
|
2097
2369
|
var ERASE_LINE_END = "\x1B[K";
|
|
2098
|
-
var
|
|
2099
|
-
var MOUSE_OFF = "\x1B[?1003l\x1B[?1006l";
|
|
2100
|
-
var RESET = "\x1B[0m";
|
|
2101
|
-
function terminalRestoreSequence(mouseActive) {
|
|
2102
|
-
return (mouseActive ? MOUSE_OFF : "") + SHOW_CURSOR + RESET + "\n";
|
|
2103
|
-
}
|
|
2370
|
+
var RESET2 = "\x1B[0m";
|
|
2104
2371
|
var PANEL_CONTENT_ROWS = 6;
|
|
2105
2372
|
var PANEL_ROWS_MIN = 2;
|
|
2106
2373
|
var MAP_ROWS_MIN = 4;
|
|
@@ -2291,7 +2558,7 @@ function waitingInfo(s, width) {
|
|
|
2291
2558
|
const clock = s.elapsedMs !== void 0 ? ` \xB7 waiting ${elapsedLabel(s.elapsedMs)}` : "";
|
|
2292
2559
|
const lines = [
|
|
2293
2560
|
`watching ${s.defaultFile}`,
|
|
2294
|
-
` and ${
|
|
2561
|
+
` and ${join6(s.pagesDir, "*.json")}`,
|
|
2295
2562
|
`polling every ${s.intervalMs} ms${clock}`
|
|
2296
2563
|
];
|
|
2297
2564
|
for (const b of s.broken) lines.push(`! ${b}`);
|
|
@@ -2300,7 +2567,7 @@ function waitingInfo(s, width) {
|
|
|
2300
2567
|
}
|
|
2301
2568
|
function splashFrame(notice, info, frame, width, height, unicode, color) {
|
|
2302
2569
|
if (width < 24 || height < info.length + 3) return void 0;
|
|
2303
|
-
const dim = (s) => color ? `\x1B[90m${s}${
|
|
2570
|
+
const dim = (s) => color ? `\x1B[90m${s}${RESET2}` : s;
|
|
2304
2571
|
const spinner = SPINNER_FRAMES[unicode ? "unicode" : "ascii"];
|
|
2305
2572
|
const status = fitWidth(`${spinner[frame % spinner.length]} ${notice}`, Math.max(1, width - 2));
|
|
2306
2573
|
const statusIndent = " ".repeat(Math.max(0, Math.floor((width - displayWidth(status)) / 2)));
|
|
@@ -2331,17 +2598,12 @@ function viewerReportOf(pane, defaultFile) {
|
|
|
2331
2598
|
const shown = pane.activeFile ?? pane.pendingFocusFile ?? defaultFile;
|
|
2332
2599
|
return { page: pageIdOfFile(defaultFile, shown), follow: pane.follow };
|
|
2333
2600
|
}
|
|
2334
|
-
function
|
|
2335
|
-
const
|
|
2336
|
-
if (!parsed.ok) {
|
|
2337
|
-
console.error(`mellos-mapping-watch: ${describeArgsError(parsed.error)}
|
|
2338
|
-
${USAGE}`);
|
|
2339
|
-
process.exit(1);
|
|
2340
|
-
}
|
|
2341
|
-
const cfg = parsed.value;
|
|
2601
|
+
function runWatcher(cfg, io = nativeWatcherIO()) {
|
|
2602
|
+
const renderScene = createWindowRenderer();
|
|
2342
2603
|
if (migrateLegacyStore(cfg.file)) console.error("mellos-mapping: moved the legacy .claude map store to .mellos/ \u2014 commit the move.");
|
|
2343
2604
|
sweepQuitRequest(cfg.file);
|
|
2344
|
-
|
|
2605
|
+
sweepQuitRequest(cfg.file, process.pid);
|
|
2606
|
+
const interactive = io.interactive;
|
|
2345
2607
|
const mouseActive = interactive && cfg.mouse;
|
|
2346
2608
|
let lastFrame = "";
|
|
2347
2609
|
let spinnerFrame = 0;
|
|
@@ -2350,28 +2612,19 @@ ${USAGE}`);
|
|
|
2350
2612
|
const standbyNotice = "waiting for the first mmap_declare ...";
|
|
2351
2613
|
let map;
|
|
2352
2614
|
let notice = standbyNotice;
|
|
2353
|
-
let lastCols =
|
|
2354
|
-
let lastRows =
|
|
2615
|
+
let lastCols = io.output.columns ?? 0;
|
|
2616
|
+
let lastRows = io.output.rows ?? 0;
|
|
2355
2617
|
let pane = initialPaneState(
|
|
2356
2618
|
cfg.follow,
|
|
2357
2619
|
cfg.page === void 0 ? void 0 : pageFilePath(cfg.file, cfg.page)
|
|
2358
2620
|
);
|
|
2359
|
-
|
|
2621
|
+
let view = initialViewState();
|
|
2360
2622
|
let lastTabSegments = [];
|
|
2361
2623
|
let tabScroll = 0;
|
|
2362
|
-
let offsetX = 0;
|
|
2363
|
-
let offsetY = 0;
|
|
2364
|
-
let zoom = ZOOM_DEFAULT;
|
|
2365
|
-
let dragAnchor;
|
|
2366
|
-
let press;
|
|
2367
|
-
let hoverId;
|
|
2368
|
-
let selectedId;
|
|
2369
2624
|
let lastHits = [];
|
|
2370
2625
|
let lastContent = { w: 0, h: 0 };
|
|
2371
2626
|
let pendingInput = "";
|
|
2372
2627
|
let panelContentRows = PANEL_CONTENT_ROWS;
|
|
2373
|
-
let dividerDrag = false;
|
|
2374
|
-
let lastClick;
|
|
2375
2628
|
let flash;
|
|
2376
2629
|
let lastTabFiles = [];
|
|
2377
2630
|
const topFiles = () => topLevelFiles(cfg.file, filesOf(pane), mapsOf(pane));
|
|
@@ -2387,8 +2640,8 @@ ${USAGE}`);
|
|
|
2387
2640
|
}
|
|
2388
2641
|
return false;
|
|
2389
2642
|
};
|
|
2390
|
-
const viewWidth = () => usableColumns(
|
|
2391
|
-
const viewHeight = () => Math.max(1, (
|
|
2643
|
+
const viewWidth = () => usableColumns(io.output.columns ?? FALLBACK_COLUMNS);
|
|
2644
|
+
const viewHeight = () => Math.max(1, (io.output.rows ?? FALLBACK_ROWS) - (1 + panelContentRows) - 1 - tabRows());
|
|
2392
2645
|
const dividerY = () => tabRows() + viewHeight() + 1;
|
|
2393
2646
|
const pageTabsOf = (files) => files.map((f) => {
|
|
2394
2647
|
const entry = entryOf(pane, f);
|
|
@@ -2415,14 +2668,8 @@ ${USAGE}`);
|
|
|
2415
2668
|
};
|
|
2416
2669
|
const adoptView = (previous) => {
|
|
2417
2670
|
const file = pane.activeFile;
|
|
2671
|
+
view = reduceView(view, { kind: "pages", previous, active: file, files: filesOf(pane) });
|
|
2418
2672
|
if (file === void 0 || file === previous) return;
|
|
2419
|
-
if (previous !== void 0) pageViews.set(previous, { offsetX, offsetY, zoom, selectedId });
|
|
2420
|
-
const view = pageViews.get(file);
|
|
2421
|
-
offsetX = view?.offsetX ?? 0;
|
|
2422
|
-
offsetY = view?.offsetY ?? 0;
|
|
2423
|
-
zoom = view?.zoom ?? ZOOM_DEFAULT;
|
|
2424
|
-
selectedId = view?.selectedId;
|
|
2425
|
-
hoverId = void 0;
|
|
2426
2673
|
const top = topFiles();
|
|
2427
2674
|
const tabIndex = top.indexOf(file);
|
|
2428
2675
|
if (tabIndex >= 0) {
|
|
@@ -2444,16 +2691,33 @@ ${USAGE}`);
|
|
|
2444
2691
|
const sy = termY - 1 - tabRows();
|
|
2445
2692
|
if (sx < 0 || sx >= viewWidth()) return void 0;
|
|
2446
2693
|
if (sy < 0 || sy >= viewHeight()) return void 0;
|
|
2447
|
-
const cx = sx + offsetX;
|
|
2448
|
-
const cy = sy + offsetY;
|
|
2694
|
+
const cx = sx + view.offsetX;
|
|
2695
|
+
const cy = sy + view.offsetY;
|
|
2449
2696
|
return lastHits.find((h) => cx >= h.x && cx < h.x + h.w && cy >= h.y && cy < h.y + h.h)?.id;
|
|
2450
2697
|
};
|
|
2451
2698
|
const publishPresence = () => {
|
|
2452
|
-
|
|
2699
|
+
io.report?.(viewerReportOf(pane, cfg.file));
|
|
2700
|
+
publishViewer(cfg.file, process.pid, {
|
|
2701
|
+
...viewerReportOf(pane, cfg.file),
|
|
2702
|
+
...cfg.owner === void 0 ? {} : { owner: cfg.owner }
|
|
2703
|
+
});
|
|
2453
2704
|
};
|
|
2454
|
-
|
|
2705
|
+
if (interactive) io.input.setRawMode(true);
|
|
2706
|
+
const terminal = openTerminalSession({ interactive, mouse: cfg.mouse, write: (text) => io.output.write(text) });
|
|
2707
|
+
const frameOutput = interactive ? createFrameOutput({
|
|
2708
|
+
write: (text) => io.output.write(text),
|
|
2709
|
+
onDrain: (ready) => {
|
|
2710
|
+
io.output.once("drain", ready);
|
|
2711
|
+
return () => {
|
|
2712
|
+
io.output.off("drain", ready);
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
}) : void 0;
|
|
2716
|
+
let paintTimer;
|
|
2455
2717
|
process.on("exit", () => {
|
|
2456
|
-
|
|
2718
|
+
clearTimeout(paintTimer);
|
|
2719
|
+
frameOutput?.close();
|
|
2720
|
+
terminal.close();
|
|
2457
2721
|
retireViewer(cfg.file, process.pid);
|
|
2458
2722
|
});
|
|
2459
2723
|
const quit = () => process.exit(0);
|
|
@@ -2465,20 +2729,31 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2465
2729
|
`);
|
|
2466
2730
|
process.exit(1);
|
|
2467
2731
|
});
|
|
2732
|
+
const sceneOptions = () => ({
|
|
2733
|
+
color: cfg.color,
|
|
2734
|
+
unicode: cfg.unicode,
|
|
2735
|
+
zoom: view.zoom,
|
|
2736
|
+
focus: view.hoverId ?? view.selectedId,
|
|
2737
|
+
// A spinner on another page must not invalidate this completed picture.
|
|
2738
|
+
spinnerFrame: map?.nodes.some((node) => node.status === "in-progress") ? spinnerFrame : 0
|
|
2739
|
+
});
|
|
2468
2740
|
const paint2 = () => {
|
|
2469
|
-
|
|
2741
|
+
clearTimeout(paintTimer);
|
|
2742
|
+
paintTimer = void 0;
|
|
2743
|
+
const cols = io.output.columns ?? FALLBACK_COLUMNS;
|
|
2470
2744
|
const viewW = viewWidth();
|
|
2471
|
-
panelContentRows = clampPanelRows(panelContentRows,
|
|
2745
|
+
panelContentRows = clampPanelRows(panelContentRows, io.output.rows ?? FALLBACK_ROWS, tabRows());
|
|
2472
2746
|
const viewH = viewHeight();
|
|
2473
|
-
const focus = hoverId ?? selectedId;
|
|
2747
|
+
const focus = view.hoverId ?? view.selectedId;
|
|
2474
2748
|
let body;
|
|
2475
2749
|
let panned = "";
|
|
2476
2750
|
let pannable = false;
|
|
2477
2751
|
if (map !== void 0) {
|
|
2478
2752
|
const rendered = renderWindow(
|
|
2479
2753
|
map,
|
|
2480
|
-
|
|
2481
|
-
{ x: offsetX, y: offsetY, width: viewW, height: viewH }
|
|
2754
|
+
sceneOptions(),
|
|
2755
|
+
{ x: view.offsetX, y: view.offsetY, width: viewW, height: viewH },
|
|
2756
|
+
renderScene
|
|
2482
2757
|
);
|
|
2483
2758
|
if (!rendered.ok) {
|
|
2484
2759
|
body = ["", fitWidth(` ! ${rendered.error}`, viewW), ""];
|
|
@@ -2487,9 +2762,12 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2487
2762
|
const windowed = rendered.value;
|
|
2488
2763
|
const maxX = Math.max(0, windowed.contentWidth - viewW);
|
|
2489
2764
|
const maxY = Math.max(0, windowed.contentHeight - viewH);
|
|
2490
|
-
if (offsetX > maxX || offsetY > maxY || offsetX < 0 || offsetY < 0) {
|
|
2491
|
-
|
|
2492
|
-
|
|
2765
|
+
if (view.offsetX > maxX || view.offsetY > maxY || view.offsetX < 0 || view.offsetY < 0) {
|
|
2766
|
+
view = reduceView(view, {
|
|
2767
|
+
kind: "position",
|
|
2768
|
+
x: Math.min(Math.max(0, view.offsetX), maxX),
|
|
2769
|
+
y: Math.min(Math.max(0, view.offsetY), maxY)
|
|
2770
|
+
});
|
|
2493
2771
|
paint2();
|
|
2494
2772
|
return;
|
|
2495
2773
|
}
|
|
@@ -2497,13 +2775,13 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2497
2775
|
body = windowed.lines;
|
|
2498
2776
|
lastHits = windowed.hits;
|
|
2499
2777
|
lastContent = { w: windowed.contentWidth, h: windowed.contentHeight };
|
|
2500
|
-
if (offsetX !== 0 || offsetY !== 0) panned = ` (+${offsetX},+${offsetY})`;
|
|
2778
|
+
if (view.offsetX !== 0 || view.offsetY !== 0) panned = ` (+${view.offsetX},+${view.offsetY})`;
|
|
2501
2779
|
}
|
|
2502
2780
|
} else {
|
|
2503
2781
|
const info = waitingInfo(
|
|
2504
2782
|
{
|
|
2505
2783
|
defaultFile: cfg.file,
|
|
2506
|
-
pagesDir:
|
|
2784
|
+
pagesDir: join6(dirname7(cfg.file), PAGES_DIR_NAME),
|
|
2507
2785
|
intervalMs: cfg.intervalMs,
|
|
2508
2786
|
elapsedMs: interactive ? Date.now() - startedAt : void 0,
|
|
2509
2787
|
broken: pane.pages.flatMap(
|
|
@@ -2522,15 +2800,15 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2522
2800
|
if (map === void 0) {
|
|
2523
2801
|
panel = Array.from({ length: panelContentRows }, () => ({ text: "", sgr: "" }));
|
|
2524
2802
|
} else if (focus !== void 0) {
|
|
2525
|
-
panel = nodePanel(map, focus, cfg.unicode, panelWidth, selectedId === focus, panelContentRows) ?? mapPanel(map, cfg.unicode, panelWidth, panelContentRows);
|
|
2803
|
+
panel = nodePanel(map, focus, cfg.unicode, panelWidth, view.selectedId === focus, panelContentRows) ?? mapPanel(map, cfg.unicode, panelWidth, panelContentRows);
|
|
2526
2804
|
} else {
|
|
2527
2805
|
panel = mapPanel(map, cfg.unicode, panelWidth, panelContentRows);
|
|
2528
2806
|
}
|
|
2529
2807
|
const separator = dividerRow(viewW, cfg.unicode, pane.follow);
|
|
2530
2808
|
const panelRows = [
|
|
2531
|
-
cfg.color ? `\x1B[90m${separator}${
|
|
2809
|
+
cfg.color ? `\x1B[90m${separator}${RESET2}` : separator,
|
|
2532
2810
|
...panel.map(
|
|
2533
|
-
(l) => cfg.color && l.sgr !== "" && l.text !== "" ? ` \x1B[${l.sgr}m${l.text}${
|
|
2811
|
+
(l) => cfg.color && l.sgr !== "" && l.text !== "" ? ` \x1B[${l.sgr}m${l.text}${RESET2}` : ` ${l.text}`
|
|
2534
2812
|
)
|
|
2535
2813
|
];
|
|
2536
2814
|
let tabLine;
|
|
@@ -2552,43 +2830,53 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2552
2830
|
action: { kind: "back" }
|
|
2553
2831
|
};
|
|
2554
2832
|
lastTabSegments = [head, tail];
|
|
2555
|
-
tabLine = lastTabSegments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${
|
|
2833
|
+
tabLine = lastTabSegments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${RESET2}` : s.text).join("");
|
|
2556
2834
|
} else if (tabRows() > 0) {
|
|
2557
2835
|
const segments = pageTabRow(pageTabsOf(lastTabFiles), viewW, cfg.unicode, tabScroll, mouseActive);
|
|
2558
2836
|
lastTabSegments = segments;
|
|
2559
|
-
tabLine = segments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${
|
|
2837
|
+
tabLine = segments.map((s) => cfg.color && s.sgr !== "" ? `\x1B[${s.sgr}m${s.text}${RESET2}` : s.text).join("");
|
|
2560
2838
|
} else {
|
|
2561
2839
|
lastTabSegments = [];
|
|
2562
2840
|
}
|
|
2563
|
-
const zoomTag = `${cfg.unicode ? "\u2295" : "zoom"} ${zoomLabel(zoom)}`;
|
|
2841
|
+
const zoomTag = `${cfg.unicode ? "\u2295" : "zoom"} ${zoomLabel(view.zoom)}`;
|
|
2564
2842
|
const hint = !interactive ? cfg.file : (flash !== void 0 ? `${flash.text} \xB7 ` : "") + `${zoomTag} \xB7 wheel zoom \xB7 ` + (pannable ? "drag pan \xB7 " : "") + "hover/click \xB7 0 reset \xB7 x delete page \xB7 q quit";
|
|
2565
2843
|
const footerText = fitWidth(` ${hint}${panned}`, viewW);
|
|
2566
|
-
const footer = cfg.color ? `\x1B[90m${footerText}${
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2844
|
+
const footer = cfg.color ? `\x1B[90m${footerText}${RESET2}` : footerText;
|
|
2845
|
+
const rows = [
|
|
2846
|
+
...tabLine === void 0 ? [] : [tabLine],
|
|
2847
|
+
...Array.from({ length: viewH }, (_, i) => body[i] ?? ""),
|
|
2848
|
+
...panelRows,
|
|
2849
|
+
footer
|
|
2850
|
+
];
|
|
2851
|
+
if (frameOutput !== void 0) {
|
|
2852
|
+
frameOutput.present({ columns: cols, rows });
|
|
2853
|
+
} else {
|
|
2854
|
+
const frame = HOME + rows.map((row) => row + ERASE_LINE_END).join("\n");
|
|
2855
|
+
if (frame !== lastFrame) {
|
|
2856
|
+
io.output.write(frame);
|
|
2857
|
+
lastFrame = frame;
|
|
2858
|
+
}
|
|
2575
2859
|
}
|
|
2576
2860
|
};
|
|
2861
|
+
const requestPaint = () => {
|
|
2862
|
+
paintTimer ??= setTimeout(paint2, 16);
|
|
2863
|
+
};
|
|
2577
2864
|
const handleResize = () => {
|
|
2578
|
-
lastCols =
|
|
2579
|
-
lastRows =
|
|
2865
|
+
lastCols = io.output.columns ?? lastCols;
|
|
2866
|
+
lastRows = io.output.rows ?? lastRows;
|
|
2580
2867
|
lastFrame = "";
|
|
2581
|
-
|
|
2868
|
+
frameOutput?.invalidate();
|
|
2869
|
+
io.output.write(CLEAR_ALL);
|
|
2582
2870
|
paint2();
|
|
2583
2871
|
};
|
|
2584
2872
|
const tick = () => {
|
|
2585
|
-
if (takeQuitRequest(cfg.file)) quit();
|
|
2586
|
-
if ((
|
|
2873
|
+
if (takeQuitRequest(cfg.file, process.pid)) quit();
|
|
2874
|
+
if ((io.output.columns ?? lastCols) !== lastCols || (io.output.rows ?? lastRows) !== lastRows) {
|
|
2587
2875
|
handleResize();
|
|
2588
2876
|
}
|
|
2589
2877
|
const discovered = listPageFiles(cfg.file);
|
|
2590
2878
|
const files = discovered.length > 0 ? discovered : [cfg.file];
|
|
2591
|
-
const request = takeFocusRequest(cfg.file);
|
|
2879
|
+
const request = takeFocusRequest(cfg.file, process.pid);
|
|
2592
2880
|
const previous = pane.activeFile;
|
|
2593
2881
|
const scanned = scan(pane, {
|
|
2594
2882
|
files,
|
|
@@ -2603,12 +2891,9 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2603
2891
|
focusRequest: request === void 0 ? void 0 : pageFilePath(cfg.file, request.page),
|
|
2604
2892
|
// a drag in progress holds auto-follow off: the user is engaged with
|
|
2605
2893
|
// THIS page, and a missed switch is re-triggered by the next save
|
|
2606
|
-
engaged: dragAnchor !== void 0
|
|
2894
|
+
engaged: view.dragAnchor !== void 0
|
|
2607
2895
|
});
|
|
2608
2896
|
pane = scanned.state;
|
|
2609
|
-
for (const known of [...pageViews.keys()]) {
|
|
2610
|
-
if (!files.includes(known)) pageViews.delete(known);
|
|
2611
|
-
}
|
|
2612
2897
|
adoptView(previous);
|
|
2613
2898
|
adoptPage();
|
|
2614
2899
|
if (flash?.confirm === true && pane.pendingDelete === void 0) flash = void 0;
|
|
@@ -2649,10 +2934,9 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2649
2934
|
tick();
|
|
2650
2935
|
};
|
|
2651
2936
|
if (interactive) {
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
process.stdin.on("data", (chunk) => {
|
|
2937
|
+
io.input.resume();
|
|
2938
|
+
io.input.setEncoding("utf8");
|
|
2939
|
+
io.input.on("data", (chunk) => {
|
|
2656
2940
|
const parsed2 = parseInput(pendingInput + chunk);
|
|
2657
2941
|
pendingInput = parsed2.rest;
|
|
2658
2942
|
let dirty = false;
|
|
@@ -2662,22 +2946,19 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2662
2946
|
quit();
|
|
2663
2947
|
return;
|
|
2664
2948
|
case "reset":
|
|
2665
|
-
|
|
2666
|
-
offsetY = 0;
|
|
2667
|
-
zoom = ZOOM_DEFAULT;
|
|
2949
|
+
view = reduceView(view, { kind: "reset" });
|
|
2668
2950
|
dirty = true;
|
|
2669
2951
|
break;
|
|
2670
2952
|
case "clear":
|
|
2671
2953
|
if (pane.pendingDelete !== void 0) {
|
|
2672
2954
|
pane = disarmDelete(pane);
|
|
2673
2955
|
flash = void 0;
|
|
2674
|
-
} else if (selectedId !== void 0)
|
|
2956
|
+
} else if (view.selectedId !== void 0) view = reduceView(view, { kind: "select", id: void 0 });
|
|
2675
2957
|
else climbBack();
|
|
2676
2958
|
dirty = true;
|
|
2677
2959
|
break;
|
|
2678
2960
|
case "pan":
|
|
2679
|
-
|
|
2680
|
-
offsetY += event.dy;
|
|
2961
|
+
view = reduceView(view, event);
|
|
2681
2962
|
dirty = true;
|
|
2682
2963
|
break;
|
|
2683
2964
|
case "zoom": {
|
|
@@ -2686,15 +2967,16 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2686
2967
|
dirty = true;
|
|
2687
2968
|
break;
|
|
2688
2969
|
}
|
|
2689
|
-
const next = clampZoom(zoom + event.delta);
|
|
2690
|
-
if (next === zoom || map === void 0) break;
|
|
2691
|
-
const anchorId = hoverId ?? selectedId ?? nearestHit(lastHits, offsetX + viewWidth() / 2, offsetY + viewHeight() / 2)?.id;
|
|
2970
|
+
const next = clampZoom(view.zoom + event.delta);
|
|
2971
|
+
if (next === view.zoom || map === void 0) break;
|
|
2972
|
+
const anchorId = view.hoverId ?? view.selectedId ?? nearestHit(lastHits, view.offsetX + viewWidth() / 2, view.offsetY + viewHeight() / 2)?.id;
|
|
2692
2973
|
const before = lastHits.find((h) => h.id === anchorId);
|
|
2693
|
-
|
|
2974
|
+
view = reduceView(view, { kind: "zoom", value: next });
|
|
2694
2975
|
const measured = renderWindow(
|
|
2695
2976
|
map,
|
|
2696
|
-
|
|
2697
|
-
{ x: 0, y: 0, width: 0, height: 0 }
|
|
2977
|
+
sceneOptions(),
|
|
2978
|
+
{ x: 0, y: 0, width: 0, height: 0 },
|
|
2979
|
+
renderScene
|
|
2698
2980
|
);
|
|
2699
2981
|
if (!measured.ok) {
|
|
2700
2982
|
dirty = true;
|
|
@@ -2704,57 +2986,48 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2704
2986
|
const after = before === void 0 ? void 0 : sized.hits.find((h) => h.id === before.id);
|
|
2705
2987
|
const moved = anchorOffsets(
|
|
2706
2988
|
before !== void 0 && after !== void 0 ? { before, after } : void 0,
|
|
2707
|
-
{ x: offsetX, y: offsetY },
|
|
2989
|
+
{ x: view.offsetX, y: view.offsetY },
|
|
2708
2990
|
lastContent,
|
|
2709
2991
|
{ w: sized.contentWidth, h: sized.contentHeight }
|
|
2710
2992
|
);
|
|
2711
|
-
|
|
2712
|
-
|
|
2993
|
+
view = reduceView(view, { kind: "position", ...moved });
|
|
2994
|
+
lastHits = sized.hits;
|
|
2995
|
+
lastContent = { w: sized.contentWidth, h: sized.contentHeight };
|
|
2713
2996
|
dirty = true;
|
|
2714
2997
|
break;
|
|
2715
2998
|
}
|
|
2716
2999
|
case "mouse-move": {
|
|
2717
3000
|
const over = hitTest(event.x, event.y);
|
|
2718
|
-
if (over !== hoverId) {
|
|
2719
|
-
|
|
3001
|
+
if (over !== view.hoverId) {
|
|
3002
|
+
view = reduceView(view, { kind: "hover", id: over });
|
|
2720
3003
|
dirty = true;
|
|
2721
3004
|
}
|
|
2722
3005
|
break;
|
|
2723
3006
|
}
|
|
2724
3007
|
case "mouse-down":
|
|
2725
|
-
|
|
2726
|
-
dividerDrag = true;
|
|
2727
|
-
break;
|
|
2728
|
-
}
|
|
2729
|
-
dragAnchor = { x: event.x, y: event.y, ox: offsetX, oy: offsetY };
|
|
2730
|
-
press = { moved: false };
|
|
3008
|
+
view = reduceView(view, { kind: "down", x: event.x, y: event.y, divider: event.y === dividerY() });
|
|
2731
3009
|
break;
|
|
2732
3010
|
case "mouse-drag":
|
|
2733
|
-
if (dividerDrag) {
|
|
2734
|
-
const next = panelRowsFromDividerY(event.y,
|
|
3011
|
+
if (view.dividerDrag) {
|
|
3012
|
+
const next = panelRowsFromDividerY(event.y, io.output.rows ?? FALLBACK_ROWS, tabRows());
|
|
2735
3013
|
if (next !== panelContentRows) {
|
|
2736
3014
|
panelContentRows = next;
|
|
2737
3015
|
dirty = true;
|
|
2738
3016
|
}
|
|
2739
3017
|
break;
|
|
2740
3018
|
}
|
|
2741
|
-
|
|
2742
|
-
const
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
offsetX = nx;
|
|
2746
|
-
offsetY = ny;
|
|
2747
|
-
if (press) press.moved = true;
|
|
2748
|
-
dirty = true;
|
|
2749
|
-
}
|
|
3019
|
+
{
|
|
3020
|
+
const nextView = reduceView(view, { kind: "drag", x: event.x, y: event.y });
|
|
3021
|
+
dirty ||= nextView !== view;
|
|
3022
|
+
view = nextView;
|
|
2750
3023
|
}
|
|
2751
3024
|
break;
|
|
2752
3025
|
case "mouse-up":
|
|
2753
|
-
if (dividerDrag) {
|
|
2754
|
-
|
|
3026
|
+
if (view.dividerDrag) {
|
|
3027
|
+
view = reduceView(view, { kind: "up" });
|
|
2755
3028
|
break;
|
|
2756
3029
|
}
|
|
2757
|
-
if (
|
|
3030
|
+
if (isPointerClick(view)) {
|
|
2758
3031
|
const tabHit = tabRows() > 0 && event.y === 1 ? lastTabSegments.find((s) => event.x >= s.lo && event.x <= s.hi) : void 0;
|
|
2759
3032
|
if (tabHit !== void 0) {
|
|
2760
3033
|
if (tabHit.action.kind === "back") {
|
|
@@ -2770,7 +3043,9 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2770
3043
|
} else {
|
|
2771
3044
|
const id = hitTest(event.x, event.y);
|
|
2772
3045
|
const now = Date.now();
|
|
2773
|
-
|
|
3046
|
+
const clicked = clickNode(view, id, now, DOUBLE_CLICK_MS);
|
|
3047
|
+
view = clicked.state;
|
|
3048
|
+
if (clicked.diveId !== void 0) {
|
|
2774
3049
|
const submap = map?.nodes.find((n) => n.id === id)?.submap;
|
|
2775
3050
|
if (submap !== void 0 && pane.activeFile !== void 0) {
|
|
2776
3051
|
const target = pageFilePath(cfg.file, submap);
|
|
@@ -2782,16 +3057,11 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2782
3057
|
flash = { text: `submap "${submap}" has no page yet`, until: now + FLASH_ACK_MS };
|
|
2783
3058
|
}
|
|
2784
3059
|
}
|
|
2785
|
-
lastClick = void 0;
|
|
2786
|
-
} else {
|
|
2787
|
-
lastClick = id !== void 0 ? { id, at: now } : void 0;
|
|
2788
3060
|
}
|
|
2789
|
-
selectedId = id;
|
|
2790
3061
|
}
|
|
2791
3062
|
dirty = true;
|
|
2792
3063
|
}
|
|
2793
|
-
|
|
2794
|
-
press = void 0;
|
|
3064
|
+
view = reduceView(view, { kind: "up" });
|
|
2795
3065
|
break;
|
|
2796
3066
|
case "next-page":
|
|
2797
3067
|
case "prev-page": {
|
|
@@ -2829,9 +3099,13 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2829
3099
|
break;
|
|
2830
3100
|
}
|
|
2831
3101
|
}
|
|
2832
|
-
if (dirty)
|
|
3102
|
+
if (dirty) {
|
|
3103
|
+
const motionOnly = parsed2.events.every((event) => event.kind === "mouse-move" || event.kind === "mouse-drag");
|
|
3104
|
+
if (motionOnly) requestPaint();
|
|
3105
|
+
else paint2();
|
|
3106
|
+
}
|
|
2833
3107
|
});
|
|
2834
|
-
|
|
3108
|
+
io.output.on("resize", handleResize);
|
|
2835
3109
|
}
|
|
2836
3110
|
tick();
|
|
2837
3111
|
setInterval(tick, cfg.intervalMs);
|
|
@@ -2845,43 +3119,12 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2845
3119
|
}, SPLASH_FRAME_MS);
|
|
2846
3120
|
}
|
|
2847
3121
|
}
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
main();
|
|
2858
|
-
}
|
|
2859
|
-
export {
|
|
2860
|
-
PANEL_ROWS_MIN,
|
|
2861
|
-
USAGE,
|
|
2862
|
-
anchorOffsets,
|
|
2863
|
-
clampPanelRows,
|
|
2864
|
-
describeArgsError,
|
|
2865
|
-
describePageFault,
|
|
2866
|
-
diveOrigin,
|
|
2867
|
-
dividerRow,
|
|
2868
|
-
elapsedLabel,
|
|
2869
|
-
fitWidth,
|
|
2870
|
-
launchedAsEntry,
|
|
2871
|
-
mapPanel,
|
|
2872
|
-
nearestHit,
|
|
2873
|
-
nodePanel,
|
|
2874
|
-
pageTabRow,
|
|
2875
|
-
panelRowsFromDividerY,
|
|
2876
|
-
parseArgs,
|
|
2877
|
-
readPage,
|
|
2878
|
-
renderWindow,
|
|
2879
|
-
splashFrame,
|
|
2880
|
-
tabScrollFor,
|
|
2881
|
-
terminalRestoreSequence,
|
|
2882
|
-
topLevelFiles,
|
|
2883
|
-
usableColumns,
|
|
2884
|
-
viewerReportOf,
|
|
2885
|
-
waitingInfo,
|
|
2886
|
-
wrapWidth
|
|
2887
|
-
};
|
|
3122
|
+
|
|
3123
|
+
// src/watch/cli.ts
|
|
3124
|
+
var parsed = parseArgs(process.argv.slice(2), process.cwd());
|
|
3125
|
+
if (!parsed.ok) {
|
|
3126
|
+
console.error(`mellos-mapping-watch: ${describeArgsError(parsed.error)}
|
|
3127
|
+
${USAGE}`);
|
|
3128
|
+
process.exit(1);
|
|
3129
|
+
}
|
|
3130
|
+
runWatcher(parsed.value);
|