@youtyan/code-viewer 0.1.10 → 0.1.12
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/package.json +1 -1
- package/web/app.js +826 -29
- package/web/shiki.js +2 -1
- package/web/style.css +192 -0
- package/web-src/routes.ts +43 -7
- package/web-src/server/cache.ts +51 -0
- package/web-src/server/preview.ts +119 -9
- package/web-src/server/search.ts +101 -0
- package/web-src/types.ts +24 -0
package/web/app.js
CHANGED
|
@@ -131,6 +131,164 @@
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// web-src/fuzzy-search.ts
|
|
135
|
+
function basenameStart(path) {
|
|
136
|
+
const slash = path.lastIndexOf("/");
|
|
137
|
+
return slash < 0 ? 0 : slash + 1;
|
|
138
|
+
}
|
|
139
|
+
function isBoundary(path, index) {
|
|
140
|
+
if (index <= 0)
|
|
141
|
+
return true;
|
|
142
|
+
const prev = path[index - 1];
|
|
143
|
+
return prev === "/" || prev === "-" || prev === "_" || prev === "." || prev === " ";
|
|
144
|
+
}
|
|
145
|
+
function toRanges(indices) {
|
|
146
|
+
const ranges = [];
|
|
147
|
+
for (const index of indices) {
|
|
148
|
+
const last = ranges[ranges.length - 1];
|
|
149
|
+
if (last && last.end === index) {
|
|
150
|
+
last.end = index + 1;
|
|
151
|
+
} else {
|
|
152
|
+
ranges.push({ start: index, end: index + 1 });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return ranges;
|
|
156
|
+
}
|
|
157
|
+
function fuzzyMatchPath(query, path) {
|
|
158
|
+
const q = query.trim().toLowerCase();
|
|
159
|
+
if (!q)
|
|
160
|
+
return { score: 0, ranges: [] };
|
|
161
|
+
const lowerPath = path.toLowerCase();
|
|
162
|
+
const baseStart = basenameStart(path);
|
|
163
|
+
const indices = [];
|
|
164
|
+
let from = 0;
|
|
165
|
+
let score = 0;
|
|
166
|
+
for (const ch of q) {
|
|
167
|
+
const index = lowerPath.indexOf(ch, from);
|
|
168
|
+
if (index < 0)
|
|
169
|
+
return null;
|
|
170
|
+
indices.push(index);
|
|
171
|
+
score += 10;
|
|
172
|
+
if (index >= baseStart)
|
|
173
|
+
score += 8;
|
|
174
|
+
if (isBoundary(path, index))
|
|
175
|
+
score += 6;
|
|
176
|
+
const prev = indices[indices.length - 2];
|
|
177
|
+
if (prev != null && prev + 1 === index)
|
|
178
|
+
score += 12;
|
|
179
|
+
from = index + 1;
|
|
180
|
+
}
|
|
181
|
+
const first = indices[0] || 0;
|
|
182
|
+
score -= Math.min(first, 40);
|
|
183
|
+
if (indices[0] >= baseStart)
|
|
184
|
+
score += 20;
|
|
185
|
+
const basename = lowerPath.slice(baseStart);
|
|
186
|
+
if (basename.startsWith(q))
|
|
187
|
+
score += 30;
|
|
188
|
+
if (basename === q || basename.startsWith(q + "."))
|
|
189
|
+
score += 25;
|
|
190
|
+
if (lowerPath.endsWith(q))
|
|
191
|
+
score += 15;
|
|
192
|
+
return { score, ranges: toRanges(indices) };
|
|
193
|
+
}
|
|
194
|
+
function rankFuzzyPaths(query, items) {
|
|
195
|
+
return items.map((item) => {
|
|
196
|
+
const match = fuzzyMatchPath(query, item.path);
|
|
197
|
+
return match ? { item, score: match.score, ranges: match.ranges } : null;
|
|
198
|
+
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
199
|
+
}
|
|
200
|
+
function isGlobPathQuery(query) {
|
|
201
|
+
return /[*?]/.test(query.trim());
|
|
202
|
+
}
|
|
203
|
+
function escapeRegexChar(ch) {
|
|
204
|
+
return /[\\^$+?.()|{}]/.test(ch) ? "\\" + ch : ch;
|
|
205
|
+
}
|
|
206
|
+
function globToRegExp(query) {
|
|
207
|
+
const pattern = query.trim();
|
|
208
|
+
if (!pattern)
|
|
209
|
+
return null;
|
|
210
|
+
let source = "^";
|
|
211
|
+
for (let i = 0;i < pattern.length; i++) {
|
|
212
|
+
const ch = pattern[i];
|
|
213
|
+
if (ch === "*") {
|
|
214
|
+
if (pattern[i + 1] === "*") {
|
|
215
|
+
source += ".*";
|
|
216
|
+
i++;
|
|
217
|
+
} else {
|
|
218
|
+
source += "[^/]*";
|
|
219
|
+
}
|
|
220
|
+
} else if (ch === "?") {
|
|
221
|
+
source += "[^/]";
|
|
222
|
+
} else if (ch === "[") {
|
|
223
|
+
const close = pattern.indexOf("]", i + 1);
|
|
224
|
+
if (close < 0) {
|
|
225
|
+
source += "\\[";
|
|
226
|
+
} else {
|
|
227
|
+
const body = pattern.slice(i + 1, close).replace(/\\/g, "\\\\");
|
|
228
|
+
source += "[" + body + "]";
|
|
229
|
+
i = close;
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
source += escapeRegexChar(ch);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
source += "$";
|
|
236
|
+
try {
|
|
237
|
+
return new RegExp(source, "i");
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function globMatchPath(query, path) {
|
|
243
|
+
const regex = globToRegExp(query);
|
|
244
|
+
const baseStart = basenameStart(path);
|
|
245
|
+
const basename = path.slice(baseStart);
|
|
246
|
+
if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
247
|
+
return null;
|
|
248
|
+
const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
249
|
+
const ranges = [];
|
|
250
|
+
const lowerPath = path.toLowerCase();
|
|
251
|
+
for (const part of literal) {
|
|
252
|
+
const start = lowerPath.indexOf(part.toLowerCase());
|
|
253
|
+
if (start >= 0)
|
|
254
|
+
ranges.push({ start, end: start + part.length });
|
|
255
|
+
}
|
|
256
|
+
ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
257
|
+
const mergedRanges = [];
|
|
258
|
+
for (const range of ranges) {
|
|
259
|
+
const last = mergedRanges[mergedRanges.length - 1];
|
|
260
|
+
if (last && last.end >= range.start) {
|
|
261
|
+
last.end = Math.max(last.end, range.end);
|
|
262
|
+
} else {
|
|
263
|
+
mergedRanges.push({ ...range });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(query.replace(/^\*+/, "").toLowerCase()) ? 50 : 0);
|
|
267
|
+
return { score, ranges: mergedRanges };
|
|
268
|
+
}
|
|
269
|
+
function rankPathMatches(query, items) {
|
|
270
|
+
if (isGlobPathQuery(query)) {
|
|
271
|
+
return items.map((item) => {
|
|
272
|
+
const match = globMatchPath(query, item.path);
|
|
273
|
+
return match ? { item, score: match.score, ranges: match.ranges, mode: "glob" } : null;
|
|
274
|
+
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
275
|
+
}
|
|
276
|
+
return rankFuzzyPaths(query, items).map((item) => ({ ...item, mode: "fuzzy" }));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// web-src/search-palette.ts
|
|
280
|
+
var PALETTE_RESULT_LIMIT = 50;
|
|
281
|
+
function limitPaletteResults(items) {
|
|
282
|
+
return items.slice(0, PALETTE_RESULT_LIMIT);
|
|
283
|
+
}
|
|
284
|
+
function movePaletteSelection(index, count, direction) {
|
|
285
|
+
if (count <= 0)
|
|
286
|
+
return -1;
|
|
287
|
+
if (index < 0)
|
|
288
|
+
return direction > 0 ? 0 : count - 1;
|
|
289
|
+
return (index + direction + count) % count;
|
|
290
|
+
}
|
|
291
|
+
|
|
134
292
|
// web-src/catch-up.ts
|
|
135
293
|
function shouldCatchUpDiff(route) {
|
|
136
294
|
return route.screen !== "repo" && !(route.screen === "file" && route.view === "blob");
|
|
@@ -160,6 +318,24 @@
|
|
|
160
318
|
to: raw.slice(sep + 2) || fallback.to
|
|
161
319
|
};
|
|
162
320
|
}
|
|
321
|
+
function parseLineTarget(value) {
|
|
322
|
+
const raw = value || "";
|
|
323
|
+
const range = /^(\d+)-(\d+)$/.exec(raw);
|
|
324
|
+
if (range) {
|
|
325
|
+
const a = Number(range[1]);
|
|
326
|
+
const b = Number(range[2]);
|
|
327
|
+
const start = Math.min(a, b);
|
|
328
|
+
const end = Math.max(a, b);
|
|
329
|
+
if (start > 0)
|
|
330
|
+
return { start, end };
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const line = Number(raw);
|
|
334
|
+
return Number.isInteger(line) && line > 0 ? line : undefined;
|
|
335
|
+
}
|
|
336
|
+
function formatLineTarget(line) {
|
|
337
|
+
return typeof line === "number" ? String(line) : line.start + "-" + line.end;
|
|
338
|
+
}
|
|
163
339
|
function parseRoute(pathname, search, fallbackRange) {
|
|
164
340
|
const params = new URLSearchParams(search);
|
|
165
341
|
const legacyRange = parseLegacyRange(params.get("range"), fallbackRange);
|
|
@@ -178,14 +354,20 @@
|
|
|
178
354
|
};
|
|
179
355
|
case "/todif":
|
|
180
356
|
case "/todiff":
|
|
181
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
screen: "diff",
|
|
359
|
+
range,
|
|
360
|
+
...params.get("path") ? { path: params.get("path") || "" } : {},
|
|
361
|
+
...parseLineTarget(params.get("line")) ? { line: parseLineTarget(params.get("line")) } : {}
|
|
362
|
+
};
|
|
182
363
|
case "/file": {
|
|
183
364
|
const path = params.get("path") || "";
|
|
184
365
|
const target = params.get("target") || "";
|
|
185
366
|
const ref = target || params.get("ref") || "worktree";
|
|
367
|
+
const line = parseLineTarget(params.get("line"));
|
|
186
368
|
if (!path)
|
|
187
369
|
return { screen: "unknown", reason: "missing-path", rawPathname: pathname, rawSearch: search, range };
|
|
188
|
-
return { screen: "file", path, ref, range, view: target ? "blob" : "detail" };
|
|
370
|
+
return { screen: "file", path, ref, range, view: target ? "blob" : "detail", ...line ? { line } : {} };
|
|
189
371
|
}
|
|
190
372
|
default:
|
|
191
373
|
return { screen: "unknown", reason: "unknown-pathname", rawPathname: pathname, rawSearch: search, range };
|
|
@@ -204,11 +386,11 @@
|
|
|
204
386
|
}
|
|
205
387
|
case "file":
|
|
206
388
|
if (route.view === "blob") {
|
|
207
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree");
|
|
389
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
208
390
|
}
|
|
209
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
391
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
210
392
|
case "diff":
|
|
211
|
-
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
393
|
+
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? "&path=" + encodeURIComponent(route.path) : "") + (route.line ? "&line=" + encodeURIComponent(formatLineTarget(route.line)) : "");
|
|
212
394
|
case "unknown":
|
|
213
395
|
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
214
396
|
default:
|
|
@@ -6393,6 +6575,7 @@
|
|
|
6393
6575
|
const VIRTUAL_SOURCE_ROW_HEIGHT = 20;
|
|
6394
6576
|
const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
|
|
6395
6577
|
let highlightLoadPromise = null;
|
|
6578
|
+
let sourceShikiLoadPromise = null;
|
|
6396
6579
|
let highlightConfigured = false;
|
|
6397
6580
|
let PROJECT_NAME = "";
|
|
6398
6581
|
let REPO_SIDEBAR_REF = null;
|
|
@@ -6498,6 +6681,100 @@
|
|
|
6498
6681
|
});
|
|
6499
6682
|
return highlightLoadPromise;
|
|
6500
6683
|
}
|
|
6684
|
+
const SOURCE_SHIKI_LANGS = Array.from(new Set([
|
|
6685
|
+
"bash",
|
|
6686
|
+
"bibtex",
|
|
6687
|
+
"c",
|
|
6688
|
+
"clojure",
|
|
6689
|
+
"cmake",
|
|
6690
|
+
"cpp",
|
|
6691
|
+
"csharp",
|
|
6692
|
+
"css",
|
|
6693
|
+
"dart",
|
|
6694
|
+
"diff",
|
|
6695
|
+
"dockerfile",
|
|
6696
|
+
"elixir",
|
|
6697
|
+
"erlang",
|
|
6698
|
+
"fortran",
|
|
6699
|
+
"go",
|
|
6700
|
+
"gradle",
|
|
6701
|
+
"graphql",
|
|
6702
|
+
"haskell",
|
|
6703
|
+
"html",
|
|
6704
|
+
"java",
|
|
6705
|
+
"javascript",
|
|
6706
|
+
"json",
|
|
6707
|
+
"julia",
|
|
6708
|
+
"kotlin",
|
|
6709
|
+
"lua",
|
|
6710
|
+
"make",
|
|
6711
|
+
"markdown",
|
|
6712
|
+
"nix",
|
|
6713
|
+
"ocaml",
|
|
6714
|
+
"perl",
|
|
6715
|
+
"php",
|
|
6716
|
+
"properties",
|
|
6717
|
+
"protobuf",
|
|
6718
|
+
"python",
|
|
6719
|
+
"r",
|
|
6720
|
+
"rst",
|
|
6721
|
+
"ruby",
|
|
6722
|
+
"rust",
|
|
6723
|
+
"scala",
|
|
6724
|
+
"scss",
|
|
6725
|
+
"sql",
|
|
6726
|
+
"swift",
|
|
6727
|
+
"terraform",
|
|
6728
|
+
"tex",
|
|
6729
|
+
"toml",
|
|
6730
|
+
"typescript",
|
|
6731
|
+
"vim",
|
|
6732
|
+
"vue",
|
|
6733
|
+
"xml",
|
|
6734
|
+
"yaml"
|
|
6735
|
+
]));
|
|
6736
|
+
const SOURCE_SHIKI_LANG_ALIASES = {
|
|
6737
|
+
makefile: "make",
|
|
6738
|
+
objectivec: "c",
|
|
6739
|
+
"objective-c": "c",
|
|
6740
|
+
"objective-cpp": "cpp",
|
|
6741
|
+
starlark: "python"
|
|
6742
|
+
};
|
|
6743
|
+
function normalizeSourceShikiLang(lang) {
|
|
6744
|
+
if (!lang)
|
|
6745
|
+
return null;
|
|
6746
|
+
return SOURCE_SHIKI_LANG_ALIASES[lang] || lang;
|
|
6747
|
+
}
|
|
6748
|
+
function loadSourceShikiHighlighter() {
|
|
6749
|
+
if (!sourceShikiLoadPromise) {
|
|
6750
|
+
sourceShikiLoadPromise = import("/shiki.js").then((mod) => {
|
|
6751
|
+
const typed = mod;
|
|
6752
|
+
const langs = typed.bundledLanguages ? SOURCE_SHIKI_LANGS.filter((lang) => !!typed.bundledLanguages?.[lang]) : SOURCE_SHIKI_LANGS;
|
|
6753
|
+
return typed.createHighlighter({
|
|
6754
|
+
themes: ["github-light", "github-dark"],
|
|
6755
|
+
langs
|
|
6756
|
+
});
|
|
6757
|
+
}).catch(() => null);
|
|
6758
|
+
}
|
|
6759
|
+
return sourceShikiLoadPromise;
|
|
6760
|
+
}
|
|
6761
|
+
function sourceShikiLines(textValue, lang, highlighter) {
|
|
6762
|
+
try {
|
|
6763
|
+
const html = highlighter.codeToHtml(textValue || " ", {
|
|
6764
|
+
lang,
|
|
6765
|
+
themes: { light: "github-light", dark: "github-dark" },
|
|
6766
|
+
defaultColor: false
|
|
6767
|
+
});
|
|
6768
|
+
const template = document.createElement("template");
|
|
6769
|
+
template.innerHTML = html;
|
|
6770
|
+
const renderedLines = Array.from(template.content.querySelectorAll(".line"));
|
|
6771
|
+
if (!renderedLines.length)
|
|
6772
|
+
return null;
|
|
6773
|
+
return renderedLines.map((line) => line.innerHTML || " ");
|
|
6774
|
+
} catch {
|
|
6775
|
+
return null;
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6501
6778
|
function rerenderLoadedDiffs() {
|
|
6502
6779
|
document.querySelectorAll(".gdp-file-shell.loaded").forEach((card) => {
|
|
6503
6780
|
const data = card._diffData;
|
|
@@ -6565,10 +6842,10 @@
|
|
|
6565
6842
|
}
|
|
6566
6843
|
function setFolderIcon(el, collapsed) {
|
|
6567
6844
|
const path = collapsed ? FOLDER_ICON_PATHS.closed : FOLDER_ICON_PATHS.open;
|
|
6568
|
-
el.innerHTML = '<svg class="octicon octicon-file-directory-' + (collapsed ? "fill" : "open-fill") + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"
|
|
6845
|
+
el.innerHTML = '<svg class="octicon octicon-file-directory-' + (collapsed ? "fill" : "open-fill") + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><path fill="currentColor" d="' + path + '"></path></svg>';
|
|
6569
6846
|
}
|
|
6570
6847
|
function setChevronIcon(el) {
|
|
6571
|
-
el.innerHTML = '<svg class="octicon octicon-chevron-down" viewBox="0 0 12 12" width="12" height="12" fill="currentColor" aria-hidden="true"
|
|
6848
|
+
el.innerHTML = '<svg class="octicon octicon-chevron-down" viewBox="0 0 12 12" width="12" height="12" fill="currentColor" aria-hidden="true"><path fill="currentColor" d="' + CHEVRON_DOWN_12_PATH + '"></path></svg>';
|
|
6572
6849
|
}
|
|
6573
6850
|
function iconSvg(className, paths) {
|
|
6574
6851
|
const pathList = Array.isArray(paths) ? paths : [paths];
|
|
@@ -6840,7 +7117,7 @@
|
|
|
6840
7117
|
if (meta.totals) {
|
|
6841
7118
|
const t2 = document.createElement("span");
|
|
6842
7119
|
t2.className = "num";
|
|
6843
|
-
t2.innerHTML = '<span class="add">+' + meta.totals.additions + "</span> " + '<span class="del">−' + meta.totals.deletions + "</span>
|
|
7120
|
+
t2.innerHTML = '<span class="add">+' + meta.totals.additions + "</span> " + '<span class="del">−' + meta.totals.deletions + "</span> <span>" + meta.totals.files + " files</span>";
|
|
6844
7121
|
el.appendChild(t2);
|
|
6845
7122
|
}
|
|
6846
7123
|
const u2 = document.createElement("span");
|
|
@@ -6859,7 +7136,41 @@
|
|
|
6859
7136
|
return;
|
|
6860
7137
|
enqueueLoad(f2, card, 5);
|
|
6861
7138
|
}
|
|
6862
|
-
function
|
|
7139
|
+
function clearDiffLineFocus() {
|
|
7140
|
+
document.querySelectorAll(".gdp-diff-line-target").forEach((row) => {
|
|
7141
|
+
row.classList.remove("gdp-diff-line-target");
|
|
7142
|
+
});
|
|
7143
|
+
}
|
|
7144
|
+
function diffRowLineNumber(row) {
|
|
7145
|
+
const newLine = row.querySelector(".line-num2, td.d2h-code-side-linenumber");
|
|
7146
|
+
const raw = (newLine?.textContent || "").trim();
|
|
7147
|
+
const line = Number(raw);
|
|
7148
|
+
return Number.isInteger(line) && line > 0 ? line : null;
|
|
7149
|
+
}
|
|
7150
|
+
function focusDiffLine(card, line) {
|
|
7151
|
+
const start = lineTargetStart(line);
|
|
7152
|
+
if (!start)
|
|
7153
|
+
return false;
|
|
7154
|
+
const rows = Array.from(card.querySelectorAll("table.d2h-diff-table tr"));
|
|
7155
|
+
const row = rows.find((candidate) => diffRowLineNumber(candidate) === start);
|
|
7156
|
+
if (!row)
|
|
7157
|
+
return false;
|
|
7158
|
+
clearDiffLineFocus();
|
|
7159
|
+
row.classList.add("gdp-diff-line-target");
|
|
7160
|
+
row.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
7161
|
+
return true;
|
|
7162
|
+
}
|
|
7163
|
+
function applyDiffRouteFocus(card) {
|
|
7164
|
+
if (STATE.route.screen !== "diff" || !STATE.route.path || !STATE.route.line)
|
|
7165
|
+
return false;
|
|
7166
|
+
if (card && card.dataset.path !== STATE.route.path)
|
|
7167
|
+
return false;
|
|
7168
|
+
const targetCard = card || document.querySelector(diffCardSelector(STATE.route.path));
|
|
7169
|
+
if (!targetCard)
|
|
7170
|
+
return false;
|
|
7171
|
+
return focusDiffLine(targetCard, STATE.route.line);
|
|
7172
|
+
}
|
|
7173
|
+
function scrollToFile(path, line) {
|
|
6863
7174
|
const card = document.querySelector(diffCardSelector(path));
|
|
6864
7175
|
if (!card)
|
|
6865
7176
|
return;
|
|
@@ -6875,7 +7186,9 @@
|
|
|
6875
7186
|
if (f2)
|
|
6876
7187
|
enqueueLoad(f2, card, 10);
|
|
6877
7188
|
}
|
|
6878
|
-
|
|
7189
|
+
if (!line || !focusDiffLine(card, line)) {
|
|
7190
|
+
card.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
7191
|
+
}
|
|
6879
7192
|
}
|
|
6880
7193
|
function markActive(path) {
|
|
6881
7194
|
STATE.activeFile = path;
|
|
@@ -7510,7 +7823,7 @@
|
|
|
7510
7823
|
}
|
|
7511
7824
|
const head = document.createElement("div");
|
|
7512
7825
|
head.className = "gdp-shell-header";
|
|
7513
|
-
head.innerHTML = '<span class="status-pill ' + escapeHtml2(f2.status || "M") + '">' + escapeHtml2(f2.status || "M") +
|
|
7826
|
+
head.innerHTML = '<span class="status-pill ' + escapeHtml2(f2.status || "M") + '">' + escapeHtml2(f2.status || "M") + '</span><span class="path">' + escapeHtml2(f2.display_path || f2.path) + '</span><span class="stats"><span class="a">+' + (f2.additions || 0) + "</span>" + '<span class="d">−' + (f2.deletions || 0) + '</span></span><span class="size-tag ' + escapeHtml2(f2.size_class || "") + '">' + escapeHtml2(f2.size_class || "") + "</span>" + '<span class="loading-indicator" hidden>loading…</span>';
|
|
7514
7827
|
card.appendChild(head);
|
|
7515
7828
|
const body = document.createElement("div");
|
|
7516
7829
|
body.className = "gdp-shell-body";
|
|
@@ -7948,7 +8261,7 @@
|
|
|
7948
8261
|
const button = document.createElement("button");
|
|
7949
8262
|
button.className = "gdp-expand-btn";
|
|
7950
8263
|
button.title = spec.title;
|
|
7951
|
-
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"
|
|
8264
|
+
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path fill="currentColor" d="' + EXPAND_ICON_PATHS[spec.direction] + '"/></svg>';
|
|
7952
8265
|
button.addEventListener("click", (e2) => {
|
|
7953
8266
|
e2.stopPropagation();
|
|
7954
8267
|
if (button.disabled)
|
|
@@ -8046,9 +8359,9 @@
|
|
|
8046
8359
|
const num = sideIndex === 0 ? oldStart + i2 : newStart + i2;
|
|
8047
8360
|
lnHtml = '<td class="d2h-code-side-linenumber d2h-cntx">' + num + "</td>";
|
|
8048
8361
|
} else {
|
|
8049
|
-
lnHtml = '<td class="d2h-code-linenumber d2h-cntx"
|
|
8362
|
+
lnHtml = '<td class="d2h-code-linenumber d2h-cntx"><div class="line-num1">' + (oldStart + i2) + '</div><div class="line-num2">' + (newStart + i2) + "</div></td>";
|
|
8050
8363
|
}
|
|
8051
|
-
tr.innerHTML = lnHtml + '<td class="d2h-cntx"
|
|
8364
|
+
tr.innerHTML = lnHtml + '<td class="d2h-cntx"><div class="' + (isSplit ? "d2h-code-side-line" : "d2h-code-line") + '"><span class="d2h-code-line-prefix"> </span><span class="d2h-code-line-ctn">' + escapeHtmlText(lines[i2]) + "</span></div></td>";
|
|
8052
8365
|
frag.appendChild(tr);
|
|
8053
8366
|
}
|
|
8054
8367
|
tbody.insertBefore(frag, anchor);
|
|
@@ -8562,12 +8875,14 @@
|
|
|
8562
8875
|
header.textContent = target.path + " @ " + target.ref;
|
|
8563
8876
|
}
|
|
8564
8877
|
const lang = inferLang(target.path);
|
|
8565
|
-
const
|
|
8878
|
+
const usesVirtualSource = shouldVirtualizeSource(textValue, lines) && !isVirtualSourceDisabled();
|
|
8879
|
+
const hljsRef = STATE.syntaxHighlight && usesVirtualSource ? await loadSyntaxHighlighter() : null;
|
|
8880
|
+
const sourceShikiRef = STATE.syntaxHighlight && !usesVirtualSource ? await loadSourceShikiHighlighter() : null;
|
|
8566
8881
|
if (signal?.aborted)
|
|
8567
8882
|
return false;
|
|
8568
8883
|
const previewable = isPreviewableSource(target.path);
|
|
8569
8884
|
const tabsHost = card.querySelector(".gdp-file-detail-tabs");
|
|
8570
|
-
if (
|
|
8885
|
+
if (usesVirtualSource) {
|
|
8571
8886
|
const virtualCode = renderVirtualSource(target, textValue, lines, hljsRef, lang);
|
|
8572
8887
|
if (previewable) {
|
|
8573
8888
|
const { tabs: tabs2, codeButton: codeButton2, previewButton: previewButton2 } = createSourceTabs("preview");
|
|
@@ -8623,23 +8938,24 @@
|
|
|
8623
8938
|
const table2 = document.createElement("table");
|
|
8624
8939
|
table2.className = "gdp-source-table";
|
|
8625
8940
|
const tbody = document.createElement("tbody");
|
|
8941
|
+
const sourceShikiLang = normalizeSourceShikiLang(lang);
|
|
8942
|
+
const shikiLines = sourceShikiRef && sourceShikiLang ? sourceShikiLines(textValue, sourceShikiLang, sourceShikiRef) : null;
|
|
8626
8943
|
for (let index = 0;index < lines.length; index++) {
|
|
8627
8944
|
if (signal?.aborted)
|
|
8628
8945
|
return false;
|
|
8629
8946
|
const line = lines[index];
|
|
8630
8947
|
const tr = document.createElement("tr");
|
|
8948
|
+
tr.dataset.line = String(index + 1);
|
|
8949
|
+
tr.classList.toggle("gdp-source-line-target", lineInSourceTarget(index + 1, currentSourceLineTarget(target)));
|
|
8631
8950
|
const num = document.createElement("td");
|
|
8632
8951
|
num.className = "gdp-source-line-number";
|
|
8633
8952
|
num.textContent = String(index + 1);
|
|
8953
|
+
bindSourceLineNumber(num, card, target, index + 1);
|
|
8634
8954
|
const code2 = document.createElement("td");
|
|
8635
8955
|
code2.className = "gdp-source-line-code";
|
|
8636
|
-
if (
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
code2.classList.add("hljs");
|
|
8640
|
-
} catch {
|
|
8641
|
-
code2.textContent = line || " ";
|
|
8642
|
-
}
|
|
8956
|
+
if (shikiLines && shikiLines[index] != null) {
|
|
8957
|
+
code2.innerHTML = shikiLines[index] || " ";
|
|
8958
|
+
code2.classList.add("shiki");
|
|
8643
8959
|
} else {
|
|
8644
8960
|
code2.textContent = line || " ";
|
|
8645
8961
|
}
|
|
@@ -8726,6 +9042,69 @@
|
|
|
8726
9042
|
url.searchParams.delete("virtual");
|
|
8727
9043
|
return url.pathname + url.search;
|
|
8728
9044
|
}
|
|
9045
|
+
function currentSourceLineTarget(target) {
|
|
9046
|
+
const routeTarget = sourceTargetFromRoute();
|
|
9047
|
+
return sourceTargetsEqual(routeTarget, target) && STATE.route.screen === "file" ? STATE.route.line : undefined;
|
|
9048
|
+
}
|
|
9049
|
+
function lineTargetStart(line) {
|
|
9050
|
+
if (!line)
|
|
9051
|
+
return;
|
|
9052
|
+
return typeof line === "number" ? line : line.start;
|
|
9053
|
+
}
|
|
9054
|
+
function lineInSourceTarget(lineNumber, target) {
|
|
9055
|
+
if (!target)
|
|
9056
|
+
return false;
|
|
9057
|
+
if (typeof target === "number")
|
|
9058
|
+
return lineNumber === target;
|
|
9059
|
+
return lineNumber >= target.start && lineNumber <= target.end;
|
|
9060
|
+
}
|
|
9061
|
+
let SOURCE_LINE_DRAG = null;
|
|
9062
|
+
function normalizeSourceLineSelection(start, end) {
|
|
9063
|
+
const a2 = Math.max(1, Math.floor(start));
|
|
9064
|
+
const b2 = Math.max(1, Math.floor(end));
|
|
9065
|
+
const from = Math.min(a2, b2);
|
|
9066
|
+
const to = Math.max(a2, b2);
|
|
9067
|
+
return from === to ? from : { start: from, end: to };
|
|
9068
|
+
}
|
|
9069
|
+
function setSourceLineRoute(target, line) {
|
|
9070
|
+
if (STATE.route.screen !== "file")
|
|
9071
|
+
return;
|
|
9072
|
+
setRoute({
|
|
9073
|
+
screen: "file",
|
|
9074
|
+
path: target.path,
|
|
9075
|
+
ref: target.ref,
|
|
9076
|
+
view: STATE.route.view,
|
|
9077
|
+
range: currentRange(),
|
|
9078
|
+
line
|
|
9079
|
+
}, true);
|
|
9080
|
+
}
|
|
9081
|
+
function syncRenderedSourceLineHighlights(card, target) {
|
|
9082
|
+
const lineTarget = currentSourceLineTarget(target);
|
|
9083
|
+
card.querySelectorAll("[data-line]").forEach((row) => {
|
|
9084
|
+
const line = Number(row.dataset.line || "0");
|
|
9085
|
+
row.classList.toggle("gdp-source-line-target", lineInSourceTarget(line, lineTarget));
|
|
9086
|
+
});
|
|
9087
|
+
}
|
|
9088
|
+
function updateSourceLineSelection(card, target, start, end) {
|
|
9089
|
+
setSourceLineRoute(target, normalizeSourceLineSelection(start, end));
|
|
9090
|
+
syncRenderedSourceLineHighlights(card, target);
|
|
9091
|
+
}
|
|
9092
|
+
function beginSourceLineSelection(event, card, target, line) {
|
|
9093
|
+
event.preventDefault();
|
|
9094
|
+
SOURCE_LINE_DRAG = { target, start: line };
|
|
9095
|
+
updateSourceLineSelection(card, target, line, line);
|
|
9096
|
+
}
|
|
9097
|
+
function bindSourceLineNumber(num, card, target, line) {
|
|
9098
|
+
num.addEventListener("mousedown", (e2) => beginSourceLineSelection(e2, card, target, line));
|
|
9099
|
+
num.addEventListener("mouseenter", () => {
|
|
9100
|
+
if (!SOURCE_LINE_DRAG || !sourceTargetsEqual(SOURCE_LINE_DRAG.target, target))
|
|
9101
|
+
return;
|
|
9102
|
+
updateSourceLineSelection(card, target, SOURCE_LINE_DRAG.start, line);
|
|
9103
|
+
});
|
|
9104
|
+
}
|
|
9105
|
+
document.addEventListener("mouseup", () => {
|
|
9106
|
+
SOURCE_LINE_DRAG = null;
|
|
9107
|
+
});
|
|
8729
9108
|
function renderVirtualSource(target, textValue, lines, hljsRef, lang) {
|
|
8730
9109
|
const wrap = document.createElement("div");
|
|
8731
9110
|
wrap.className = "gdp-source-virtual";
|
|
@@ -8764,7 +9143,8 @@
|
|
|
8764
9143
|
full.title = "Render every line without virtualization. This can be slow for large files.";
|
|
8765
9144
|
full.addEventListener("click", (e2) => {
|
|
8766
9145
|
e2.preventDefault();
|
|
8767
|
-
|
|
9146
|
+
const url = new URL(full.href, window.location.origin);
|
|
9147
|
+
setRoute(parseRoute(url.pathname, url.search, currentRange()), true);
|
|
8768
9148
|
renderStandaloneSource(target);
|
|
8769
9149
|
});
|
|
8770
9150
|
actions.append(copy, full);
|
|
@@ -8798,9 +9178,12 @@
|
|
|
8798
9178
|
for (let index = start;index < end; index++) {
|
|
8799
9179
|
const row = document.createElement("div");
|
|
8800
9180
|
row.className = "gdp-source-virtual-row";
|
|
9181
|
+
row.dataset.line = String(index + 1);
|
|
9182
|
+
row.classList.toggle("gdp-source-line-target", lineInSourceTarget(index + 1, currentSourceLineTarget(target)));
|
|
8801
9183
|
const num = document.createElement("span");
|
|
8802
9184
|
num.className = "gdp-source-virtual-line-number";
|
|
8803
9185
|
num.textContent = String(index + 1);
|
|
9186
|
+
bindSourceLineNumber(num, wrap, target, index + 1);
|
|
8804
9187
|
const code2 = document.createElement("span");
|
|
8805
9188
|
code2.className = "gdp-source-virtual-line-code";
|
|
8806
9189
|
const line = lines[index] ?? "";
|
|
@@ -9048,6 +9431,7 @@
|
|
|
9048
9431
|
return;
|
|
9049
9432
|
if (!rendered)
|
|
9050
9433
|
return;
|
|
9434
|
+
scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
|
|
9051
9435
|
finishSourceLoad(req);
|
|
9052
9436
|
}
|
|
9053
9437
|
} catch (err) {
|
|
@@ -9061,6 +9445,19 @@
|
|
|
9061
9445
|
renderSourceError(card, target, "Cannot load " + target.path + " at " + target.ref);
|
|
9062
9446
|
}
|
|
9063
9447
|
}
|
|
9448
|
+
function scrollStandaloneSourceLine(card, line) {
|
|
9449
|
+
if (!line || line < 1)
|
|
9450
|
+
return;
|
|
9451
|
+
const virtualScroller = card.querySelector(".gdp-source-virtual-scroller");
|
|
9452
|
+
if (virtualScroller) {
|
|
9453
|
+
const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
|
|
9454
|
+
virtualScroller.scrollTop = Math.max(0, (line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - Math.max(0, centeredOffset));
|
|
9455
|
+
return;
|
|
9456
|
+
}
|
|
9457
|
+
const row = card.querySelector('.gdp-source-table tr[data-line="' + String(line) + '"]');
|
|
9458
|
+
if (row)
|
|
9459
|
+
row.scrollIntoView({ block: "center" });
|
|
9460
|
+
}
|
|
9064
9461
|
function applySourceRouteToShell() {
|
|
9065
9462
|
const target = sourceTargetFromRoute();
|
|
9066
9463
|
setPageMode();
|
|
@@ -9249,6 +9646,7 @@
|
|
|
9249
9646
|
card.classList.add("loaded");
|
|
9250
9647
|
card.style.minHeight = "";
|
|
9251
9648
|
mountDiff(card, file, data);
|
|
9649
|
+
applyDiffRouteFocus(card);
|
|
9252
9650
|
card.style.containIntrinsicSize = Math.max(card.offsetHeight, file.estimated_height_px || 200) + "px";
|
|
9253
9651
|
applyViewedToCard(card, STATE.viewedFiles.has(file.path), true);
|
|
9254
9652
|
if (data.truncated && data.mode === "preview") {
|
|
@@ -9450,7 +9848,7 @@
|
|
|
9450
9848
|
leftHTML = mediaTag(path, "HEAD");
|
|
9451
9849
|
rightHTML = mediaTag(path, "worktree");
|
|
9452
9850
|
}
|
|
9453
|
-
container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML +
|
|
9851
|
+
container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML + '</div><div class="media-side"><div class="media-label add">After</div>' + rightHTML + "</div>";
|
|
9454
9852
|
body.replaceWith(container);
|
|
9455
9853
|
}
|
|
9456
9854
|
function setupScrollSpy() {
|
|
@@ -9680,10 +10078,409 @@
|
|
|
9680
10078
|
input.focus();
|
|
9681
10079
|
input.select();
|
|
9682
10080
|
}
|
|
10081
|
+
let PALETTE = null;
|
|
10082
|
+
const REPO_FILE_CACHE = new Map;
|
|
10083
|
+
function paletteSource() {
|
|
10084
|
+
if (STATE.route.screen === "diff")
|
|
10085
|
+
return "diff";
|
|
10086
|
+
if (STATE.route.screen === "file" && STATE.route.view !== "blob")
|
|
10087
|
+
return "diff";
|
|
10088
|
+
return "repo";
|
|
10089
|
+
}
|
|
10090
|
+
function paletteRef(source) {
|
|
10091
|
+
if (source === "diff")
|
|
10092
|
+
return STATE.to && STATE.to !== "worktree" ? STATE.to : "worktree";
|
|
10093
|
+
if (STATE.route.screen === "repo")
|
|
10094
|
+
return STATE.route.ref || "worktree";
|
|
10095
|
+
if (STATE.route.screen === "file")
|
|
10096
|
+
return STATE.route.ref || "worktree";
|
|
10097
|
+
return STATE.repoRef || "worktree";
|
|
10098
|
+
}
|
|
10099
|
+
function closeSearchPalette() {
|
|
10100
|
+
if (!PALETTE)
|
|
10101
|
+
return;
|
|
10102
|
+
PALETTE.controller?.abort();
|
|
10103
|
+
if (PALETTE.debounce)
|
|
10104
|
+
window.clearTimeout(PALETTE.debounce);
|
|
10105
|
+
PALETTE.root.remove();
|
|
10106
|
+
PALETTE = null;
|
|
10107
|
+
}
|
|
10108
|
+
function createPalette(mode) {
|
|
10109
|
+
closeSearchPalette();
|
|
10110
|
+
const root = document.createElement("div");
|
|
10111
|
+
root.className = "gdp-palette-backdrop";
|
|
10112
|
+
const dialog = document.createElement("div");
|
|
10113
|
+
dialog.className = "gdp-palette";
|
|
10114
|
+
dialog.setAttribute("role", "dialog");
|
|
10115
|
+
dialog.setAttribute("aria-modal", "true");
|
|
10116
|
+
const label = document.createElement("div");
|
|
10117
|
+
label.className = "gdp-palette-label";
|
|
10118
|
+
label.textContent = mode === "file" ? "Files" : "Grep";
|
|
10119
|
+
const input = document.createElement("input");
|
|
10120
|
+
input.className = "gdp-palette-input";
|
|
10121
|
+
input.type = "search";
|
|
10122
|
+
input.autocomplete = "off";
|
|
10123
|
+
input.spellcheck = false;
|
|
10124
|
+
input.placeholder = mode === "file" ? "Search files" : "Search text";
|
|
10125
|
+
input.setAttribute("role", "combobox");
|
|
10126
|
+
input.setAttribute("aria-expanded", "true");
|
|
10127
|
+
input.setAttribute("aria-controls", "gdp-palette-list");
|
|
10128
|
+
const status = document.createElement("div");
|
|
10129
|
+
status.className = "gdp-palette-status";
|
|
10130
|
+
const controls = document.createElement("div");
|
|
10131
|
+
controls.className = "gdp-palette-controls";
|
|
10132
|
+
const list2 = document.createElement("div");
|
|
10133
|
+
list2.id = "gdp-palette-list";
|
|
10134
|
+
list2.className = "gdp-palette-list";
|
|
10135
|
+
list2.setAttribute("role", "listbox");
|
|
10136
|
+
dialog.append(label, input, controls, status, list2);
|
|
10137
|
+
root.appendChild(dialog);
|
|
10138
|
+
document.body.appendChild(root);
|
|
10139
|
+
const state = {
|
|
10140
|
+
root,
|
|
10141
|
+
input,
|
|
10142
|
+
controls,
|
|
10143
|
+
list: list2,
|
|
10144
|
+
status,
|
|
10145
|
+
mode,
|
|
10146
|
+
grepRegex: false,
|
|
10147
|
+
selected: -1,
|
|
10148
|
+
items: [],
|
|
10149
|
+
composing: false,
|
|
10150
|
+
diffSnapshot: [...STATE.files]
|
|
10151
|
+
};
|
|
10152
|
+
PALETTE = state;
|
|
10153
|
+
root.addEventListener("mousedown", (e2) => {
|
|
10154
|
+
if (e2.target === root)
|
|
10155
|
+
closeSearchPalette();
|
|
10156
|
+
});
|
|
10157
|
+
input.addEventListener("compositionstart", () => {
|
|
10158
|
+
state.composing = true;
|
|
10159
|
+
});
|
|
10160
|
+
input.addEventListener("compositionend", () => {
|
|
10161
|
+
state.composing = false;
|
|
10162
|
+
});
|
|
10163
|
+
input.addEventListener("input", () => updatePaletteResults(state));
|
|
10164
|
+
input.addEventListener("keydown", (e2) => handlePaletteKeydown(e2, state));
|
|
10165
|
+
input.focus();
|
|
10166
|
+
updatePaletteResults(state);
|
|
10167
|
+
return state;
|
|
10168
|
+
}
|
|
10169
|
+
function renderPaletteControls(state) {
|
|
10170
|
+
state.controls.innerHTML = "";
|
|
10171
|
+
if (state.mode === "file") {
|
|
10172
|
+
const hint2 = document.createElement("span");
|
|
10173
|
+
hint2.className = "gdp-palette-mode-hint";
|
|
10174
|
+
hint2.textContent = isGlobPathQuery(state.input.value) ? "Glob: * ? []" : "Fuzzy path search";
|
|
10175
|
+
state.controls.appendChild(hint2);
|
|
10176
|
+
return;
|
|
10177
|
+
}
|
|
10178
|
+
const plain = document.createElement("button");
|
|
10179
|
+
plain.type = "button";
|
|
10180
|
+
plain.className = "gdp-palette-mode-button";
|
|
10181
|
+
plain.setAttribute("aria-pressed", String(!state.grepRegex));
|
|
10182
|
+
plain.textContent = "Plain";
|
|
10183
|
+
plain.addEventListener("mousedown", (e2) => {
|
|
10184
|
+
e2.preventDefault();
|
|
10185
|
+
state.grepRegex = false;
|
|
10186
|
+
renderPaletteControls(state);
|
|
10187
|
+
updatePaletteResults(state);
|
|
10188
|
+
state.input.focus();
|
|
10189
|
+
});
|
|
10190
|
+
const regex = document.createElement("button");
|
|
10191
|
+
regex.type = "button";
|
|
10192
|
+
regex.className = "gdp-palette-mode-button";
|
|
10193
|
+
regex.setAttribute("aria-pressed", String(state.grepRegex));
|
|
10194
|
+
regex.textContent = ".* Regex";
|
|
10195
|
+
regex.title = "Alt+R";
|
|
10196
|
+
regex.addEventListener("mousedown", (e2) => {
|
|
10197
|
+
e2.preventDefault();
|
|
10198
|
+
state.grepRegex = true;
|
|
10199
|
+
renderPaletteControls(state);
|
|
10200
|
+
updatePaletteResults(state);
|
|
10201
|
+
state.input.focus();
|
|
10202
|
+
});
|
|
10203
|
+
const hint = document.createElement("span");
|
|
10204
|
+
hint.className = "gdp-palette-mode-hint";
|
|
10205
|
+
hint.textContent = "Alt+R toggles regex";
|
|
10206
|
+
state.controls.append(plain, regex, hint);
|
|
10207
|
+
}
|
|
10208
|
+
function regexQueryIsValid(query) {
|
|
10209
|
+
try {
|
|
10210
|
+
new RegExp(query);
|
|
10211
|
+
return true;
|
|
10212
|
+
} catch {
|
|
10213
|
+
return false;
|
|
10214
|
+
}
|
|
10215
|
+
}
|
|
10216
|
+
function appendHighlightedPath(parent, path, ranges) {
|
|
10217
|
+
let cursor = 0;
|
|
10218
|
+
for (const range of ranges) {
|
|
10219
|
+
if (range.start > cursor)
|
|
10220
|
+
parent.appendChild(document.createTextNode(path.slice(cursor, range.start)));
|
|
10221
|
+
const mark = document.createElement("mark");
|
|
10222
|
+
mark.textContent = path.slice(range.start, range.end);
|
|
10223
|
+
parent.appendChild(mark);
|
|
10224
|
+
cursor = range.end;
|
|
10225
|
+
}
|
|
10226
|
+
if (cursor < path.length)
|
|
10227
|
+
parent.appendChild(document.createTextNode(path.slice(cursor)));
|
|
10228
|
+
}
|
|
10229
|
+
function renderPalette(state) {
|
|
10230
|
+
state.list.innerHTML = "";
|
|
10231
|
+
state.items.forEach((item, index) => {
|
|
10232
|
+
const row = document.createElement("button");
|
|
10233
|
+
row.type = "button";
|
|
10234
|
+
row.id = "gdp-palette-item-" + index;
|
|
10235
|
+
row.className = "gdp-palette-row";
|
|
10236
|
+
row.setAttribute("role", "option");
|
|
10237
|
+
row.setAttribute("aria-selected", index === state.selected ? "true" : "false");
|
|
10238
|
+
const title = document.createElement("span");
|
|
10239
|
+
title.className = "gdp-palette-row-title";
|
|
10240
|
+
const detail = document.createElement("span");
|
|
10241
|
+
detail.className = "gdp-palette-row-detail";
|
|
10242
|
+
if (item.kind === "file") {
|
|
10243
|
+
title.textContent = item.path.split("/").pop() || item.path;
|
|
10244
|
+
appendHighlightedPath(detail, item.displayPath, item.ranges);
|
|
10245
|
+
if (item.old_path && item.displayPath !== item.old_path) {
|
|
10246
|
+
detail.appendChild(document.createTextNode(" " + item.old_path));
|
|
10247
|
+
}
|
|
10248
|
+
} else {
|
|
10249
|
+
title.textContent = item.path + ":" + item.line;
|
|
10250
|
+
detail.textContent = item.preview;
|
|
10251
|
+
}
|
|
10252
|
+
row.append(title, detail);
|
|
10253
|
+
row.addEventListener("mouseenter", () => {
|
|
10254
|
+
state.selected = index;
|
|
10255
|
+
syncPaletteSelection(state);
|
|
10256
|
+
});
|
|
10257
|
+
row.addEventListener("mousedown", (e2) => {
|
|
10258
|
+
e2.preventDefault();
|
|
10259
|
+
state.selected = index;
|
|
10260
|
+
selectPaletteItem(state);
|
|
10261
|
+
});
|
|
10262
|
+
state.list.appendChild(row);
|
|
10263
|
+
});
|
|
10264
|
+
syncPaletteSelection(state);
|
|
10265
|
+
}
|
|
10266
|
+
function syncPaletteSelection(state) {
|
|
10267
|
+
state.input.setAttribute("aria-activedescendant", state.selected >= 0 ? "gdp-palette-item-" + state.selected : "");
|
|
10268
|
+
state.list.querySelectorAll(".gdp-palette-row").forEach((row, index) => {
|
|
10269
|
+
row.setAttribute("aria-selected", index === state.selected ? "true" : "false");
|
|
10270
|
+
if (index === state.selected)
|
|
10271
|
+
row.scrollIntoView({ block: "nearest" });
|
|
10272
|
+
});
|
|
10273
|
+
}
|
|
10274
|
+
async function repoPaletteFiles(ref) {
|
|
10275
|
+
const cached = REPO_FILE_CACHE.get(ref);
|
|
10276
|
+
if (cached && cached.generation === SERVER_GENERATION)
|
|
10277
|
+
return cached;
|
|
10278
|
+
const params = new URLSearchParams;
|
|
10279
|
+
params.set("ref", ref);
|
|
10280
|
+
const res = await trackLoad(fetch("/_files?" + params.toString()).then((r2) => {
|
|
10281
|
+
if (!r2.ok)
|
|
10282
|
+
throw new Error("failed to load files");
|
|
10283
|
+
return r2.json();
|
|
10284
|
+
}));
|
|
10285
|
+
REPO_FILE_CACHE.set(ref, res);
|
|
10286
|
+
return res;
|
|
10287
|
+
}
|
|
10288
|
+
function diffFilePaletteItems(state, query) {
|
|
10289
|
+
const matchPath = isGlobPathQuery(query) ? globMatchPath : fuzzyMatchPath;
|
|
10290
|
+
const candidates = state.diffSnapshot.map((file) => {
|
|
10291
|
+
const current = matchPath(query, file.path);
|
|
10292
|
+
const old = file.old_path ? matchPath(query, file.old_path) : null;
|
|
10293
|
+
const best = old && (!current || old.score > current.score) ? { match: old, displayPath: file.old_path || file.path } : current ? { match: current, displayPath: file.path } : null;
|
|
10294
|
+
return best ? { file, ...best } : null;
|
|
10295
|
+
}).filter((item) => item !== null).sort((a2, b2) => b2.match.score - a2.match.score || a2.file.path.localeCompare(b2.file.path));
|
|
10296
|
+
return limitPaletteResults(candidates).map((candidate) => ({
|
|
10297
|
+
kind: "file",
|
|
10298
|
+
path: candidate.file.path,
|
|
10299
|
+
old_path: candidate.file.old_path,
|
|
10300
|
+
displayPath: candidate.displayPath,
|
|
10301
|
+
ref: paletteRef("diff"),
|
|
10302
|
+
targetPath: fileSourceTarget(candidate.file).path,
|
|
10303
|
+
targetRef: fileSourceTarget(candidate.file).ref,
|
|
10304
|
+
source: "diff",
|
|
10305
|
+
ranges: candidate.match.ranges
|
|
10306
|
+
}));
|
|
10307
|
+
}
|
|
10308
|
+
async function updateFilePalette(state, query) {
|
|
10309
|
+
renderPaletteControls(state);
|
|
10310
|
+
const source = paletteSource();
|
|
10311
|
+
if (!query.trim()) {
|
|
10312
|
+
const base2 = source === "diff" ? state.diffSnapshot.map((file) => {
|
|
10313
|
+
const target = fileSourceTarget(file);
|
|
10314
|
+
return { kind: "file", path: file.path, old_path: file.old_path, displayPath: file.path, ref: paletteRef(source), targetPath: target.path, targetRef: target.ref, source, ranges: [] };
|
|
10315
|
+
}) : [];
|
|
10316
|
+
state.items = limitPaletteResults(base2);
|
|
10317
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10318
|
+
state.status.textContent = source === "diff" ? state.diffSnapshot.length + " diff files" : "Type to search repository files";
|
|
10319
|
+
renderPalette(state);
|
|
10320
|
+
return;
|
|
10321
|
+
}
|
|
10322
|
+
if (source === "diff") {
|
|
10323
|
+
state.items = diffFilePaletteItems(state, query);
|
|
10324
|
+
} else {
|
|
10325
|
+
state.status.textContent = "Loading files...";
|
|
10326
|
+
const ref = paletteRef(source);
|
|
10327
|
+
const response = await repoPaletteFiles(ref);
|
|
10328
|
+
if (PALETTE !== state || state.input.value !== query)
|
|
10329
|
+
return;
|
|
10330
|
+
state.items = limitPaletteResults(rankPathMatches(query, response.files)).map((match2) => ({
|
|
10331
|
+
kind: "file",
|
|
10332
|
+
path: match2.item.path,
|
|
10333
|
+
displayPath: match2.item.path,
|
|
10334
|
+
ref,
|
|
10335
|
+
source,
|
|
10336
|
+
ranges: match2.ranges
|
|
10337
|
+
}));
|
|
10338
|
+
}
|
|
10339
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10340
|
+
state.status.textContent = state.items.length ? state.items.length + " results" : "No results";
|
|
10341
|
+
renderPalette(state);
|
|
10342
|
+
}
|
|
10343
|
+
function updateGrepPalette(state, query) {
|
|
10344
|
+
renderPaletteControls(state);
|
|
10345
|
+
state.controller?.abort();
|
|
10346
|
+
if (state.debounce)
|
|
10347
|
+
window.clearTimeout(state.debounce);
|
|
10348
|
+
if (!query.trim()) {
|
|
10349
|
+
state.items = [];
|
|
10350
|
+
state.selected = -1;
|
|
10351
|
+
state.status.textContent = "Type to grep";
|
|
10352
|
+
renderPalette(state);
|
|
10353
|
+
return;
|
|
10354
|
+
}
|
|
10355
|
+
if (state.grepRegex && !regexQueryIsValid(query)) {
|
|
10356
|
+
state.controller?.abort();
|
|
10357
|
+
state.items = [];
|
|
10358
|
+
state.selected = -1;
|
|
10359
|
+
state.status.textContent = "Invalid regular expression";
|
|
10360
|
+
renderPalette(state);
|
|
10361
|
+
return;
|
|
10362
|
+
}
|
|
10363
|
+
state.status.textContent = "Searching...";
|
|
10364
|
+
state.debounce = window.setTimeout(() => {
|
|
10365
|
+
const source = paletteSource();
|
|
10366
|
+
const ref = paletteRef(source);
|
|
10367
|
+
const params = new URLSearchParams;
|
|
10368
|
+
params.set("ref", ref);
|
|
10369
|
+
params.set("q", query);
|
|
10370
|
+
params.set("max", "200");
|
|
10371
|
+
if (state.grepRegex)
|
|
10372
|
+
params.set("regex", "1");
|
|
10373
|
+
if (source === "diff") {
|
|
10374
|
+
for (const file of state.diffSnapshot)
|
|
10375
|
+
params.append("path", file.path);
|
|
10376
|
+
}
|
|
10377
|
+
const controller = new AbortController;
|
|
10378
|
+
state.controller = controller;
|
|
10379
|
+
trackLoad(fetch("/_grep?" + params.toString(), { signal: controller.signal }).then((r2) => {
|
|
10380
|
+
if (!r2.ok)
|
|
10381
|
+
throw new Error("grep failed");
|
|
10382
|
+
return r2.json();
|
|
10383
|
+
})).then((response) => {
|
|
10384
|
+
if (PALETTE !== state || controller.signal.aborted)
|
|
10385
|
+
return;
|
|
10386
|
+
state.items = limitPaletteResults(response.matches.map((match2) => ({
|
|
10387
|
+
kind: "grep",
|
|
10388
|
+
path: match2.path,
|
|
10389
|
+
line: match2.line,
|
|
10390
|
+
column: match2.column,
|
|
10391
|
+
preview: match2.preview,
|
|
10392
|
+
ref,
|
|
10393
|
+
source
|
|
10394
|
+
})));
|
|
10395
|
+
state.selected = state.items.length ? 0 : -1;
|
|
10396
|
+
state.status.textContent = response.engine + (state.grepRegex ? " regex" : " plain") + (response.truncated ? " truncated" : "") + " - " + state.items.length + " results";
|
|
10397
|
+
renderPalette(state);
|
|
10398
|
+
}).catch((err) => {
|
|
10399
|
+
if (isAbortError(err))
|
|
10400
|
+
return;
|
|
10401
|
+
state.status.textContent = "Search failed";
|
|
10402
|
+
});
|
|
10403
|
+
}, 80);
|
|
10404
|
+
}
|
|
10405
|
+
function updatePaletteResults(state) {
|
|
10406
|
+
const query = state.input.value;
|
|
10407
|
+
if (state.mode === "file") {
|
|
10408
|
+
updateFilePalette(state, query).catch(() => {
|
|
10409
|
+
state.status.textContent = "Search failed";
|
|
10410
|
+
});
|
|
10411
|
+
} else {
|
|
10412
|
+
updateGrepPalette(state, query);
|
|
10413
|
+
}
|
|
10414
|
+
}
|
|
10415
|
+
function selectPaletteItem(state) {
|
|
10416
|
+
const item = state.items[state.selected];
|
|
10417
|
+
if (!item)
|
|
10418
|
+
return;
|
|
10419
|
+
closeSearchPalette();
|
|
10420
|
+
if (item.kind === "file") {
|
|
10421
|
+
if (item.source === "diff") {
|
|
10422
|
+
if (STATE.route.screen === "file") {
|
|
10423
|
+
setRoute({ screen: "file", path: item.targetPath || item.path, ref: item.targetRef || item.ref, range: currentRange() });
|
|
10424
|
+
applySourceRouteToShell();
|
|
10425
|
+
} else {
|
|
10426
|
+
scrollToFile(item.path);
|
|
10427
|
+
}
|
|
10428
|
+
} else {
|
|
10429
|
+
setRoute({ screen: "file", path: item.path, ref: item.ref, view: "blob", range: currentRange() });
|
|
10430
|
+
renderStandaloneSource({ path: item.path, ref: item.ref });
|
|
10431
|
+
}
|
|
10432
|
+
return;
|
|
10433
|
+
}
|
|
10434
|
+
if (item.source === "diff") {
|
|
10435
|
+
setRoute({ screen: "diff", range: currentRange(), path: item.path, line: item.line });
|
|
10436
|
+
scrollToFile(item.path, item.line);
|
|
10437
|
+
} else {
|
|
10438
|
+
setRoute({ screen: "file", path: item.path, ref: item.ref, view: "blob", line: item.line, range: currentRange() });
|
|
10439
|
+
renderStandaloneSource({ path: item.path, ref: item.ref });
|
|
10440
|
+
}
|
|
10441
|
+
}
|
|
10442
|
+
function handlePaletteKeydown(e2, state) {
|
|
10443
|
+
if (e2.key === "Escape") {
|
|
10444
|
+
e2.preventDefault();
|
|
10445
|
+
closeSearchPalette();
|
|
10446
|
+
return;
|
|
10447
|
+
}
|
|
10448
|
+
if (e2.key === "Enter") {
|
|
10449
|
+
if (state.composing)
|
|
10450
|
+
return;
|
|
10451
|
+
e2.preventDefault();
|
|
10452
|
+
selectPaletteItem(state);
|
|
10453
|
+
return;
|
|
10454
|
+
}
|
|
10455
|
+
if (state.mode === "grep" && e2.altKey && e2.key.toLowerCase() === "r") {
|
|
10456
|
+
e2.preventDefault();
|
|
10457
|
+
state.grepRegex = !state.grepRegex;
|
|
10458
|
+
updatePaletteResults(state);
|
|
10459
|
+
return;
|
|
10460
|
+
}
|
|
10461
|
+
const direction = e2.key === "ArrowDown" || e2.ctrlKey && e2.key.toLowerCase() === "n" ? 1 : e2.key === "ArrowUp" || e2.ctrlKey && e2.key.toLowerCase() === "p" ? -1 : 0;
|
|
10462
|
+
if (direction) {
|
|
10463
|
+
e2.preventDefault();
|
|
10464
|
+
state.selected = movePaletteSelection(state.selected, state.items.length, direction);
|
|
10465
|
+
syncPaletteSelection(state);
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
function openSearchPalette(mode) {
|
|
10469
|
+
createPalette(mode);
|
|
10470
|
+
}
|
|
9683
10471
|
document.addEventListener("keydown", (e2) => {
|
|
9684
10472
|
if ((e2.metaKey || e2.ctrlKey) && e2.key.toLowerCase() === "k") {
|
|
9685
10473
|
e2.preventDefault();
|
|
9686
|
-
|
|
10474
|
+
if (PALETTE?.mode === "file")
|
|
10475
|
+
return;
|
|
10476
|
+
openSearchPalette("file");
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
if ((e2.metaKey || e2.ctrlKey) && e2.key.toLowerCase() === "g") {
|
|
10480
|
+
e2.preventDefault();
|
|
10481
|
+
if (PALETTE?.mode === "grep")
|
|
10482
|
+
return;
|
|
10483
|
+
openSearchPalette("grep");
|
|
9687
10484
|
return;
|
|
9688
10485
|
}
|
|
9689
10486
|
const targetEl = e2.target;
|
|
@@ -9842,7 +10639,7 @@
|
|
|
9842
10639
|
const [sha, subject, author, when] = c2.split("\t");
|
|
9843
10640
|
if (!sha)
|
|
9844
10641
|
continue;
|
|
9845
|
-
html.push('<div class="rp-item-commit" data-val="' + escapeAttr(sha) + '"
|
|
10642
|
+
html.push('<div class="rp-item-commit" data-val="' + escapeAttr(sha) + '"><div class="row1"><span class="sha">' + escapeHtml2(sha) + '</span><span class="subject" title="' + escapeAttr(subject || "") + '">' + escapeHtml2(subject || "") + '</span></div><div class="row2"><span class="author">' + escapeHtml2(author || "") + '</span><span class="when">' + escapeHtml2(when || "") + "</span></div></div>");
|
|
9846
10643
|
}
|
|
9847
10644
|
} else if (popTab === "branches") {
|
|
9848
10645
|
const branches = (REFS.branches || []).filter(m);
|
|
@@ -9851,7 +10648,7 @@
|
|
|
9851
10648
|
}
|
|
9852
10649
|
for (const b2 of branches) {
|
|
9853
10650
|
const cur = b2 === REFS.current;
|
|
9854
|
-
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(b2) + '"
|
|
10651
|
+
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(b2) + '"><span class="name">' + escapeHtml2(b2) + "</span>" + (cur ? '<span class="badge cur">current</span>' : '<span class="badge">branch</span>') + "</div>");
|
|
9855
10652
|
}
|
|
9856
10653
|
} else if (popTab === "tags") {
|
|
9857
10654
|
const tags = (REFS.tags || []).filter(m);
|
|
@@ -9859,7 +10656,7 @@
|
|
|
9859
10656
|
html.push('<div class="rp-empty">no tags</div>');
|
|
9860
10657
|
}
|
|
9861
10658
|
for (const t2 of tags) {
|
|
9862
|
-
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(t2) + '"
|
|
10659
|
+
html.push('<div class="rp-item-ref" data-val="' + escapeAttr(t2) + '"><span class="name">' + escapeHtml2(t2) + '</span><span class="badge">tag</span></div>');
|
|
9863
10660
|
}
|
|
9864
10661
|
}
|
|
9865
10662
|
popBody.innerHTML = html.join("");
|