@youtyan/code-viewer 0.1.15 → 0.1.17
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 +34 -8
- package/dist/code-viewer.js +2235 -0
- package/package.json +23 -18
- package/web/app.js +514 -5
- package/web/style.css +44 -3
- package/web-src/routes.ts +0 -148
- package/web-src/server/cache.ts +0 -64
- package/web-src/server/dev-assets.ts +0 -37
- package/web-src/server/dev.ts +0 -100
- package/web-src/server/git.ts +0 -483
- package/web-src/server/preview.ts +0 -985
- package/web-src/server/range.ts +0 -8
- package/web-src/server/runtime.d.ts +0 -51
- package/web-src/server/search.ts +0 -104
- package/web-src/types.ts +0 -136
package/web/app.js
CHANGED
|
@@ -378,6 +378,18 @@
|
|
|
378
378
|
{ action: "scroll-main-up", key: "k", scope: "main" },
|
|
379
379
|
{ action: "scroll-main-page-down", key: "d", scope: "main", ctrl: true },
|
|
380
380
|
{ action: "scroll-main-page-up", key: "u", scope: "main", ctrl: true },
|
|
381
|
+
{ action: "scroll-main-page-down", key: "pagedown", scope: "main" },
|
|
382
|
+
{ action: "scroll-main-page-up", key: "pageup", scope: "main" },
|
|
383
|
+
{ action: "scroll-main-page-down", key: "pagedown", scope: "global" },
|
|
384
|
+
{ action: "scroll-main-page-up", key: "pageup", scope: "global" },
|
|
385
|
+
{ action: "scroll-main-page-down", key: "pagedown", scope: "sidebar" },
|
|
386
|
+
{ action: "scroll-main-page-up", key: "pageup", scope: "sidebar" },
|
|
387
|
+
{ action: "scroll-main-page-down", key: "arrowdown", scope: "main", ctrl: true },
|
|
388
|
+
{ action: "scroll-main-page-up", key: "arrowup", scope: "main", ctrl: true },
|
|
389
|
+
{ action: "scroll-main-page-down", key: "arrowdown", scope: "global", ctrl: true },
|
|
390
|
+
{ action: "scroll-main-page-up", key: "arrowup", scope: "global", ctrl: true },
|
|
391
|
+
{ action: "scroll-main-page-down", key: "arrowdown", scope: "sidebar", ctrl: true },
|
|
392
|
+
{ action: "scroll-main-page-up", key: "arrowup", scope: "sidebar", ctrl: true },
|
|
381
393
|
{ action: "tab-preview", key: "p", scope: "main", pendingG: true },
|
|
382
394
|
{ action: "tab-code", key: "c", scope: "main", pendingG: true },
|
|
383
395
|
{ action: "goto-top", key: "g", pendingG: true },
|
|
@@ -6772,6 +6784,7 @@
|
|
|
6772
6784
|
const DEFAULT_RANGE = { from: "HEAD", to: "worktree" };
|
|
6773
6785
|
const VIRTUAL_SOURCE_LINE_THRESHOLD = 3000;
|
|
6774
6786
|
const VIRTUAL_SOURCE_SIZE_THRESHOLD = 1024 * 1024;
|
|
6787
|
+
const VIRTUAL_SOURCE_PAGE_SIZE = 2000;
|
|
6775
6788
|
const VIRTUAL_SOURCE_ROW_HEIGHT = 20;
|
|
6776
6789
|
const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
|
|
6777
6790
|
let highlightLoadPromise = null;
|
|
@@ -6912,6 +6925,8 @@
|
|
|
6912
6925
|
const before = scroller.scrollTop;
|
|
6913
6926
|
if (edge === "center")
|
|
6914
6927
|
scroller.scrollTop = Math.max(0, top - Math.round(scroller.clientHeight / 2));
|
|
6928
|
+
else if (edge === "start")
|
|
6929
|
+
scroller.scrollTop = top;
|
|
6915
6930
|
else if (top < scroller.scrollTop)
|
|
6916
6931
|
scroller.scrollTop = top;
|
|
6917
6932
|
else if (bottom > scroller.scrollTop + scroller.clientHeight)
|
|
@@ -6944,7 +6959,7 @@
|
|
|
6944
6959
|
const delta = unit === "page" ? pageRows : 1;
|
|
6945
6960
|
cursor.line = Math.max(1, Math.min(total, cursor.line + direction * delta));
|
|
6946
6961
|
syncSourceCursorRows(target);
|
|
6947
|
-
scrollSourceCursorIntoView(cursor);
|
|
6962
|
+
scrollSourceCursorIntoView(cursor, unit === "page" ? "start" : "nearest");
|
|
6948
6963
|
return true;
|
|
6949
6964
|
}
|
|
6950
6965
|
function scrollMainPanel(direction, repeated = false, unit = "line") {
|
|
@@ -6959,6 +6974,31 @@
|
|
|
6959
6974
|
else
|
|
6960
6975
|
window.scrollBy({ top, behavior });
|
|
6961
6976
|
}
|
|
6977
|
+
let MAIN_SURFACE_FOCUS_SEQ = 0;
|
|
6978
|
+
function focusMainSurface() {
|
|
6979
|
+
const target = findMainScrollTarget();
|
|
6980
|
+
if (target?.matches("#content .gdp-source-virtual-scroller")) {
|
|
6981
|
+
target.focus({ preventScroll: true });
|
|
6982
|
+
setPanelFocusScope("main");
|
|
6983
|
+
return;
|
|
6984
|
+
}
|
|
6985
|
+
focusMainPanel();
|
|
6986
|
+
}
|
|
6987
|
+
function scheduleMainSurfaceFocus() {
|
|
6988
|
+
const seq = ++MAIN_SURFACE_FOCUS_SEQ;
|
|
6989
|
+
const apply = () => {
|
|
6990
|
+
if (seq !== MAIN_SURFACE_FOCUS_SEQ || PALETTE)
|
|
6991
|
+
return;
|
|
6992
|
+
if (isEditableKeyTarget(document.activeElement))
|
|
6993
|
+
return;
|
|
6994
|
+
focusMainSurface();
|
|
6995
|
+
};
|
|
6996
|
+
focusMainPanel();
|
|
6997
|
+
queueMicrotask(apply);
|
|
6998
|
+
requestAnimationFrame(apply);
|
|
6999
|
+
setTimeout(apply, 100);
|
|
7000
|
+
setTimeout(apply, 300);
|
|
7001
|
+
}
|
|
6962
7002
|
function scrollMainToEdge(edge) {
|
|
6963
7003
|
if (moveSourceCursor(edge === "bottom" ? 1 : -1, "edge", edge))
|
|
6964
7004
|
return;
|
|
@@ -7586,7 +7626,7 @@
|
|
|
7586
7626
|
children_omitted: dir.children_omitted,
|
|
7587
7627
|
children_omitted_reason: dir.children_omitted_reason
|
|
7588
7628
|
});
|
|
7589
|
-
|
|
7629
|
+
scheduleMainSurfaceFocus();
|
|
7590
7630
|
});
|
|
7591
7631
|
} else {
|
|
7592
7632
|
li.addEventListener("click", toggleDir);
|
|
@@ -7622,7 +7662,7 @@
|
|
|
7622
7662
|
onFileClick(f2);
|
|
7623
7663
|
else
|
|
7624
7664
|
scrollToFile(f2.path);
|
|
7625
|
-
|
|
7665
|
+
scheduleMainSurfaceFocus();
|
|
7626
7666
|
});
|
|
7627
7667
|
if (!onFileClick)
|
|
7628
7668
|
li.addEventListener("mouseenter", () => prefetchByPath(f2.path), { passive: true });
|
|
@@ -7655,7 +7695,7 @@
|
|
|
7655
7695
|
onFileClick(f2);
|
|
7656
7696
|
else
|
|
7657
7697
|
scrollToFile(f2.path);
|
|
7658
|
-
|
|
7698
|
+
scheduleMainSurfaceFocus();
|
|
7659
7699
|
});
|
|
7660
7700
|
if (!onFileClick)
|
|
7661
7701
|
li.addEventListener("mouseenter", () => prefetchByPath(f2.path), { passive: true });
|
|
@@ -9875,6 +9915,9 @@
|
|
|
9875
9915
|
url.searchParams.delete("virtual");
|
|
9876
9916
|
return url.pathname + url.search;
|
|
9877
9917
|
}
|
|
9918
|
+
function buildFileRangeUrl(target, start, end) {
|
|
9919
|
+
return "/file_range?path=" + encodeURIComponent(target.path) + "&ref=" + encodeURIComponent(target.ref || "worktree") + "&start=" + encodeURIComponent(String(start)) + "&end=" + encodeURIComponent(String(end));
|
|
9920
|
+
}
|
|
9878
9921
|
function currentSourceLineTarget(target) {
|
|
9879
9922
|
const routeTarget = sourceTargetFromRoute();
|
|
9880
9923
|
return sourceTargetsEqual(routeTarget, target) && STATE.route.screen === "file" ? STATE.route.line : undefined;
|
|
@@ -9938,6 +9981,161 @@
|
|
|
9938
9981
|
document.addEventListener("mouseup", () => {
|
|
9939
9982
|
SOURCE_LINE_DRAG = null;
|
|
9940
9983
|
});
|
|
9984
|
+
function virtualSourceSearchRanges(line, query) {
|
|
9985
|
+
const needle = query.toLowerCase();
|
|
9986
|
+
if (!needle)
|
|
9987
|
+
return [];
|
|
9988
|
+
const haystack = line.toLowerCase();
|
|
9989
|
+
const ranges = [];
|
|
9990
|
+
let cursor = 0;
|
|
9991
|
+
while (cursor <= haystack.length) {
|
|
9992
|
+
const index = haystack.indexOf(needle, cursor);
|
|
9993
|
+
if (index < 0)
|
|
9994
|
+
break;
|
|
9995
|
+
ranges.push({ start: index, end: index + query.length });
|
|
9996
|
+
cursor = Math.max(index + query.length, index + 1);
|
|
9997
|
+
}
|
|
9998
|
+
return ranges;
|
|
9999
|
+
}
|
|
10000
|
+
function collectVirtualSourceSearchMatches(lines, query, max = 5000) {
|
|
10001
|
+
const matches = [];
|
|
10002
|
+
for (let index = 0;index < lines.length && matches.length < max; index++) {
|
|
10003
|
+
for (const range of virtualSourceSearchRanges(lines[index] || "", query)) {
|
|
10004
|
+
matches.push({ line: index + 1, start: range.start, end: range.end });
|
|
10005
|
+
if (matches.length >= max)
|
|
10006
|
+
break;
|
|
10007
|
+
}
|
|
10008
|
+
}
|
|
10009
|
+
return matches;
|
|
10010
|
+
}
|
|
10011
|
+
function appendVirtualSourceLineCode(code2, line, query, activeRange, lineNumber) {
|
|
10012
|
+
const ranges = virtualSourceSearchRanges(line, query);
|
|
10013
|
+
if (!ranges.length)
|
|
10014
|
+
return false;
|
|
10015
|
+
let cursor = 0;
|
|
10016
|
+
for (const range of ranges) {
|
|
10017
|
+
if (range.start > cursor)
|
|
10018
|
+
code2.appendChild(document.createTextNode(line.slice(cursor, range.start)));
|
|
10019
|
+
const mark = document.createElement("mark");
|
|
10020
|
+
const active = !!activeRange && activeRange.line === lineNumber && activeRange.start === range.start && activeRange.end === range.end;
|
|
10021
|
+
mark.className = active ? "gdp-source-virtual-search-hit active" : "gdp-source-virtual-search-hit";
|
|
10022
|
+
mark.textContent = line.slice(range.start, range.end);
|
|
10023
|
+
code2.appendChild(mark);
|
|
10024
|
+
cursor = range.end;
|
|
10025
|
+
}
|
|
10026
|
+
if (cursor < line.length)
|
|
10027
|
+
code2.appendChild(document.createTextNode(line.slice(cursor)));
|
|
10028
|
+
return true;
|
|
10029
|
+
}
|
|
10030
|
+
function createVirtualSourceSearch(wrap, scroller, findMatches, renderFn) {
|
|
10031
|
+
const bar = document.createElement("div");
|
|
10032
|
+
bar.className = "gdp-source-virtual-search";
|
|
10033
|
+
const input = document.createElement("input");
|
|
10034
|
+
input.type = "search";
|
|
10035
|
+
input.placeholder = "Find in file";
|
|
10036
|
+
input.autocomplete = "off";
|
|
10037
|
+
input.spellcheck = false;
|
|
10038
|
+
const count = document.createElement("span");
|
|
10039
|
+
count.className = "gdp-source-virtual-search-count";
|
|
10040
|
+
const previous = document.createElement("button");
|
|
10041
|
+
previous.type = "button";
|
|
10042
|
+
previous.textContent = "Prev";
|
|
10043
|
+
const next = document.createElement("button");
|
|
10044
|
+
next.type = "button";
|
|
10045
|
+
next.textContent = "Next";
|
|
10046
|
+
const close = document.createElement("button");
|
|
10047
|
+
close.type = "button";
|
|
10048
|
+
close.textContent = "Close";
|
|
10049
|
+
bar.append(input, count, previous, next, close);
|
|
10050
|
+
wrap.querySelector(".gdp-source-virtual-info")?.appendChild(bar);
|
|
10051
|
+
bar.hidden = true;
|
|
10052
|
+
let matches = [];
|
|
10053
|
+
let active = -1;
|
|
10054
|
+
let debounce = 0;
|
|
10055
|
+
let searchVersion = 0;
|
|
10056
|
+
const hide = () => {
|
|
10057
|
+
bar.hidden = true;
|
|
10058
|
+
renderFn();
|
|
10059
|
+
scroller.focus({ preventScroll: true });
|
|
10060
|
+
};
|
|
10061
|
+
const sync = () => {
|
|
10062
|
+
const query = input.value;
|
|
10063
|
+
const version = ++searchVersion;
|
|
10064
|
+
if (!query) {
|
|
10065
|
+
matches = [];
|
|
10066
|
+
active = -1;
|
|
10067
|
+
count.textContent = "";
|
|
10068
|
+
renderFn();
|
|
10069
|
+
return;
|
|
10070
|
+
}
|
|
10071
|
+
count.textContent = "Searching...";
|
|
10072
|
+
findMatches(query).then((nextMatches) => {
|
|
10073
|
+
if (version !== searchVersion)
|
|
10074
|
+
return;
|
|
10075
|
+
matches = nextMatches;
|
|
10076
|
+
active = matches.length ? Math.max(0, Math.min(active, matches.length - 1)) : -1;
|
|
10077
|
+
count.textContent = matches.length ? active + 1 + " / " + matches.length : "0 / 0";
|
|
10078
|
+
if (active >= 0)
|
|
10079
|
+
scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
|
|
10080
|
+
renderFn();
|
|
10081
|
+
}).catch(() => {
|
|
10082
|
+
if (version !== searchVersion)
|
|
10083
|
+
return;
|
|
10084
|
+
matches = [];
|
|
10085
|
+
active = -1;
|
|
10086
|
+
count.textContent = "Search failed";
|
|
10087
|
+
renderFn();
|
|
10088
|
+
});
|
|
10089
|
+
};
|
|
10090
|
+
const scheduleSync = () => {
|
|
10091
|
+
if (debounce)
|
|
10092
|
+
window.clearTimeout(debounce);
|
|
10093
|
+
debounce = window.setTimeout(sync, 120);
|
|
10094
|
+
};
|
|
10095
|
+
const move = (direction) => {
|
|
10096
|
+
if (!matches.length)
|
|
10097
|
+
return;
|
|
10098
|
+
active = (active + direction + matches.length) % matches.length;
|
|
10099
|
+
count.textContent = active + 1 + " / " + matches.length;
|
|
10100
|
+
scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
|
|
10101
|
+
renderFn();
|
|
10102
|
+
};
|
|
10103
|
+
input.addEventListener("input", () => {
|
|
10104
|
+
active = 0;
|
|
10105
|
+
scheduleSync();
|
|
10106
|
+
});
|
|
10107
|
+
input.addEventListener("keydown", (e2) => {
|
|
10108
|
+
if (e2.key === "Escape") {
|
|
10109
|
+
e2.preventDefault();
|
|
10110
|
+
hide();
|
|
10111
|
+
} else if (e2.key === "Enter") {
|
|
10112
|
+
e2.preventDefault();
|
|
10113
|
+
move(e2.shiftKey ? -1 : 1);
|
|
10114
|
+
}
|
|
10115
|
+
});
|
|
10116
|
+
previous.addEventListener("click", () => move(-1));
|
|
10117
|
+
next.addEventListener("click", () => move(1));
|
|
10118
|
+
close.addEventListener("click", hide);
|
|
10119
|
+
return {
|
|
10120
|
+
open: () => {
|
|
10121
|
+
bar.hidden = false;
|
|
10122
|
+
input.focus();
|
|
10123
|
+
input.select();
|
|
10124
|
+
sync();
|
|
10125
|
+
},
|
|
10126
|
+
query: () => bar.hidden ? "" : input.value,
|
|
10127
|
+
activeRange: () => active >= 0 ? matches[active] || null : null
|
|
10128
|
+
};
|
|
10129
|
+
}
|
|
10130
|
+
function openVirtualSourceSearchFromKeyboard(targetEl) {
|
|
10131
|
+
const active = targetEl?.closest("#content .gdp-source-virtual");
|
|
10132
|
+
const fallback = document.querySelector("#content .gdp-source-viewer.virtual .gdp-source-virtual:not([hidden])");
|
|
10133
|
+
const search = active?.__gdpVirtualSourceSearch || fallback?.__gdpVirtualSourceSearch;
|
|
10134
|
+
if (!search)
|
|
10135
|
+
return false;
|
|
10136
|
+
search.open();
|
|
10137
|
+
return true;
|
|
10138
|
+
}
|
|
9941
10139
|
function renderVirtualSource(target, textValue, lines, hljsRef, lang) {
|
|
9942
10140
|
const wrap = document.createElement("div");
|
|
9943
10141
|
wrap.className = "gdp-source-virtual";
|
|
@@ -9986,6 +10184,9 @@
|
|
|
9986
10184
|
info.append(badge, summary, actions);
|
|
9987
10185
|
const scroller = document.createElement("div");
|
|
9988
10186
|
scroller.className = "gdp-source-virtual-scroller";
|
|
10187
|
+
scroller.tabIndex = 0;
|
|
10188
|
+
scroller.setAttribute("role", "region");
|
|
10189
|
+
scroller.setAttribute("aria-label", target.path + " source code");
|
|
9989
10190
|
const spacer = document.createElement("div");
|
|
9990
10191
|
spacer.className = "gdp-source-virtual-spacer";
|
|
9991
10192
|
spacer.style.height = Math.max(1, lines.length * VIRTUAL_SOURCE_ROW_HEIGHT) + "px";
|
|
@@ -9997,6 +10198,7 @@
|
|
|
9997
10198
|
let raf = 0;
|
|
9998
10199
|
let renderedStart = -1;
|
|
9999
10200
|
let renderedEnd = -1;
|
|
10201
|
+
let search = null;
|
|
10000
10202
|
const render = () => {
|
|
10001
10203
|
raf = 0;
|
|
10002
10204
|
const viewportHeight = scroller.clientHeight || window.innerHeight;
|
|
@@ -10023,7 +10225,9 @@
|
|
|
10023
10225
|
const code2 = document.createElement("span");
|
|
10024
10226
|
code2.className = "gdp-source-virtual-line-code";
|
|
10025
10227
|
const line = lines[index] ?? "";
|
|
10026
|
-
|
|
10228
|
+
const searchQuery = search?.query() || "";
|
|
10229
|
+
const activeRange = search?.activeRange() || null;
|
|
10230
|
+
if (appendVirtualSourceLineCode(code2, line, searchQuery, activeRange, index + 1)) {} else if (hljsRef && hljsRef.highlight && lang && line.length <= VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH && (!hljsRef.getLanguage || hljsRef.getLanguage(lang))) {
|
|
10027
10231
|
try {
|
|
10028
10232
|
code2.innerHTML = hljsRef.highlight(line, { language: lang, ignoreIllegals: true }).value;
|
|
10029
10233
|
code2.classList.add("hljs");
|
|
@@ -10044,6 +10248,8 @@
|
|
|
10044
10248
|
};
|
|
10045
10249
|
scroller.__gdpRenderVirtualSource = render;
|
|
10046
10250
|
scroller.addEventListener("scroll", schedule, { passive: true });
|
|
10251
|
+
search = createVirtualSourceSearch(wrap, scroller, (query) => Promise.resolve(collectVirtualSourceSearchMatches(lines, query)), render);
|
|
10252
|
+
wrap.__gdpVirtualSourceSearch = search;
|
|
10047
10253
|
let resizeObserver = null;
|
|
10048
10254
|
resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
10049
10255
|
if (!scroller.isConnected) {
|
|
@@ -10058,6 +10264,253 @@
|
|
|
10058
10264
|
schedule();
|
|
10059
10265
|
return wrap;
|
|
10060
10266
|
}
|
|
10267
|
+
function renderPagedVirtualSource(target, size, initialStart, initialLines, initialComplete, initialTotal, hljsRef, lang, signal) {
|
|
10268
|
+
const wrap = document.createElement("div");
|
|
10269
|
+
wrap.className = "gdp-source-virtual";
|
|
10270
|
+
const info = document.createElement("div");
|
|
10271
|
+
info.className = "gdp-source-virtual-info";
|
|
10272
|
+
const badge = document.createElement("span");
|
|
10273
|
+
badge.className = "gdp-source-virtual-badge";
|
|
10274
|
+
badge.textContent = "Virtual mode";
|
|
10275
|
+
const summary = document.createElement("span");
|
|
10276
|
+
summary.className = "gdp-source-virtual-summary";
|
|
10277
|
+
const actions = document.createElement("span");
|
|
10278
|
+
actions.className = "gdp-source-virtual-actions";
|
|
10279
|
+
const raw = document.createElement("a");
|
|
10280
|
+
raw.className = "gdp-source-virtual-action";
|
|
10281
|
+
raw.href = buildRawFileUrl(target);
|
|
10282
|
+
raw.target = "_blank";
|
|
10283
|
+
raw.rel = "noreferrer";
|
|
10284
|
+
raw.textContent = "Open raw";
|
|
10285
|
+
const full = document.createElement("a");
|
|
10286
|
+
full.className = "gdp-source-virtual-action";
|
|
10287
|
+
full.href = buildCurrentFileRouteWithVirtualMode(target, "off");
|
|
10288
|
+
full.textContent = "Open full view";
|
|
10289
|
+
full.title = "Render every line without paged loading. This can be slow for large files.";
|
|
10290
|
+
full.addEventListener("click", (e2) => {
|
|
10291
|
+
e2.preventDefault();
|
|
10292
|
+
const url = new URL(full.href, window.location.origin);
|
|
10293
|
+
setRoute(parseRoute(url.pathname, url.search, currentRange()), true);
|
|
10294
|
+
renderStandaloneSource(target);
|
|
10295
|
+
});
|
|
10296
|
+
actions.append(raw, full);
|
|
10297
|
+
info.append(badge, summary, actions);
|
|
10298
|
+
const scroller = document.createElement("div");
|
|
10299
|
+
scroller.className = "gdp-source-virtual-scroller";
|
|
10300
|
+
scroller.tabIndex = 0;
|
|
10301
|
+
scroller.setAttribute("role", "region");
|
|
10302
|
+
scroller.setAttribute("aria-label", target.path + " source code");
|
|
10303
|
+
const spacer = document.createElement("div");
|
|
10304
|
+
spacer.className = "gdp-source-virtual-spacer";
|
|
10305
|
+
const windowEl = document.createElement("div");
|
|
10306
|
+
windowEl.className = "gdp-source-virtual-window";
|
|
10307
|
+
spacer.appendChild(windowEl);
|
|
10308
|
+
scroller.appendChild(spacer);
|
|
10309
|
+
wrap.append(info, scroller);
|
|
10310
|
+
const lines = new Map;
|
|
10311
|
+
const requestedPages = new Set;
|
|
10312
|
+
const failedPages = new Set;
|
|
10313
|
+
const targetLine = lineTargetStart(currentSourceLineTarget(target)) || 1;
|
|
10314
|
+
let complete = initialComplete;
|
|
10315
|
+
let totalRows = initialComplete ? Math.max(1, initialTotal) : Math.max(initialTotal || 1, initialStart + initialLines.length - 1, targetLine + VIRTUAL_SOURCE_PAGE_SIZE);
|
|
10316
|
+
initialLines.forEach((line, index) => lines.set(initialStart + index, line));
|
|
10317
|
+
requestedPages.add(Math.max(0, Math.floor((initialStart - 1) / VIRTUAL_SOURCE_PAGE_SIZE)));
|
|
10318
|
+
for (let line = initialStart;line < initialStart + initialLines.length; line += VIRTUAL_SOURCE_PAGE_SIZE) {
|
|
10319
|
+
requestedPages.add(Math.max(0, Math.floor((line - 1) / VIRTUAL_SOURCE_PAGE_SIZE)));
|
|
10320
|
+
}
|
|
10321
|
+
const updateTotals = () => {
|
|
10322
|
+
SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), totalRows);
|
|
10323
|
+
summary.textContent = (complete ? totalRows.toLocaleString() : lines.size.toLocaleString() + "+") + " lines loaded from " + formatBytes(size) + ". More rows load as you scroll.";
|
|
10324
|
+
spacer.style.height = Math.max(1, totalRows * VIRTUAL_SOURCE_ROW_HEIGHT) + "px";
|
|
10325
|
+
};
|
|
10326
|
+
const loadPage = (line) => {
|
|
10327
|
+
if (signal?.aborted || complete && line > totalRows)
|
|
10328
|
+
return;
|
|
10329
|
+
const page = Math.max(0, Math.floor((line - 1) / VIRTUAL_SOURCE_PAGE_SIZE));
|
|
10330
|
+
if (requestedPages.has(page))
|
|
10331
|
+
return;
|
|
10332
|
+
if (failedPages.has(page))
|
|
10333
|
+
return;
|
|
10334
|
+
requestedPages.add(page);
|
|
10335
|
+
const start = page * VIRTUAL_SOURCE_PAGE_SIZE + 1;
|
|
10336
|
+
const end = start + VIRTUAL_SOURCE_PAGE_SIZE - 1;
|
|
10337
|
+
trackLoad(fetch(buildFileRangeUrl(target, start, end), { signal }).then((res) => res.ok ? res.json() : null).then((data) => {
|
|
10338
|
+
if (!data || signal?.aborted)
|
|
10339
|
+
return;
|
|
10340
|
+
data.lines.forEach((lineValue, index) => lines.set(data.start + index, lineValue));
|
|
10341
|
+
totalRows = data.complete ? Math.max(1, data.total) : Math.max(totalRows, data.total, end + VIRTUAL_SOURCE_PAGE_SIZE);
|
|
10342
|
+
complete = data.complete === true;
|
|
10343
|
+
updateTotals();
|
|
10344
|
+
renderedStart = -1;
|
|
10345
|
+
renderedEnd = -1;
|
|
10346
|
+
render();
|
|
10347
|
+
}).catch((err) => {
|
|
10348
|
+
if (!isAbortError(err)) {
|
|
10349
|
+
failedPages.add(page);
|
|
10350
|
+
renderedStart = -1;
|
|
10351
|
+
renderedEnd = -1;
|
|
10352
|
+
schedule();
|
|
10353
|
+
}
|
|
10354
|
+
}));
|
|
10355
|
+
};
|
|
10356
|
+
let raf = 0;
|
|
10357
|
+
let renderedStart = -1;
|
|
10358
|
+
let renderedEnd = -1;
|
|
10359
|
+
let search = null;
|
|
10360
|
+
let searchController = null;
|
|
10361
|
+
const render = () => {
|
|
10362
|
+
raf = 0;
|
|
10363
|
+
const viewportHeight = scroller.clientHeight || window.innerHeight;
|
|
10364
|
+
const overscan = 20;
|
|
10365
|
+
const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
|
|
10366
|
+
const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
|
|
10367
|
+
if (start === renderedStart && end === renderedEnd)
|
|
10368
|
+
return;
|
|
10369
|
+
renderedStart = start;
|
|
10370
|
+
renderedEnd = end;
|
|
10371
|
+
windowEl.replaceChildren();
|
|
10372
|
+
windowEl.style.transform = "translateY(" + start * VIRTUAL_SOURCE_ROW_HEIGHT + "px)";
|
|
10373
|
+
const fragment = document.createDocumentFragment();
|
|
10374
|
+
for (let index = start;index < end; index++) {
|
|
10375
|
+
const lineNumber = index + 1;
|
|
10376
|
+
if (!lines.has(lineNumber))
|
|
10377
|
+
loadPage(lineNumber);
|
|
10378
|
+
const row = document.createElement("div");
|
|
10379
|
+
row.className = "gdp-source-virtual-row";
|
|
10380
|
+
row.dataset.line = String(lineNumber);
|
|
10381
|
+
row.classList.toggle("gdp-source-line-target", lineInSourceTarget(lineNumber, currentSourceLineTarget(target)));
|
|
10382
|
+
row.classList.toggle("gdp-source-cursor", sourceCursorMatches(target, lineNumber));
|
|
10383
|
+
const num = document.createElement("span");
|
|
10384
|
+
num.className = "gdp-source-virtual-line-number";
|
|
10385
|
+
num.textContent = String(lineNumber);
|
|
10386
|
+
bindSourceLineNumber(num, wrap, target, lineNumber);
|
|
10387
|
+
const code2 = document.createElement("span");
|
|
10388
|
+
code2.className = "gdp-source-virtual-line-code";
|
|
10389
|
+
const line = lines.get(lineNumber);
|
|
10390
|
+
if (line == null) {
|
|
10391
|
+
code2.textContent = "";
|
|
10392
|
+
} else if (appendVirtualSourceLineCode(code2, line, search?.query() || "", search?.activeRange() || null, lineNumber)) {} else if (hljsRef && hljsRef.highlight && lang && line.length <= VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH && (!hljsRef.getLanguage || hljsRef.getLanguage(lang))) {
|
|
10393
|
+
try {
|
|
10394
|
+
code2.innerHTML = hljsRef.highlight(line, { language: lang, ignoreIllegals: true }).value;
|
|
10395
|
+
code2.classList.add("hljs");
|
|
10396
|
+
} catch {
|
|
10397
|
+
code2.textContent = line;
|
|
10398
|
+
}
|
|
10399
|
+
} else {
|
|
10400
|
+
code2.textContent = line;
|
|
10401
|
+
}
|
|
10402
|
+
row.append(num, code2);
|
|
10403
|
+
fragment.appendChild(row);
|
|
10404
|
+
}
|
|
10405
|
+
windowEl.appendChild(fragment);
|
|
10406
|
+
if (!complete && totalRows - end < VIRTUAL_SOURCE_PAGE_SIZE) {
|
|
10407
|
+
totalRows += VIRTUAL_SOURCE_PAGE_SIZE;
|
|
10408
|
+
updateTotals();
|
|
10409
|
+
}
|
|
10410
|
+
};
|
|
10411
|
+
const schedule = () => {
|
|
10412
|
+
if (!raf)
|
|
10413
|
+
raf = requestAnimationFrame(render);
|
|
10414
|
+
};
|
|
10415
|
+
scroller.__gdpRenderVirtualSource = render;
|
|
10416
|
+
scroller.addEventListener("scroll", schedule, { passive: true });
|
|
10417
|
+
const findPagedMatches = async (query, matchSignal) => {
|
|
10418
|
+
const matches = [];
|
|
10419
|
+
let startLine = 1;
|
|
10420
|
+
let done = false;
|
|
10421
|
+
while (!done && matches.length < 5000) {
|
|
10422
|
+
const endLine = startLine + VIRTUAL_SOURCE_PAGE_SIZE - 1;
|
|
10423
|
+
const data = await trackLoad(fetch(buildFileRangeUrl(target, startLine, endLine), { signal: matchSignal }).then((res) => {
|
|
10424
|
+
if (!res.ok)
|
|
10425
|
+
throw new Error("file range failed");
|
|
10426
|
+
return res.json();
|
|
10427
|
+
}));
|
|
10428
|
+
if (matchSignal?.aborted)
|
|
10429
|
+
return [];
|
|
10430
|
+
data.lines.forEach((lineValue, index) => {
|
|
10431
|
+
const lineNumber = data.start + index;
|
|
10432
|
+
lines.set(lineNumber, lineValue);
|
|
10433
|
+
for (const range of virtualSourceSearchRanges(lineValue, query)) {
|
|
10434
|
+
matches.push({ line: lineNumber, start: range.start, end: range.end });
|
|
10435
|
+
if (matches.length >= 5000)
|
|
10436
|
+
break;
|
|
10437
|
+
}
|
|
10438
|
+
});
|
|
10439
|
+
totalRows = data.complete ? Math.max(1, data.total) : Math.max(totalRows, data.total, endLine + VIRTUAL_SOURCE_PAGE_SIZE);
|
|
10440
|
+
complete = data.complete === true;
|
|
10441
|
+
updateTotals();
|
|
10442
|
+
if (data.complete || !data.lines.length)
|
|
10443
|
+
done = true;
|
|
10444
|
+
else
|
|
10445
|
+
startLine = data.start + data.lines.length;
|
|
10446
|
+
}
|
|
10447
|
+
renderedStart = -1;
|
|
10448
|
+
renderedEnd = -1;
|
|
10449
|
+
schedule();
|
|
10450
|
+
return matches;
|
|
10451
|
+
};
|
|
10452
|
+
search = createVirtualSourceSearch(wrap, scroller, (query) => {
|
|
10453
|
+
searchController?.abort();
|
|
10454
|
+
searchController = new AbortController;
|
|
10455
|
+
return findPagedMatches(query, searchController.signal);
|
|
10456
|
+
}, render);
|
|
10457
|
+
wrap.__gdpVirtualSourceSearch = search;
|
|
10458
|
+
let resizeObserver = null;
|
|
10459
|
+
resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
10460
|
+
if (!scroller.isConnected) {
|
|
10461
|
+
resizeObserver?.disconnect();
|
|
10462
|
+
resizeObserver = null;
|
|
10463
|
+
return;
|
|
10464
|
+
}
|
|
10465
|
+
schedule();
|
|
10466
|
+
}) : null;
|
|
10467
|
+
resizeObserver?.observe(scroller);
|
|
10468
|
+
updateTotals();
|
|
10469
|
+
if (targetLine <= 1) {
|
|
10470
|
+
render();
|
|
10471
|
+
schedule();
|
|
10472
|
+
}
|
|
10473
|
+
return wrap;
|
|
10474
|
+
}
|
|
10475
|
+
async function renderPagedSourceText(card, target, size, signal) {
|
|
10476
|
+
const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
|
|
10477
|
+
const isStandalone = card.classList.contains("gdp-standalone-source");
|
|
10478
|
+
const view = document.createElement("div");
|
|
10479
|
+
view.className = "gdp-source-viewer virtual";
|
|
10480
|
+
const header = isStandalone ? null : document.createElement("div");
|
|
10481
|
+
if (header) {
|
|
10482
|
+
header.className = "gdp-source-meta";
|
|
10483
|
+
header.textContent = target.path + " @ " + target.ref;
|
|
10484
|
+
view.appendChild(header);
|
|
10485
|
+
}
|
|
10486
|
+
const lineTarget = lineTargetStart(currentSourceLineTarget(target)) || 1;
|
|
10487
|
+
const initialPage = Math.max(0, Math.floor((lineTarget - 1) / VIRTUAL_SOURCE_PAGE_SIZE));
|
|
10488
|
+
const initialStart = initialPage * VIRTUAL_SOURCE_PAGE_SIZE + 1;
|
|
10489
|
+
const initialEnd = initialStart + VIRTUAL_SOURCE_PAGE_SIZE - 1;
|
|
10490
|
+
const lang = inferLang(target.path);
|
|
10491
|
+
const hljsRef = STATE.syntaxHighlight ? await loadSyntaxHighlighter() : null;
|
|
10492
|
+
if (signal?.aborted)
|
|
10493
|
+
return false;
|
|
10494
|
+
const initial = await trackLoad(fetch(buildFileRangeUrl(target, initialStart, initialEnd), { signal }).then((res) => res.ok ? res.json() : null));
|
|
10495
|
+
if (!initial)
|
|
10496
|
+
return false;
|
|
10497
|
+
if (signal?.aborted)
|
|
10498
|
+
return false;
|
|
10499
|
+
const tabsHost = card.querySelector(".gdp-file-detail-tabs");
|
|
10500
|
+
if (tabsHost) {
|
|
10501
|
+
tabsHost.hidden = false;
|
|
10502
|
+
tabsHost.replaceChildren(createSourceTabs("code").tabs);
|
|
10503
|
+
}
|
|
10504
|
+
SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), Math.max(1, initial.total, lineTarget));
|
|
10505
|
+
resetSourceCursorForTarget(target, Math.max(1, initial.total, lineTarget));
|
|
10506
|
+
const virtualCode = renderPagedVirtualSource(target, size, initialStart, initial.lines, initial.complete === true, initial.total, hljsRef, lang, signal);
|
|
10507
|
+
view.appendChild(virtualCode);
|
|
10508
|
+
if (body)
|
|
10509
|
+
body.replaceWith(view);
|
|
10510
|
+
else
|
|
10511
|
+
card.appendChild(view);
|
|
10512
|
+
return true;
|
|
10513
|
+
}
|
|
10061
10514
|
function renderSourceMedia(card, target, mediaKind) {
|
|
10062
10515
|
const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
|
|
10063
10516
|
const isStandalone = card.classList.contains("gdp-standalone-source");
|
|
@@ -10259,6 +10712,19 @@
|
|
|
10259
10712
|
return;
|
|
10260
10713
|
}
|
|
10261
10714
|
if (displayKind === "text") {
|
|
10715
|
+
const meta = await loadRawFileInfo(target);
|
|
10716
|
+
if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
|
|
10717
|
+
return;
|
|
10718
|
+
if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
|
|
10719
|
+
const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
|
|
10720
|
+
if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
|
|
10721
|
+
return;
|
|
10722
|
+
if (!rendered2)
|
|
10723
|
+
return;
|
|
10724
|
+
scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
|
|
10725
|
+
finishSourceLoad(req);
|
|
10726
|
+
return;
|
|
10727
|
+
}
|
|
10262
10728
|
const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
|
|
10263
10729
|
if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
|
|
10264
10730
|
return;
|
|
@@ -10296,6 +10762,7 @@
|
|
|
10296
10762
|
if (virtualScroller) {
|
|
10297
10763
|
const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
|
|
10298
10764
|
virtualScroller.scrollTop = Math.max(0, (line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - Math.max(0, centeredOffset));
|
|
10765
|
+
virtualScroller.__gdpRenderVirtualSource?.();
|
|
10299
10766
|
return;
|
|
10300
10767
|
}
|
|
10301
10768
|
const row = card.querySelector('.gdp-source-table tr[data-line="' + String(line) + '"]');
|
|
@@ -11541,8 +12008,50 @@
|
|
|
11541
12008
|
}
|
|
11542
12009
|
return false;
|
|
11543
12010
|
}
|
|
12011
|
+
function handleVirtualSourcePagingKey(e2, targetEl) {
|
|
12012
|
+
if (e2.__gdpVirtualSourcePagingHandled)
|
|
12013
|
+
return true;
|
|
12014
|
+
if (e2.defaultPrevented || e2.isComposing || PALETTE || document.querySelector(".mkdp-lightbox"))
|
|
12015
|
+
return false;
|
|
12016
|
+
const editable = isEditableKeyTarget(targetEl);
|
|
12017
|
+
const inVirtualSearch = !!targetEl?.closest(".gdp-source-virtual-search");
|
|
12018
|
+
if (editable && !inVirtualSearch)
|
|
12019
|
+
return false;
|
|
12020
|
+
const key = e2.key.toLowerCase();
|
|
12021
|
+
if (e2.altKey || e2.metaKey)
|
|
12022
|
+
return false;
|
|
12023
|
+
const isPlainPageKey = (key === "pagedown" || key === "pageup") && !e2.ctrlKey && !e2.shiftKey;
|
|
12024
|
+
const isCtrlArrowKey = (key === "arrowdown" || key === "arrowup") && e2.ctrlKey && !e2.shiftKey;
|
|
12025
|
+
if (!isPlainPageKey && !isCtrlArrowKey)
|
|
12026
|
+
return false;
|
|
12027
|
+
const scroller = findMainScrollTarget();
|
|
12028
|
+
if (!scroller || !scroller.matches("#content .gdp-source-virtual-scroller"))
|
|
12029
|
+
return false;
|
|
12030
|
+
const pageDown = key === "pagedown" || key === "arrowdown";
|
|
12031
|
+
const pageUp = key === "pageup" || key === "arrowup";
|
|
12032
|
+
if (!pageDown && !pageUp)
|
|
12033
|
+
return false;
|
|
12034
|
+
e2.__gdpVirtualSourcePagingHandled = true;
|
|
12035
|
+
e2.preventDefault();
|
|
12036
|
+
e2.stopPropagation();
|
|
12037
|
+
scrollMainPanel(pageDown ? 1 : -1, e2.repeat, "page");
|
|
12038
|
+
focusMainSurface();
|
|
12039
|
+
return true;
|
|
12040
|
+
}
|
|
12041
|
+
function handleVirtualSourcePagingKeydown(e2) {
|
|
12042
|
+
handleVirtualSourcePagingKey(e2, e2.target);
|
|
12043
|
+
}
|
|
12044
|
+
document.addEventListener("keydown", handleVirtualSourcePagingKeydown, { capture: true });
|
|
11544
12045
|
document.addEventListener("keydown", (e2) => {
|
|
12046
|
+
if (e2.__gdpVirtualSourcePagingHandled)
|
|
12047
|
+
return;
|
|
11545
12048
|
const targetEl = e2.target;
|
|
12049
|
+
if ((e2.ctrlKey || e2.metaKey) && e2.key.toLowerCase() === "f" && !isEditableKeyTarget(targetEl)) {
|
|
12050
|
+
if (openVirtualSourceSearchFromKeyboard(targetEl)) {
|
|
12051
|
+
e2.preventDefault();
|
|
12052
|
+
return;
|
|
12053
|
+
}
|
|
12054
|
+
}
|
|
11546
12055
|
const scope = keymapScope(targetEl);
|
|
11547
12056
|
const action = resolveKeymapAction(e2, {
|
|
11548
12057
|
scope,
|