@youtyan/code-viewer 0.1.28 → 0.1.30
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/dist/code-viewer.js +46 -12
- package/package.json +1 -1
- package/web/app.js +154 -11
package/dist/code-viewer.js
CHANGED
|
@@ -280,6 +280,8 @@ var DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
|
280
280
|
"out",
|
|
281
281
|
"target",
|
|
282
282
|
".gradle",
|
|
283
|
+
".pnpm-store",
|
|
284
|
+
".turbo",
|
|
283
285
|
"__pycache__",
|
|
284
286
|
".pytest_cache",
|
|
285
287
|
".tox",
|
|
@@ -1279,6 +1281,9 @@ function startWorktreeUpdateWatch(options) {
|
|
|
1279
1281
|
const debounceMs = options.debounceMs ?? 250;
|
|
1280
1282
|
const watchers = new Map;
|
|
1281
1283
|
const signatures = new Map;
|
|
1284
|
+
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
1285
|
+
const initialScanQueue = [];
|
|
1286
|
+
let initialScanTimer = null;
|
|
1282
1287
|
let timer = null;
|
|
1283
1288
|
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
1284
1289
|
const scheduleUpdate = () => {
|
|
@@ -1301,6 +1306,11 @@ function startWorktreeUpdateWatch(options) {
|
|
|
1301
1306
|
}
|
|
1302
1307
|
};
|
|
1303
1308
|
const closeAll = () => {
|
|
1309
|
+
if (initialScanTimer) {
|
|
1310
|
+
clearTimer(initialScanTimer);
|
|
1311
|
+
initialScanTimer = null;
|
|
1312
|
+
}
|
|
1313
|
+
initialScanQueue.length = 0;
|
|
1304
1314
|
for (const watcher of [...watchers.values()]) {
|
|
1305
1315
|
try {
|
|
1306
1316
|
watcher.close?.();
|
|
@@ -1309,7 +1319,36 @@ function startWorktreeUpdateWatch(options) {
|
|
|
1309
1319
|
watchers.clear();
|
|
1310
1320
|
signatures.clear();
|
|
1311
1321
|
};
|
|
1312
|
-
const
|
|
1322
|
+
const readChildDirectories = (dir) => {
|
|
1323
|
+
let entries;
|
|
1324
|
+
try {
|
|
1325
|
+
entries = readDirs(dir);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
options.onError?.(error);
|
|
1328
|
+
return [];
|
|
1329
|
+
}
|
|
1330
|
+
const children = [];
|
|
1331
|
+
for (const entry of entries) {
|
|
1332
|
+
if (!entry.isDirectory())
|
|
1333
|
+
continue;
|
|
1334
|
+
children.push(join4(dir, entry.name));
|
|
1335
|
+
}
|
|
1336
|
+
return children;
|
|
1337
|
+
};
|
|
1338
|
+
const processInitialScanQueue = () => {
|
|
1339
|
+
initialScanTimer = null;
|
|
1340
|
+
const next = initialScanQueue.shift();
|
|
1341
|
+
if (next)
|
|
1342
|
+
watchDirectory(next, true);
|
|
1343
|
+
if (initialScanQueue.length)
|
|
1344
|
+
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
1345
|
+
};
|
|
1346
|
+
const queueInitialChildren = (dir) => {
|
|
1347
|
+
initialScanQueue.push(...readChildDirectories(dir));
|
|
1348
|
+
if (!initialScanTimer)
|
|
1349
|
+
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
1350
|
+
};
|
|
1351
|
+
const watchDirectory = (dir, initialScan = false) => {
|
|
1313
1352
|
if (watchers.has(dir))
|
|
1314
1353
|
return;
|
|
1315
1354
|
const rel = normalizeRelativePath(relative(options.root, dir));
|
|
@@ -1364,20 +1403,14 @@ function startWorktreeUpdateWatch(options) {
|
|
|
1364
1403
|
options.onError?.(error);
|
|
1365
1404
|
return;
|
|
1366
1405
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
entries = readDirs(dir);
|
|
1370
|
-
} catch (error) {
|
|
1371
|
-
options.onError?.(error);
|
|
1406
|
+
if (initialScanAsync && initialScan) {
|
|
1407
|
+
queueInitialChildren(dir);
|
|
1372
1408
|
return;
|
|
1373
1409
|
}
|
|
1374
|
-
for (const
|
|
1375
|
-
|
|
1376
|
-
continue;
|
|
1377
|
-
watchDirectory(join4(dir, entry.name));
|
|
1378
|
-
}
|
|
1410
|
+
for (const child of readChildDirectories(dir))
|
|
1411
|
+
watchDirectory(child);
|
|
1379
1412
|
};
|
|
1380
|
-
watchDirectory(options.root);
|
|
1413
|
+
watchDirectory(options.root, true);
|
|
1381
1414
|
return { started: watchers.size > 0, close: closeAll };
|
|
1382
1415
|
}
|
|
1383
1416
|
|
|
@@ -3147,6 +3180,7 @@ startWorktreeUpdateWatch({
|
|
|
3147
3180
|
omitDirNames: scopeOmitDirNames,
|
|
3148
3181
|
excludeNames: scopeExcludeNames,
|
|
3149
3182
|
watch,
|
|
3183
|
+
initialScanMode: "async",
|
|
3150
3184
|
onUpdate: triggerUpdate,
|
|
3151
3185
|
onError: (error) => {
|
|
3152
3186
|
const message = error instanceof Error ? error.message : String(error);
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -90,6 +90,12 @@
|
|
|
90
90
|
function trailingClickRange(hunkEndNew, step) {
|
|
91
91
|
return { start: hunkEndNew, end: hunkEndNew + step - 1 };
|
|
92
92
|
}
|
|
93
|
+
function trailingExpandTargetIndex(hunkCount) {
|
|
94
|
+
return hunkCount > 0 ? hunkCount - 1 : null;
|
|
95
|
+
}
|
|
96
|
+
function shouldAttachTrailingExpand(probeLineCount) {
|
|
97
|
+
return probeLineCount > 0;
|
|
98
|
+
}
|
|
93
99
|
function applyTrailingResult(state, receivedCount, step) {
|
|
94
100
|
return {
|
|
95
101
|
newStart: state.newStart + receivedCount,
|
|
@@ -106,6 +112,8 @@
|
|
|
106
112
|
applyUp,
|
|
107
113
|
applyDown,
|
|
108
114
|
mapNewToOld,
|
|
115
|
+
trailingExpandTargetIndex,
|
|
116
|
+
shouldAttachTrailingExpand,
|
|
109
117
|
trailingClickRange,
|
|
110
118
|
applyTrailingResult
|
|
111
119
|
};
|
|
@@ -7002,7 +7010,9 @@ ${frontmatter.yaml}
|
|
|
7002
7010
|
let SIDEBAR_VISIBLE_ROWS = [];
|
|
7003
7011
|
let SIDEBAR_ROW_BY_PATH = new Map;
|
|
7004
7012
|
let SIDEBAR_VIRTUAL_ACTIVE_PATH = "";
|
|
7005
|
-
|
|
7013
|
+
let SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
|
|
7014
|
+
const SIDEBAR_LAZY_LOADED_DIRS = new Set;
|
|
7015
|
+
const SIDEBAR_LAZY_LOADING_DIRS = new Map;
|
|
7006
7016
|
let REPO_SORT = {
|
|
7007
7017
|
key: "name",
|
|
7008
7018
|
direction: "asc"
|
|
@@ -8058,6 +8068,86 @@ ${frontmatter.yaml}
|
|
|
8058
8068
|
SIDEBAR_TREE_ITEMS_CACHE.set(node, items);
|
|
8059
8069
|
return items;
|
|
8060
8070
|
}
|
|
8071
|
+
function sidebarTreeNodeHasChildren(node) {
|
|
8072
|
+
return Object.keys(node.dirs).length > 0 || node.files.length > 0;
|
|
8073
|
+
}
|
|
8074
|
+
function shouldLazyLoadSidebarDir(dir) {
|
|
8075
|
+
return isRepositorySidebarMode() && isVirtualSidebarActive() && !dir.children_omitted && !sidebarTreeNodeHasChildren(dir) && !SIDEBAR_LAZY_LOADED_DIRS.has(dir.path);
|
|
8076
|
+
}
|
|
8077
|
+
function upsertSidebarTreeEntry(entry, order) {
|
|
8078
|
+
if (!SIDEBAR_TREE_ROOT)
|
|
8079
|
+
return;
|
|
8080
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
8081
|
+
if (!parts.length)
|
|
8082
|
+
return;
|
|
8083
|
+
let node = SIDEBAR_TREE_ROOT;
|
|
8084
|
+
let acc = "";
|
|
8085
|
+
const dirPartCount = entry.type === "tree" ? parts.length : parts.length - 1;
|
|
8086
|
+
for (let i2 = 0;i2 < dirPartCount; i2++) {
|
|
8087
|
+
const part = parts[i2];
|
|
8088
|
+
acc = acc ? `${acc}/${part}` : part;
|
|
8089
|
+
if (!node.dirs[part]) {
|
|
8090
|
+
node.dirs[part] = {
|
|
8091
|
+
name: part,
|
|
8092
|
+
dirs: {},
|
|
8093
|
+
files: [],
|
|
8094
|
+
path: acc,
|
|
8095
|
+
minOrder: order
|
|
8096
|
+
};
|
|
8097
|
+
}
|
|
8098
|
+
node = node.dirs[part];
|
|
8099
|
+
node.minOrder = Math.min(node.minOrder, order);
|
|
8100
|
+
}
|
|
8101
|
+
if (entry.type === "tree") {
|
|
8102
|
+
node.explicit = true;
|
|
8103
|
+
if (entry.children_omitted === true) {
|
|
8104
|
+
node.children_omitted = true;
|
|
8105
|
+
node.children_omitted_reason = entry.children_omitted_reason;
|
|
8106
|
+
}
|
|
8107
|
+
return;
|
|
8108
|
+
}
|
|
8109
|
+
if (!node.files.some((file) => file.path === entry.path))
|
|
8110
|
+
node.files.push({ ...entry, order });
|
|
8111
|
+
}
|
|
8112
|
+
function mergeSidebarTreeEntries(entries) {
|
|
8113
|
+
entries.forEach((entry, index) => {
|
|
8114
|
+
upsertSidebarTreeEntry(entry, entry.order ?? index + 1);
|
|
8115
|
+
});
|
|
8116
|
+
SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
|
|
8117
|
+
if (SIDEBAR_TREE_ROOT)
|
|
8118
|
+
buildSidebarTreeRows(SIDEBAR_TREE_ROOT);
|
|
8119
|
+
}
|
|
8120
|
+
function ensureVirtualSidebarDirLoaded(dir) {
|
|
8121
|
+
if (!shouldLazyLoadSidebarDir(dir))
|
|
8122
|
+
return Promise.resolve();
|
|
8123
|
+
const existing = SIDEBAR_LAZY_LOADING_DIRS.get(dir.path);
|
|
8124
|
+
if (existing)
|
|
8125
|
+
return existing;
|
|
8126
|
+
const params = new URLSearchParams;
|
|
8127
|
+
params.set("ref", REPO_SIDEBAR_REF || "worktree");
|
|
8128
|
+
params.set("path", dir.path);
|
|
8129
|
+
appendScopeParams(params);
|
|
8130
|
+
const load2 = trackLoad(fetch(`/_tree?${params.toString()}`).then((response) => {
|
|
8131
|
+
if (!response.ok)
|
|
8132
|
+
throw new Error("failed to load repository tree");
|
|
8133
|
+
return response.json();
|
|
8134
|
+
})).then((meta) => {
|
|
8135
|
+
const entries = meta.entries.map((entry, index) => ({
|
|
8136
|
+
order: dir.minOrder + (index + 1) / 1e5,
|
|
8137
|
+
path: entry.path,
|
|
8138
|
+
display_path: entry.path,
|
|
8139
|
+
type: entry.type,
|
|
8140
|
+
children_omitted: entry.children_omitted,
|
|
8141
|
+
children_omitted_reason: entry.children_omitted_reason
|
|
8142
|
+
}));
|
|
8143
|
+
mergeSidebarTreeEntries(entries);
|
|
8144
|
+
SIDEBAR_LAZY_LOADED_DIRS.add(dir.path);
|
|
8145
|
+
}).finally(() => {
|
|
8146
|
+
SIDEBAR_LAZY_LOADING_DIRS.delete(dir.path);
|
|
8147
|
+
});
|
|
8148
|
+
SIDEBAR_LAZY_LOADING_DIRS.set(dir.path, load2);
|
|
8149
|
+
return load2;
|
|
8150
|
+
}
|
|
8061
8151
|
function createTreeDirRow(dir, depth, onFileClick) {
|
|
8062
8152
|
const li = document.createElement("li");
|
|
8063
8153
|
li.className = "tree-dir";
|
|
@@ -8105,16 +8195,26 @@ ${frontmatter.yaml}
|
|
|
8105
8195
|
const updateIcon = () => {
|
|
8106
8196
|
setFolderIcon(dirIcon, li.classList.contains("collapsed"));
|
|
8107
8197
|
};
|
|
8108
|
-
const toggleDir = (e2) => {
|
|
8198
|
+
const toggleDir = async (e2) => {
|
|
8109
8199
|
e2.stopPropagation();
|
|
8110
|
-
li.
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8200
|
+
if (li.dataset.toggling === "true")
|
|
8201
|
+
return;
|
|
8202
|
+
const expanding = li.classList.contains("collapsed");
|
|
8203
|
+
li.dataset.toggling = "true";
|
|
8204
|
+
try {
|
|
8205
|
+
if (expanding)
|
|
8206
|
+
await ensureVirtualSidebarDirLoaded(dir);
|
|
8207
|
+
li.classList.toggle("collapsed");
|
|
8208
|
+
updateIcon();
|
|
8209
|
+
if (li.classList.contains("collapsed"))
|
|
8210
|
+
STATE.collapsedDirs.add(dir.path);
|
|
8211
|
+
else
|
|
8212
|
+
STATE.collapsedDirs.delete(dir.path);
|
|
8213
|
+
localStorage.setItem("gdp:collapsed-dirs", JSON.stringify([...STATE.collapsedDirs]));
|
|
8214
|
+
rerenderVirtualSidebar();
|
|
8215
|
+
} finally {
|
|
8216
|
+
delete li.dataset.toggling;
|
|
8217
|
+
}
|
|
8118
8218
|
};
|
|
8119
8219
|
li.classList.toggle("collapsed", STATE.collapsedDirs.has(dir.path));
|
|
8120
8220
|
updateIcon();
|
|
@@ -8382,6 +8482,8 @@ ${frontmatter.yaml}
|
|
|
8382
8482
|
SIDEBAR_TREE_ROWS = [];
|
|
8383
8483
|
SIDEBAR_VISIBLE_ROWS = [];
|
|
8384
8484
|
SIDEBAR_ROW_BY_PATH = new Map;
|
|
8485
|
+
SIDEBAR_LAZY_LOADED_DIRS.clear();
|
|
8486
|
+
SIDEBAR_LAZY_LOADING_DIRS.clear();
|
|
8385
8487
|
STATE.files = files;
|
|
8386
8488
|
SIDEBAR_FILES = files;
|
|
8387
8489
|
SIDEBAR_ON_FILE_CLICK = onFileClick;
|
|
@@ -9851,6 +9953,14 @@ ${frontmatter.yaml}
|
|
|
9851
9953
|
function activateRepoSidebarPath(currentPath) {
|
|
9852
9954
|
markActive(currentPath, { reveal: true });
|
|
9853
9955
|
applyFilter();
|
|
9956
|
+
const row = SIDEBAR_ROW_BY_PATH.get(currentPath);
|
|
9957
|
+
if (row?.kind === "dir" && row.dir && shouldLazyLoadSidebarDir(row.dir))
|
|
9958
|
+
ensureVirtualSidebarDirLoaded(row.dir).then(() => {
|
|
9959
|
+
if (SIDEBAR_VIRTUAL_ACTIVE_PATH === currentPath) {
|
|
9960
|
+
rerenderVirtualSidebar();
|
|
9961
|
+
scrollVirtualSidebarPathIntoView(currentPath);
|
|
9962
|
+
}
|
|
9963
|
+
});
|
|
9854
9964
|
}
|
|
9855
9965
|
function createPlaceholder(f2) {
|
|
9856
9966
|
const card = document.createElement("div");
|
|
@@ -10208,6 +10318,10 @@ ${frontmatter.yaml}
|
|
|
10208
10318
|
for (const item of infoRows) {
|
|
10209
10319
|
attachExpandControls(item, file, ref, refPath);
|
|
10210
10320
|
}
|
|
10321
|
+
const trailingIndex = window.GdpExpandLogic.trailingExpandTargetIndex(infoRows.length);
|
|
10322
|
+
if (trailingIndex != null) {
|
|
10323
|
+
probeAndAttachTrailingExpandControls(infoRows[trailingIndex], file, ref, refPath);
|
|
10324
|
+
}
|
|
10211
10325
|
}
|
|
10212
10326
|
function attachExpandControls(item, file, ref, refPath) {
|
|
10213
10327
|
const { hunk, prevHunkEndNew, prevHunkEndOld } = item;
|
|
@@ -10349,7 +10463,10 @@ ${frontmatter.yaml}
|
|
|
10349
10463
|
requestAnimationFrame(syncHeight);
|
|
10350
10464
|
setTimeout(syncHeight, 100);
|
|
10351
10465
|
}
|
|
10352
|
-
function
|
|
10466
|
+
function attachTrailingExpandControls(item, file, ref, refPath) {
|
|
10467
|
+
const hasTrailingRow = (item.siblings || []).some((sib) => !!sib.tr.parentElement?.querySelector(".gdp-trailing-expand-row"));
|
|
10468
|
+
if (hasTrailingRow)
|
|
10469
|
+
return;
|
|
10353
10470
|
const STEP = 20;
|
|
10354
10471
|
let nextNewStart = nextNewLine(item.hunk);
|
|
10355
10472
|
let nextOldStart = nextOldLine(item.hunk);
|
|
@@ -10383,9 +10500,16 @@ ${frontmatter.yaml}
|
|
|
10383
10500
|
};
|
|
10384
10501
|
const fetchAndInsert = () => {
|
|
10385
10502
|
const range = window.GdpExpandLogic.trailingClickRange(nextNewStart, STEP);
|
|
10503
|
+
const myGen = SERVER_GENERATION;
|
|
10386
10504
|
setBusy(true);
|
|
10387
10505
|
const url = "/file_range?path=" + refPath + "&ref=" + encodeURIComponent(ref) + "&start=" + range.start + "&end=" + range.end;
|
|
10388
10506
|
trackLoad(fetch(url).then((r2) => r2.json())).then((data) => {
|
|
10507
|
+
if (myGen !== SERVER_GENERATION || data.generation && data.generation !== SERVER_GENERATION) {
|
|
10508
|
+
setBusy(false);
|
|
10509
|
+
return;
|
|
10510
|
+
}
|
|
10511
|
+
if (!item.tr.isConnected)
|
|
10512
|
+
return;
|
|
10389
10513
|
const lines = data?.lines || [];
|
|
10390
10514
|
if (!lines.length) {
|
|
10391
10515
|
rows.forEach((row) => {
|
|
@@ -10424,6 +10548,25 @@ ${frontmatter.yaml}
|
|
|
10424
10548
|
});
|
|
10425
10549
|
syncExpandRowHeights(rows.map((row) => row.tr), rows[0].tr);
|
|
10426
10550
|
}
|
|
10551
|
+
function probeAndAttachTrailingExpandControls(item, file, ref, refPath) {
|
|
10552
|
+
const start = nextNewLine(item.hunk);
|
|
10553
|
+
const myGen = SERVER_GENERATION;
|
|
10554
|
+
const url = "/file_range?path=" + refPath + "&ref=" + encodeURIComponent(ref) + "&start=" + start + "&end=" + start;
|
|
10555
|
+
trackLoad(fetch(url).then((r2) => r2.json())).then((data) => {
|
|
10556
|
+
if (myGen !== SERVER_GENERATION)
|
|
10557
|
+
return;
|
|
10558
|
+
if (data.generation && data.generation !== SERVER_GENERATION)
|
|
10559
|
+
return;
|
|
10560
|
+
if (!item.tr.isConnected)
|
|
10561
|
+
return;
|
|
10562
|
+
const hasTrailingRow = (item.siblings || []).some((sib) => !!sib.tr.parentElement?.querySelector(".gdp-trailing-expand-row"));
|
|
10563
|
+
if (hasTrailingRow)
|
|
10564
|
+
return;
|
|
10565
|
+
if (!window.GdpExpandLogic.shouldAttachTrailingExpand(data?.lines?.length || 0))
|
|
10566
|
+
return;
|
|
10567
|
+
attachTrailingExpandControls(item, file, ref, refPath);
|
|
10568
|
+
}).catch(() => {});
|
|
10569
|
+
}
|
|
10427
10570
|
function insertContextRows(targetTr, lines, newStart, oldStart, dir, sideIndex) {
|
|
10428
10571
|
const tbody = targetTr.parentElement;
|
|
10429
10572
|
if (!tbody)
|