@youtyan/code-viewer 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -6
- package/dist/code-viewer.js +526 -99
- package/package.json +1 -1
- package/web/app.js +2242 -541
- package/web/index.html +3 -0
- package/web/style.css +221 -4
package/dist/code-viewer.js
CHANGED
|
@@ -128,6 +128,79 @@ var init_id = __esm({
|
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
// web-src/core/history.ts
|
|
132
|
+
function parseHistoryLineRange(value) {
|
|
133
|
+
const match = /^(\d+)-(\d+)$/.exec(value || "");
|
|
134
|
+
if (!match) return void 0;
|
|
135
|
+
const a = Number(match[1]);
|
|
136
|
+
const b = Number(match[2]);
|
|
137
|
+
if (!(a > 0) || !(b > 0)) return void 0;
|
|
138
|
+
return { start: Math.min(a, b), end: Math.max(a, b) };
|
|
139
|
+
}
|
|
140
|
+
function formatHistoryLineRange(range) {
|
|
141
|
+
return `${range.start}-${range.end}`;
|
|
142
|
+
}
|
|
143
|
+
function tokenizeHistoryQuery(raw) {
|
|
144
|
+
const tokens = [];
|
|
145
|
+
const known = new Set(HISTORY_QUERY_PREFIXES);
|
|
146
|
+
for (const match of raw.trim().matchAll(/(?:([A-Za-z-]+):)?("([^"]*)"|(\S+))/g)) {
|
|
147
|
+
const prefix = (match[1] || "").toLowerCase();
|
|
148
|
+
const value = match[3] !== void 0 ? match[3] : match[4] ?? "";
|
|
149
|
+
if (!prefix) {
|
|
150
|
+
if (value === "no-merges") {
|
|
151
|
+
tokens.push({ kind: "merges", value: "no" });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (value.endsWith(":") && known.has(value.slice(0, -1).toLowerCase()))
|
|
155
|
+
continue;
|
|
156
|
+
if (value) tokens.push({ kind: "text", value });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!known.has(prefix)) {
|
|
160
|
+
tokens.push({ kind: "text", value: `${match[1]}:${value}` });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!value) continue;
|
|
164
|
+
switch (prefix) {
|
|
165
|
+
case "author":
|
|
166
|
+
case "path":
|
|
167
|
+
case "code":
|
|
168
|
+
tokens.push({ kind: prefix, value });
|
|
169
|
+
break;
|
|
170
|
+
case "since":
|
|
171
|
+
case "after":
|
|
172
|
+
tokens.push({ kind: "since", value });
|
|
173
|
+
break;
|
|
174
|
+
case "until":
|
|
175
|
+
case "before":
|
|
176
|
+
tokens.push({ kind: "until", value });
|
|
177
|
+
break;
|
|
178
|
+
case "merges":
|
|
179
|
+
if (value === "no" || value === "only")
|
|
180
|
+
tokens.push({ kind: "merges", value });
|
|
181
|
+
else tokens.push({ kind: "text", value: `merges:${value}` });
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return tokens;
|
|
186
|
+
}
|
|
187
|
+
var HISTORY_QUERY_PREFIXES, DAY_MS;
|
|
188
|
+
var init_history = __esm({
|
|
189
|
+
"web-src/core/history.ts"() {
|
|
190
|
+
HISTORY_QUERY_PREFIXES = [
|
|
191
|
+
"author",
|
|
192
|
+
"path",
|
|
193
|
+
"since",
|
|
194
|
+
"after",
|
|
195
|
+
"until",
|
|
196
|
+
"before",
|
|
197
|
+
"code",
|
|
198
|
+
"merges"
|
|
199
|
+
];
|
|
200
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
131
204
|
// web-src/core/shell.ts
|
|
132
205
|
function isShellSessionId(value) {
|
|
133
206
|
return typeof value === "string" && /^shell-[0-9a-z]{6,}$/.test(value);
|
|
@@ -201,14 +274,14 @@ function buildRoute(route) {
|
|
|
201
274
|
}
|
|
202
275
|
case "file":
|
|
203
276
|
if (route.view === "blob") {
|
|
204
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=blob" + (route.preview ? "&preview=1" : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
|
|
277
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=blob" + (route.preview ? "&preview=1" : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.line && route.hl ? `&hl=${encodeURIComponent(route.hl)}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
|
|
205
278
|
}
|
|
206
279
|
if (route.view === "blame") {
|
|
207
280
|
const ref = route.ref || "worktree";
|
|
208
281
|
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(ref) + "&view=blame" + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
209
282
|
}
|
|
210
283
|
if (route.view === "history") {
|
|
211
|
-
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=history" + (route.commit ? `&commit=${encodeURIComponent(route.commit)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
284
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=history" + (route.commit ? `&commit=${encodeURIComponent(route.commit)}` : "") + (route.compare ? `&compare=${encodeURIComponent(route.compare)}` : "") + (route.q ? `&q=${encodeURIComponent(route.q)}` : "") + (route.lines ? `&lines=${encodeURIComponent(formatHistoryLineRange(route.lines))}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
212
285
|
}
|
|
213
286
|
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))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
|
|
214
287
|
case "diff":
|
|
@@ -232,7 +305,12 @@ function buildRoute(route) {
|
|
|
232
305
|
case "history": {
|
|
233
306
|
const params = new URLSearchParams();
|
|
234
307
|
if (route.ref && route.ref !== "HEAD") params.set("ref", route.ref);
|
|
308
|
+
if (route.path) params.set("path", route.path);
|
|
309
|
+
if (route.path && route.lines)
|
|
310
|
+
params.set("lines", formatHistoryLineRange(route.lines));
|
|
235
311
|
if (route.commit) params.set("commit", route.commit);
|
|
312
|
+
if (route.compare) params.set("compare", route.compare);
|
|
313
|
+
if (route.q) params.set("q", route.q);
|
|
236
314
|
const qs = params.toString();
|
|
237
315
|
return `/history${qs ? `?${qs}` : ""}`;
|
|
238
316
|
}
|
|
@@ -265,6 +343,7 @@ function buildRoute(route) {
|
|
|
265
343
|
var SPA_PATHS, APP_ENTRY_PATHS;
|
|
266
344
|
var init_routes = __esm({
|
|
267
345
|
"web-src/core/routes.ts"() {
|
|
346
|
+
init_history();
|
|
268
347
|
init_shell();
|
|
269
348
|
init_tools();
|
|
270
349
|
SPA_PATHS = [
|
|
@@ -1667,8 +1746,17 @@ async function readFileTextRange(path, start, endExclusive) {
|
|
|
1667
1746
|
}
|
|
1668
1747
|
function startServer(options) {
|
|
1669
1748
|
const server2 = createServer(async (req, res) => {
|
|
1749
|
+
const abort = new AbortController();
|
|
1750
|
+
res.on("close", () => {
|
|
1751
|
+
if (!res.writableFinished) abort.abort();
|
|
1752
|
+
});
|
|
1670
1753
|
try {
|
|
1671
|
-
const request = nodeRequestToWeb(
|
|
1754
|
+
const request = nodeRequestToWeb(
|
|
1755
|
+
req,
|
|
1756
|
+
options.hostname,
|
|
1757
|
+
server2.address(),
|
|
1758
|
+
abort.signal
|
|
1759
|
+
);
|
|
1672
1760
|
const response = await options.fetch(request);
|
|
1673
1761
|
await writeWebResponse(res, response);
|
|
1674
1762
|
} catch (error) {
|
|
@@ -1719,7 +1807,7 @@ function startServer(options) {
|
|
|
1719
1807
|
});
|
|
1720
1808
|
});
|
|
1721
1809
|
}
|
|
1722
|
-
function nodeRequestToWeb(req, hostname, address) {
|
|
1810
|
+
function nodeRequestToWeb(req, hostname, address, signal) {
|
|
1723
1811
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
1724
1812
|
const host = req.headers.host || `${hostname}:${port}`;
|
|
1725
1813
|
const url = new URL(req.url || "/", `http://${host}`);
|
|
@@ -1737,7 +1825,8 @@ function nodeRequestToWeb(req, hostname, address) {
|
|
|
1737
1825
|
method,
|
|
1738
1826
|
headers,
|
|
1739
1827
|
body: hasBody ? Readable.toWeb(req) : void 0,
|
|
1740
|
-
duplex: hasBody ? "half" : void 0
|
|
1828
|
+
duplex: hasBody ? "half" : void 0,
|
|
1829
|
+
signal
|
|
1741
1830
|
});
|
|
1742
1831
|
}
|
|
1743
1832
|
async function writeWebResponse(res, response) {
|
|
@@ -2383,6 +2472,31 @@ async function remoteWebUrlAsync(cwd2) {
|
|
|
2383
2472
|
if (res.code !== 0) return null;
|
|
2384
2473
|
return parseRemoteWebUrl(res.stdout.trim());
|
|
2385
2474
|
}
|
|
2475
|
+
function parseHistoryDecorations(raw) {
|
|
2476
|
+
const refs = [];
|
|
2477
|
+
for (const part of raw.split(",")) {
|
|
2478
|
+
const item = part.trim();
|
|
2479
|
+
if (!item) continue;
|
|
2480
|
+
if (item === "HEAD") {
|
|
2481
|
+
refs.push({ name: "HEAD", kind: "head" });
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
if (item.startsWith("HEAD -> ")) {
|
|
2485
|
+
refs.push({
|
|
2486
|
+
name: item.slice("HEAD -> ".length),
|
|
2487
|
+
kind: "branch",
|
|
2488
|
+
head: true
|
|
2489
|
+
});
|
|
2490
|
+
continue;
|
|
2491
|
+
}
|
|
2492
|
+
if (item.startsWith("tag: ")) {
|
|
2493
|
+
refs.push({ name: item.slice("tag: ".length), kind: "tag" });
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
refs.push({ name: item, kind: "branch" });
|
|
2497
|
+
}
|
|
2498
|
+
return refs;
|
|
2499
|
+
}
|
|
2386
2500
|
function parseHistoryLog(stdout) {
|
|
2387
2501
|
const parts = stdout.split("\0");
|
|
2388
2502
|
const commits = [];
|
|
@@ -2396,6 +2510,7 @@ function parseHistoryLog(stdout) {
|
|
|
2396
2510
|
const author = parts[index++] || "";
|
|
2397
2511
|
const when = parts[index++] || "";
|
|
2398
2512
|
const parentsRaw = (parts[index++] || "").trim();
|
|
2513
|
+
const decorations = (parts[index++] || "").trim();
|
|
2399
2514
|
const body = (parts[index++] || "").trim();
|
|
2400
2515
|
if (sha)
|
|
2401
2516
|
commits.push({
|
|
@@ -2404,7 +2519,8 @@ function parseHistoryLog(stdout) {
|
|
|
2404
2519
|
author,
|
|
2405
2520
|
when,
|
|
2406
2521
|
parents: parentsRaw ? parentsRaw.split(/\s+/) : [],
|
|
2407
|
-
body
|
|
2522
|
+
body,
|
|
2523
|
+
refs: parseHistoryDecorations(decorations)
|
|
2408
2524
|
});
|
|
2409
2525
|
}
|
|
2410
2526
|
return commits;
|
|
@@ -2412,37 +2528,125 @@ function parseHistoryLog(stdout) {
|
|
|
2412
2528
|
function historyQueryArgs(query) {
|
|
2413
2529
|
const trimmed = query.trim().slice(0, 200).replace(/\0/g, "");
|
|
2414
2530
|
if (!trimmed) return { filterArgs: [], pathspec: [], shaTerm: "" };
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2531
|
+
const filterArgs = [];
|
|
2532
|
+
const pathspec = [];
|
|
2533
|
+
const texts = [];
|
|
2534
|
+
let needsTextFlags = false;
|
|
2535
|
+
for (const token of tokenizeHistoryQuery(trimmed)) {
|
|
2536
|
+
switch (token.kind) {
|
|
2537
|
+
case "text":
|
|
2538
|
+
texts.push(token.value);
|
|
2539
|
+
break;
|
|
2540
|
+
case "author":
|
|
2541
|
+
needsTextFlags = true;
|
|
2542
|
+
filterArgs.push(`--author=${token.value}`);
|
|
2543
|
+
break;
|
|
2544
|
+
case "path":
|
|
2545
|
+
pathspec.push(`:(icase)*${token.value}*`);
|
|
2546
|
+
break;
|
|
2547
|
+
case "since":
|
|
2548
|
+
filterArgs.push(`--since=${token.value}`);
|
|
2549
|
+
break;
|
|
2550
|
+
case "until":
|
|
2551
|
+
filterArgs.push(`--until=${token.value}`);
|
|
2552
|
+
break;
|
|
2553
|
+
case "code":
|
|
2554
|
+
filterArgs.push(`-S${token.value}`);
|
|
2555
|
+
break;
|
|
2556
|
+
case "merges":
|
|
2557
|
+
filterArgs.push(token.value === "no" ? "--no-merges" : "--merges");
|
|
2558
|
+
break;
|
|
2429
2559
|
}
|
|
2430
|
-
return {
|
|
2431
|
-
filterArgs: [],
|
|
2432
|
-
pathspec: ["--", `:(icase)*${term}*`],
|
|
2433
|
-
shaTerm: ""
|
|
2434
|
-
};
|
|
2435
2560
|
}
|
|
2561
|
+
const phrase = texts.join(" ");
|
|
2562
|
+
if (phrase) {
|
|
2563
|
+
needsTextFlags = true;
|
|
2564
|
+
filterArgs.push(`--grep=${phrase}`);
|
|
2565
|
+
}
|
|
2566
|
+
if (needsTextFlags)
|
|
2567
|
+
filterArgs.unshift("--regexp-ignore-case", "--fixed-strings");
|
|
2436
2568
|
return {
|
|
2437
|
-
filterArgs
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
`--grep=${trimmed}`
|
|
2441
|
-
],
|
|
2442
|
-
pathspec: [],
|
|
2443
|
-
shaTerm: /^[0-9a-f]{4,40}$/i.test(trimmed) ? trimmed : ""
|
|
2569
|
+
filterArgs,
|
|
2570
|
+
pathspec,
|
|
2571
|
+
shaTerm: texts.length === 1 && /^[0-9a-f]{4,40}$/i.test(texts[0]) ? texts[0] : ""
|
|
2444
2572
|
};
|
|
2445
2573
|
}
|
|
2574
|
+
async function fileRevisionNeighborsAsync(cwd2, options) {
|
|
2575
|
+
const path = options.path.trim();
|
|
2576
|
+
if (!path || path.includes("\0") || path.startsWith("-"))
|
|
2577
|
+
return { previous: null, next: null, error: "invalid path" };
|
|
2578
|
+
const rawRef = (options.ref || "HEAD").trim();
|
|
2579
|
+
const ref = rawRef === "worktree" ? "HEAD" : rawRef;
|
|
2580
|
+
if (!ref || ref.startsWith("-") || ref.includes("\0"))
|
|
2581
|
+
return { previous: null, next: null, error: "invalid ref" };
|
|
2582
|
+
const verified = await runGitAsync(
|
|
2583
|
+
["git", "rev-parse", "--verify", `${ref}^{commit}`],
|
|
2584
|
+
cwd2
|
|
2585
|
+
);
|
|
2586
|
+
if (verified.code !== 0)
|
|
2587
|
+
return {
|
|
2588
|
+
previous: null,
|
|
2589
|
+
next: null,
|
|
2590
|
+
...gitFailureResult(verified, "unknown ref")
|
|
2591
|
+
};
|
|
2592
|
+
const sha = verified.stdout.trim();
|
|
2593
|
+
const older = await runGitAsync(
|
|
2594
|
+
["git", "log", "--format=%H", "--max-count=2", sha, "--", path],
|
|
2595
|
+
cwd2
|
|
2596
|
+
);
|
|
2597
|
+
if (older.code !== 0)
|
|
2598
|
+
return {
|
|
2599
|
+
previous: null,
|
|
2600
|
+
next: null,
|
|
2601
|
+
...gitFailureResult(older, "git log failed")
|
|
2602
|
+
};
|
|
2603
|
+
const olderShas = older.stdout.split("\n").filter(Boolean);
|
|
2604
|
+
const previous = olderShas[0] && olderShas[0] !== sha ? olderShas[0] : olderShas[1] ?? null;
|
|
2605
|
+
const newer = await runGitAsync(
|
|
2606
|
+
["git", "log", "--format=%H", `${sha}..HEAD`, "--", path],
|
|
2607
|
+
cwd2
|
|
2608
|
+
);
|
|
2609
|
+
if (newer.code !== 0)
|
|
2610
|
+
return {
|
|
2611
|
+
previous: null,
|
|
2612
|
+
next: null,
|
|
2613
|
+
...gitFailureResult(newer, "git log failed")
|
|
2614
|
+
};
|
|
2615
|
+
const newerShas = newer.stdout.split("\n").filter(Boolean);
|
|
2616
|
+
const next = newerShas.length ? newerShas[newerShas.length - 1] : null;
|
|
2617
|
+
return { previous, next };
|
|
2618
|
+
}
|
|
2619
|
+
async function commitAuthorsAsync(cwd2, ref) {
|
|
2620
|
+
const target = (ref || "HEAD").trim();
|
|
2621
|
+
if (!target || target.startsWith("-") || target.includes("\0"))
|
|
2622
|
+
return { authors: [], error: "invalid ref" };
|
|
2623
|
+
const verified = await runGitAsync(
|
|
2624
|
+
["git", "rev-parse", "--verify", `${target}^{commit}`],
|
|
2625
|
+
cwd2
|
|
2626
|
+
);
|
|
2627
|
+
if (verified.code !== 0)
|
|
2628
|
+
return { authors: [], ...gitFailureResult(verified, "unknown ref") };
|
|
2629
|
+
const res = await runGitAsync(
|
|
2630
|
+
[
|
|
2631
|
+
"git",
|
|
2632
|
+
"log",
|
|
2633
|
+
"--format=%an",
|
|
2634
|
+
`--max-count=${MAX_HISTORY_AUTHOR_SCAN}`,
|
|
2635
|
+
verified.stdout.trim()
|
|
2636
|
+
],
|
|
2637
|
+
cwd2
|
|
2638
|
+
);
|
|
2639
|
+
if (res.code !== 0)
|
|
2640
|
+
return { authors: [], ...gitFailureResult(res, "git log failed") };
|
|
2641
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2642
|
+
for (const line of res.stdout.split("\n")) {
|
|
2643
|
+
const name = line.trim();
|
|
2644
|
+
if (!name) continue;
|
|
2645
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
2646
|
+
}
|
|
2647
|
+
const authors = [...counts.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
2648
|
+
return { authors };
|
|
2649
|
+
}
|
|
2446
2650
|
async function commitHistoryAsync(cwd2, options) {
|
|
2447
2651
|
const ref = (options.ref || "HEAD").trim();
|
|
2448
2652
|
if (!ref || ref.startsWith("-") || ref.includes("\0"))
|
|
@@ -2466,10 +2670,13 @@ async function commitHistoryAsync(cwd2, options) {
|
|
|
2466
2670
|
options.query || ""
|
|
2467
2671
|
);
|
|
2468
2672
|
const pathFilter = (options.path || "").trim();
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2673
|
+
const safePathFilter = pathFilter && !pathFilter.includes("\0") && !pathFilter.startsWith("-") ? pathFilter : "";
|
|
2674
|
+
const lineRange = options.lines && safePathFilter && !safePathFilter.endsWith("/") ? options.lines : void 0;
|
|
2675
|
+
const specs = lineRange ? [] : [...pathspec];
|
|
2676
|
+
let follow = false;
|
|
2677
|
+
if (safePathFilter && !lineRange) {
|
|
2678
|
+
specs.push(safePathFilter);
|
|
2679
|
+
follow = !safePathFilter.endsWith("/") && specs.length === 1;
|
|
2473
2680
|
}
|
|
2474
2681
|
const res = await runGitAsync(
|
|
2475
2682
|
[
|
|
@@ -2480,9 +2687,12 @@ async function commitHistoryAsync(cwd2, options) {
|
|
|
2480
2687
|
`--max-count=${limit + 1}`,
|
|
2481
2688
|
`--format=${HISTORY_FORMAT}`,
|
|
2482
2689
|
...filterArgs,
|
|
2690
|
+
...follow ? ["--follow"] : [],
|
|
2691
|
+
// -L prints patches by default; -s keeps the output to the headers
|
|
2692
|
+
// our parser expects.
|
|
2693
|
+
...lineRange ? [`-L${lineRange.start},${lineRange.end}:${safePathFilter}`, "-s"] : [],
|
|
2483
2694
|
verified.stdout.trim(),
|
|
2484
|
-
...
|
|
2485
|
-
...pathArgs
|
|
2695
|
+
...specs.length ? ["--", ...specs] : []
|
|
2486
2696
|
],
|
|
2487
2697
|
cwd2
|
|
2488
2698
|
);
|
|
@@ -3258,10 +3468,11 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
3258
3468
|
lineTruncated
|
|
3259
3469
|
};
|
|
3260
3470
|
}
|
|
3261
|
-
var BLAME_ZERO_SHA, WORKTREE_RECURSIVE_DEPTH_LIMIT, WORKTREE_RECURSIVE_ENTRY_LIMIT, DEFAULT_REF_COMMIT_LIMIT, MAX_REF_COMMIT_LIMIT, COMMIT_FORMAT, ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS, repoStatusMapCache, STATUS_PORCELAIN_ARGS, HISTORY_FORMAT, MAX_HISTORY_LIMIT, LS_TREE_SYMLINK_MODE, untrackedScanCache, UNTRACKED_SCAN_CONCURRENCY;
|
|
3471
|
+
var BLAME_ZERO_SHA, WORKTREE_RECURSIVE_DEPTH_LIMIT, WORKTREE_RECURSIVE_ENTRY_LIMIT, DEFAULT_REF_COMMIT_LIMIT, MAX_REF_COMMIT_LIMIT, COMMIT_FORMAT, ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS, repoStatusMapCache, STATUS_PORCELAIN_ARGS, HISTORY_FORMAT, MAX_HISTORY_LIMIT, MAX_HISTORY_AUTHOR_SCAN, LS_TREE_SYMLINK_MODE, untrackedScanCache, UNTRACKED_SCAN_CONCURRENCY;
|
|
3262
3472
|
var init_git = __esm({
|
|
3263
3473
|
"web-src/server/git.ts"() {
|
|
3264
3474
|
init_error_detail();
|
|
3475
|
+
init_history();
|
|
3265
3476
|
init_worktree();
|
|
3266
3477
|
init_cache();
|
|
3267
3478
|
init_command_resolver();
|
|
@@ -3333,8 +3544,9 @@ var init_git = __esm({
|
|
|
3333
3544
|
// would make git enumerate every file under it on each poll.
|
|
3334
3545
|
"--untracked-files=normal"
|
|
3335
3546
|
];
|
|
3336
|
-
HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b";
|
|
3547
|
+
HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%D%x00%b";
|
|
3337
3548
|
MAX_HISTORY_LIMIT = 200;
|
|
3549
|
+
MAX_HISTORY_AUTHOR_SCAN = 5e3;
|
|
3338
3550
|
LS_TREE_SYMLINK_MODE = "120000";
|
|
3339
3551
|
untrackedScanCache = /* @__PURE__ */ new Map();
|
|
3340
3552
|
UNTRACKED_SCAN_CONCURRENCY = 8;
|
|
@@ -5305,8 +5517,12 @@ blame options:
|
|
|
5305
5517
|
history options:
|
|
5306
5518
|
--limit <n> 1..${FILE_HISTORY_HARD_CAP} (default: ${FILE_DEFAULT_HISTORY_LIMIT}).
|
|
5307
5519
|
--skip <n> Non-negative. Used to paginate.
|
|
5308
|
-
--query <text> git log filter
|
|
5309
|
-
|
|
5520
|
+
--query <text> git log filter, same syntax as the browser history filter:
|
|
5521
|
+
free words match the message ("quoted" keeps spaces),
|
|
5522
|
+
author:<name>, path:<part>, since:/after:<date>,
|
|
5523
|
+
until:/before:<date>, code:<text> (git log -S),
|
|
5524
|
+
merges:no (or no-merges), merges:only. Kinds AND together;
|
|
5525
|
+
a lone 4-40 hex word also tries a sha prefix.
|
|
5310
5526
|
|
|
5311
5527
|
show options:
|
|
5312
5528
|
--start <line> 1-indexed start line (inclusive). Requires --end.
|
|
@@ -5370,6 +5586,7 @@ Diff Viewer views.
|
|
|
5370
5586
|
code-viewer file blame --path src/sample.ts --base HEAD --json
|
|
5371
5587
|
code-viewer file history --path src/sample.ts --limit 10 --json
|
|
5372
5588
|
code-viewer file history --path src/sample.ts --query "author:tester" --json
|
|
5589
|
+
code-viewer file history --path src/sample.ts --query "since:2024-01-01 code:handler merges:no" --json
|
|
5373
5590
|
code-viewer file show --path src/sample.ts --json
|
|
5374
5591
|
code-viewer file show --path src/sample.ts --start 100 --end 150 --json
|
|
5375
5592
|
code-viewer file show --path src/sample.ts --ref main --json
|
|
@@ -9289,21 +9506,25 @@ function computeFuzzyMatch(query, path) {
|
|
|
9289
9506
|
tier
|
|
9290
9507
|
};
|
|
9291
9508
|
}
|
|
9292
|
-
function rankFuzzyPaths(query, items, limit) {
|
|
9509
|
+
function rankFuzzyPaths(query, items, limit, stats) {
|
|
9293
9510
|
const bounded = Number.isInteger(limit) && limit !== void 0 && limit > 0 ? Math.floor(limit) : 0;
|
|
9294
9511
|
const compare = (a, b) => b.tier - a.tier || b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
9295
9512
|
if (!bounded) {
|
|
9296
|
-
|
|
9513
|
+
const ranked = items.map((item) => {
|
|
9297
9514
|
const match = computeFuzzyMatch(query, item.path);
|
|
9298
9515
|
return match ? { item, score: match.score, ranges: match.ranges, tier: match.tier } : null;
|
|
9299
9516
|
}).filter(
|
|
9300
9517
|
(item) => item !== null
|
|
9301
9518
|
).sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
9519
|
+
if (stats) stats.total = ranked.length;
|
|
9520
|
+
return ranked;
|
|
9302
9521
|
}
|
|
9303
9522
|
const top = [];
|
|
9523
|
+
let total = 0;
|
|
9304
9524
|
for (const item of items) {
|
|
9305
9525
|
const match = computeFuzzyMatch(query, item.path);
|
|
9306
9526
|
if (!match) continue;
|
|
9527
|
+
total++;
|
|
9307
9528
|
const ranked = {
|
|
9308
9529
|
item,
|
|
9309
9530
|
score: match.score,
|
|
@@ -9312,6 +9533,7 @@ function rankFuzzyPaths(query, items, limit) {
|
|
|
9312
9533
|
};
|
|
9313
9534
|
pushBoundedTop(top, ranked, bounded, compare);
|
|
9314
9535
|
}
|
|
9536
|
+
if (stats) stats.total = total;
|
|
9315
9537
|
return top.sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
9316
9538
|
}
|
|
9317
9539
|
function pushBoundedTop(heap, item, limit, compareBestFirst) {
|
|
@@ -9346,13 +9568,16 @@ function pushBoundedTop(heap, item, limit, compareBestFirst) {
|
|
|
9346
9568
|
heap[0] = item;
|
|
9347
9569
|
siftDown(0);
|
|
9348
9570
|
}
|
|
9349
|
-
function rankGlobPathMatches(query, items, limit) {
|
|
9571
|
+
function rankGlobPathMatches(query, items, limit, stats) {
|
|
9350
9572
|
const matchPath = createGlobPathMatcher(query);
|
|
9351
|
-
if (!matchPath)
|
|
9573
|
+
if (!matchPath) {
|
|
9574
|
+
if (stats) stats.total = 0;
|
|
9575
|
+
return [];
|
|
9576
|
+
}
|
|
9352
9577
|
const bounded = Number.isInteger(limit) && limit !== void 0 && limit > 0 ? Math.floor(limit) : 0;
|
|
9353
9578
|
const compare = (a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
9354
9579
|
if (!bounded) {
|
|
9355
|
-
|
|
9580
|
+
const ranked = items.map((item) => {
|
|
9356
9581
|
const match = matchPath(item.path);
|
|
9357
9582
|
return match ? {
|
|
9358
9583
|
item,
|
|
@@ -9361,11 +9586,15 @@ function rankGlobPathMatches(query, items, limit) {
|
|
|
9361
9586
|
mode: "glob"
|
|
9362
9587
|
} : null;
|
|
9363
9588
|
}).filter((item) => item !== null).sort(compare);
|
|
9589
|
+
if (stats) stats.total = ranked.length;
|
|
9590
|
+
return ranked;
|
|
9364
9591
|
}
|
|
9365
9592
|
const top = [];
|
|
9593
|
+
let total = 0;
|
|
9366
9594
|
for (const item of items) {
|
|
9367
9595
|
const match = matchPath(item.path);
|
|
9368
9596
|
if (!match) continue;
|
|
9597
|
+
total++;
|
|
9369
9598
|
const ranked = {
|
|
9370
9599
|
item,
|
|
9371
9600
|
score: match.score,
|
|
@@ -9374,13 +9603,14 @@ function rankGlobPathMatches(query, items, limit) {
|
|
|
9374
9603
|
};
|
|
9375
9604
|
pushBoundedTop(top, ranked, bounded, compare);
|
|
9376
9605
|
}
|
|
9606
|
+
if (stats) stats.total = total;
|
|
9377
9607
|
return top.sort(compare);
|
|
9378
9608
|
}
|
|
9379
|
-
function rankPathMatches(query, items, limit) {
|
|
9609
|
+
function rankPathMatches(query, items, limit, stats) {
|
|
9380
9610
|
if (isGlobPathQuery(query)) {
|
|
9381
|
-
return rankGlobPathMatches(query, items, limit);
|
|
9611
|
+
return rankGlobPathMatches(query, items, limit, stats);
|
|
9382
9612
|
}
|
|
9383
|
-
return rankFuzzyPaths(query, items, limit).map((item) => ({
|
|
9613
|
+
return rankFuzzyPaths(query, items, limit, stats).map((item) => ({
|
|
9384
9614
|
...item,
|
|
9385
9615
|
mode: "fuzzy"
|
|
9386
9616
|
}));
|
|
@@ -9399,8 +9629,13 @@ function globToRegExp(query) {
|
|
|
9399
9629
|
const ch = pattern[i];
|
|
9400
9630
|
if (ch === "*") {
|
|
9401
9631
|
if (pattern[i + 1] === "*") {
|
|
9402
|
-
|
|
9403
|
-
|
|
9632
|
+
if (pattern[i + 2] === "/") {
|
|
9633
|
+
source += "(?:.*/)?";
|
|
9634
|
+
i += 2;
|
|
9635
|
+
} else {
|
|
9636
|
+
source += ".*";
|
|
9637
|
+
i++;
|
|
9638
|
+
}
|
|
9404
9639
|
} else {
|
|
9405
9640
|
source += "[^/]*";
|
|
9406
9641
|
}
|
|
@@ -9426,6 +9661,9 @@ function globToRegExp(query) {
|
|
|
9426
9661
|
return null;
|
|
9427
9662
|
}
|
|
9428
9663
|
}
|
|
9664
|
+
function globMatchPath(query, path) {
|
|
9665
|
+
return createGlobPathMatcher(query)?.(path) ?? null;
|
|
9666
|
+
}
|
|
9429
9667
|
function createGlobPathMatcher(query) {
|
|
9430
9668
|
const regex = globToRegExp(query);
|
|
9431
9669
|
if (!regex) return null;
|
|
@@ -9475,14 +9713,31 @@ function isSkippableSearchPath(path, omitDirNames = [], excludeNames = []) {
|
|
|
9475
9713
|
return lower === ".git" || lower === ".code-viewer" || omitDirs.matches(part) || excluded.matches(part);
|
|
9476
9714
|
});
|
|
9477
9715
|
}
|
|
9478
|
-
function
|
|
9479
|
-
const
|
|
9480
|
-
|
|
9716
|
+
function isWordBoundary(line, start, end) {
|
|
9717
|
+
const before = start > 0 ? line[start - 1] : "";
|
|
9718
|
+
const after = end < line.length ? line[end] : "";
|
|
9719
|
+
return !WORD_CHAR_RE.test(before) && !WORD_CHAR_RE.test(after);
|
|
9720
|
+
}
|
|
9721
|
+
function fixedStringColumn(line, query, options = {}) {
|
|
9722
|
+
if (!query) return -1;
|
|
9723
|
+
const haystack = options.caseSensitive ? line : line.toLowerCase();
|
|
9724
|
+
const needle = options.caseSensitive ? query : query.toLowerCase();
|
|
9725
|
+
let from = 0;
|
|
9726
|
+
for (; ; ) {
|
|
9727
|
+
const column = haystack.indexOf(needle, from);
|
|
9728
|
+
if (column < 0) return -1;
|
|
9729
|
+
if (!options.wholeWord || isWordBoundary(line, column, column + needle.length))
|
|
9730
|
+
return column;
|
|
9731
|
+
from = column + 1;
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
function fixedStringLineMatches(path, text2, query, max, options = {}) {
|
|
9735
|
+
if (!query) return [];
|
|
9481
9736
|
const matches = [];
|
|
9482
9737
|
const lines = text2.split("\n");
|
|
9483
9738
|
for (let i = 0; i < lines.length && matches.length < max; i++) {
|
|
9484
9739
|
const line = lines[i];
|
|
9485
|
-
const column = line
|
|
9740
|
+
const column = fixedStringColumn(line, query, options);
|
|
9486
9741
|
if (column < 0) continue;
|
|
9487
9742
|
matches.push({
|
|
9488
9743
|
path,
|
|
@@ -9505,8 +9760,12 @@ function buildFileSearchList(ref, generation2, entries) {
|
|
|
9505
9760
|
truncated: entries.length > FILE_SEARCH_ABSOLUTE_MAX
|
|
9506
9761
|
};
|
|
9507
9762
|
}
|
|
9508
|
-
function buildRgArgs(query, max, paths, regex = false, omitDirNames = [], excludeNames = [], excludeTests = false) {
|
|
9763
|
+
function buildRgArgs(query, max, paths, regex = false, omitDirNames = [], excludeNames = [], excludeTests = false, options = {}) {
|
|
9509
9764
|
const safePaths = paths.length ? paths : ["."];
|
|
9765
|
+
const includeGlobs = (options.pathGlobs ?? []).flatMap((pattern) => [
|
|
9766
|
+
"--glob",
|
|
9767
|
+
pattern
|
|
9768
|
+
]);
|
|
9510
9769
|
const omitGlobs = omitDirNames.flatMap((name) => [
|
|
9511
9770
|
"--glob",
|
|
9512
9771
|
`!${name}/**`,
|
|
@@ -9556,12 +9815,14 @@ function buildRgArgs(query, max, paths, regex = false, omitDirNames = [], exclud
|
|
|
9556
9815
|
"--with-filename",
|
|
9557
9816
|
"--color",
|
|
9558
9817
|
"never",
|
|
9559
|
-
"--
|
|
9818
|
+
options.caseSensitive ? "--case-sensitive" : "--ignore-case",
|
|
9819
|
+
...options.wholeWord ? ["--word-regexp"] : [],
|
|
9560
9820
|
...matchMode,
|
|
9561
9821
|
"--max-count",
|
|
9562
9822
|
String(max),
|
|
9563
9823
|
"--max-filesize",
|
|
9564
9824
|
"2M",
|
|
9825
|
+
...includeGlobs,
|
|
9565
9826
|
...omitGlobs,
|
|
9566
9827
|
...excludeGlobs,
|
|
9567
9828
|
...testGlobs,
|
|
@@ -9633,7 +9894,7 @@ function parseGitGrepOutput(stdout, ref, max, omitDirNames = [], excludeNames =
|
|
|
9633
9894
|
includePath
|
|
9634
9895
|
);
|
|
9635
9896
|
}
|
|
9636
|
-
var GREP_DEFAULT_MAX, GREP_ABSOLUTE_MAX, GREP_MAX_FILE_BYTES, FILE_SEARCH_ABSOLUTE_MAX, DEFAULT_EXCLUDE_NAMES;
|
|
9897
|
+
var GREP_DEFAULT_MAX, GREP_ABSOLUTE_MAX, GREP_MAX_FILE_BYTES, FILE_SEARCH_ABSOLUTE_MAX, DEFAULT_EXCLUDE_NAMES, WORD_CHAR_RE;
|
|
9637
9898
|
var init_search = __esm({
|
|
9638
9899
|
"web-src/server/search.ts"() {
|
|
9639
9900
|
init_name_pattern();
|
|
@@ -9642,6 +9903,7 @@ var init_search = __esm({
|
|
|
9642
9903
|
GREP_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
9643
9904
|
FILE_SEARCH_ABSOLUTE_MAX = 5e4;
|
|
9644
9905
|
DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
|
|
9906
|
+
WORD_CHAR_RE = /[\p{L}\p{N}_]/u;
|
|
9645
9907
|
}
|
|
9646
9908
|
});
|
|
9647
9909
|
|
|
@@ -9735,11 +9997,13 @@ function parseSearchArgs(argv) {
|
|
|
9735
9997
|
return { ok: false, error: "--term must be a single line" };
|
|
9736
9998
|
}
|
|
9737
9999
|
if (subcommand === "files") {
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
10000
|
+
for (const codeOnly of ["--regex", "--case-sensitive", "--word"]) {
|
|
10001
|
+
if (flags.has(codeOnly)) {
|
|
10002
|
+
return {
|
|
10003
|
+
ok: false,
|
|
10004
|
+
error: `search files does not accept ${codeOnly}`
|
|
10005
|
+
};
|
|
10006
|
+
}
|
|
9743
10007
|
}
|
|
9744
10008
|
if (paths.length > 0) {
|
|
9745
10009
|
return {
|
|
@@ -9780,6 +10044,8 @@ function parseSearchArgs(argv) {
|
|
|
9780
10044
|
ref: options.get("--ref"),
|
|
9781
10045
|
paths,
|
|
9782
10046
|
regex: flags.has("--regex"),
|
|
10047
|
+
caseSensitive: flags.has("--case-sensitive"),
|
|
10048
|
+
wholeWord: flags.has("--word"),
|
|
9783
10049
|
max: parsedMax.value,
|
|
9784
10050
|
json: flags.has("--json")
|
|
9785
10051
|
},
|
|
@@ -9795,6 +10061,8 @@ function buildGrepPath(command) {
|
|
|
9795
10061
|
if (command.ref) params.set("ref", command.ref);
|
|
9796
10062
|
if (command.max !== void 0) params.set("max", String(command.max));
|
|
9797
10063
|
if (command.regex) params.set("regex", "1");
|
|
10064
|
+
if (command.caseSensitive) params.set("case", "1");
|
|
10065
|
+
if (command.wholeWord) params.set("word", "1");
|
|
9798
10066
|
for (const path of command.paths) params.append("path", path);
|
|
9799
10067
|
return `/_grep?${params.toString()}`;
|
|
9800
10068
|
}
|
|
@@ -9925,7 +10193,8 @@ var init_search_cli = __esm({
|
|
|
9925
10193
|
|
|
9926
10194
|
Usage:
|
|
9927
10195
|
code-viewer search code --term <text> [--ref <ref>] [--path <path>...]
|
|
9928
|
-
[--regex] [--
|
|
10196
|
+
[--regex] [--case-sensitive] [--word]
|
|
10197
|
+
[--max <n>] [--json]
|
|
9929
10198
|
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
9930
10199
|
code-viewer search files --term <pattern> [--ref <ref>] [--max <n>] [--json]
|
|
9931
10200
|
[--cwd <dir>] [--server <url>] [--bin <name>=<path>]
|
|
@@ -9946,8 +10215,12 @@ Common options:
|
|
|
9946
10215
|
--help, -h Show this help.
|
|
9947
10216
|
|
|
9948
10217
|
search code only:
|
|
9949
|
-
--path <path> Restrict the search to a
|
|
10218
|
+
--path <path> Restrict the search to a file, a directory, or a glob
|
|
10219
|
+
such as "src/**/*.ts". Repeatable.
|
|
9950
10220
|
--regex Treat --term as an extended regex instead of a fixed string.
|
|
10221
|
+
--case-sensitive Match case. Default: case-insensitive on every engine.
|
|
10222
|
+
--word Match whole words only (rg -w / git grep -w; the
|
|
10223
|
+
fallback scanner applies the same boundary rule).
|
|
9951
10224
|
|
|
9952
10225
|
search files:
|
|
9953
10226
|
--term auto-switches between fuzzy and glob matching:
|
|
@@ -9995,6 +10268,8 @@ environments. Results are NOT persisted on the server; this is a pure read.
|
|
|
9995
10268
|
code-viewer search code --term "TODO" --json
|
|
9996
10269
|
code-viewer search code --term "fn handler" --regex --json
|
|
9997
10270
|
code-viewer search code --term "config" --path src --path tests --json
|
|
10271
|
+
code-viewer search code --term "Token" --case-sensitive --word --json
|
|
10272
|
+
code-viewer search code --term "config" --path "src/**/*.ts" --json
|
|
9998
10273
|
code-viewer search code --term "release" --ref main --json
|
|
9999
10274
|
|
|
10000
10275
|
code-viewer search files --term "sample"
|
|
@@ -10053,6 +10328,10 @@ Parse failures and unreachable servers exit 1.
|
|
|
10053
10328
|
- search code: engine=fallback with regex=true returns zero matches by
|
|
10054
10329
|
design — the fallback path does not support regex. Install ripgrep or
|
|
10055
10330
|
use a fixed string instead.
|
|
10331
|
+
- search code: matching is case-insensitive on every engine unless you
|
|
10332
|
+
pass --case-sensitive; --word requires the hit to sit on word
|
|
10333
|
+
boundaries. --path accepts globs ("src/**/*.ts", "*.md") as well as
|
|
10334
|
+
files and directories.
|
|
10056
10335
|
- search files: --term containing * or ? triggers glob mode (e.g.
|
|
10057
10336
|
"src/**/*.test.ts"). Bare words use fuzzy ranking (e.g. "auth",
|
|
10058
10337
|
"userId"). The /_files list is shared with the browser Ctrl+K palette,
|
|
@@ -10064,7 +10343,7 @@ Parse failures and unreachable servers exit 1.
|
|
|
10064
10343
|
`;
|
|
10065
10344
|
VALUE_FLAGS3 = /* @__PURE__ */ new Set(["--term", "--ref", "--max"]);
|
|
10066
10345
|
REPEATABLE_VALUE_FLAGS = /* @__PURE__ */ new Set(["--path"]);
|
|
10067
|
-
BOOL_FLAGS3 = /* @__PURE__ */ new Set(["--regex", "--json"]);
|
|
10346
|
+
BOOL_FLAGS3 = /* @__PURE__ */ new Set(["--regex", "--case-sensitive", "--word", "--json"]);
|
|
10068
10347
|
}
|
|
10069
10348
|
});
|
|
10070
10349
|
|
|
@@ -17529,6 +17808,8 @@ var init_keymap = __esm({
|
|
|
17529
17808
|
"previous-hunk",
|
|
17530
17809
|
"goto-diff",
|
|
17531
17810
|
"goto-history",
|
|
17811
|
+
"history-next-commit",
|
|
17812
|
+
"history-previous-commit",
|
|
17532
17813
|
"goto-repo",
|
|
17533
17814
|
"toggle-terminal-panel",
|
|
17534
17815
|
"toggle-sidebar",
|
|
@@ -17719,6 +18000,11 @@ function sanitizeSettings(raw) {
|
|
|
17719
18000
|
if (hideTests !== void 0) out.hideTests = hideTests;
|
|
17720
18001
|
const grepRegex = optionalBoolean(raw.grepRegex);
|
|
17721
18002
|
if (grepRegex !== void 0) out.grepRegex = grepRegex;
|
|
18003
|
+
const grepCaseSensitive = optionalBoolean(raw.grepCaseSensitive);
|
|
18004
|
+
if (grepCaseSensitive !== void 0)
|
|
18005
|
+
out.grepCaseSensitive = grepCaseSensitive;
|
|
18006
|
+
const grepWholeWord = optionalBoolean(raw.grepWholeWord);
|
|
18007
|
+
if (grepWholeWord !== void 0) out.grepWholeWord = grepWholeWord;
|
|
17722
18008
|
const grepGroupByFile = optionalBoolean(raw.grepGroupByFile);
|
|
17723
18009
|
if (grepGroupByFile !== void 0) out.grepGroupByFile = grepGroupByFile;
|
|
17724
18010
|
const grepPaletteWidth = optionalNumber(
|
|
@@ -17748,6 +18034,13 @@ function sanitizeSettings(raw) {
|
|
|
17748
18034
|
sort: false
|
|
17749
18035
|
});
|
|
17750
18036
|
if (fileSelectionHistory) out.fileSelectionHistory = fileSelectionHistory;
|
|
18037
|
+
const recentRefs = normalizeStringList(raw.recentRefs, {
|
|
18038
|
+
maxItems: MAX_RECENT_REFS,
|
|
18039
|
+
maxLen: MAX_REF_LEN,
|
|
18040
|
+
keepLast: true,
|
|
18041
|
+
sort: false
|
|
18042
|
+
});
|
|
18043
|
+
if (recentRefs) out.recentRefs = recentRefs;
|
|
17751
18044
|
const scopeOmitDirs = normalizeStringList(raw.scopeOmitDirs, {
|
|
17752
18045
|
maxItems: 100,
|
|
17753
18046
|
maxLen: 64,
|
|
@@ -18126,7 +18419,7 @@ async function loadToolsState(root) {
|
|
|
18126
18419
|
async function patchToolsState(root, patch) {
|
|
18127
18420
|
return patchJsonState(toolsStore, root, patch, mergeToolsState);
|
|
18128
18421
|
}
|
|
18129
|
-
var CODE_VIEWER_DIR2, SETTINGS_FILE_NAME, VIEW_STATE_FILE_NAME, DB_UI_FILE_NAME, TOOLS_FILE_NAME, MAX_SETTINGS_BYTES, MAX_VIEW_STATE_BYTES, MAX_DB_UI_BYTES, MAX_TOOL_DRAFT_LEN, MAX_TOOLS_BYTES, MAX_REF_LEN, MAX_KEY_LEN, MAX_VIEW_ITEMS, MAX_GREP_SELECTION_HISTORY_ITEMS, MAX_DB_UI_DBS, MAX_DB_UI_TABLES, MAX_DB_UI_COLUMNS, MAX_DB_UI_EXPANDED_SCOPES, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore, toolsStore;
|
|
18422
|
+
var CODE_VIEWER_DIR2, SETTINGS_FILE_NAME, VIEW_STATE_FILE_NAME, DB_UI_FILE_NAME, TOOLS_FILE_NAME, MAX_SETTINGS_BYTES, MAX_VIEW_STATE_BYTES, MAX_DB_UI_BYTES, MAX_TOOL_DRAFT_LEN, MAX_TOOLS_BYTES, MAX_REF_LEN, MAX_KEY_LEN, MAX_VIEW_ITEMS, MAX_GREP_SELECTION_HISTORY_ITEMS, MAX_RECENT_REFS, MAX_DB_UI_DBS, MAX_DB_UI_TABLES, MAX_DB_UI_COLUMNS, MAX_DB_UI_EXPANDED_SCOPES, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore, toolsStore;
|
|
18130
18423
|
var init_state_store = __esm({
|
|
18131
18424
|
"web-src/server/state-store.ts"() {
|
|
18132
18425
|
init_keymap();
|
|
@@ -18149,6 +18442,7 @@ var init_state_store = __esm({
|
|
|
18149
18442
|
MAX_KEY_LEN = 2048;
|
|
18150
18443
|
MAX_VIEW_ITEMS = 2e4;
|
|
18151
18444
|
MAX_GREP_SELECTION_HISTORY_ITEMS = 100;
|
|
18445
|
+
MAX_RECENT_REFS = 8;
|
|
18152
18446
|
MAX_DB_UI_DBS = 200;
|
|
18153
18447
|
MAX_DB_UI_TABLES = 500;
|
|
18154
18448
|
MAX_DB_UI_COLUMNS = 1e3;
|
|
@@ -27708,6 +28002,7 @@ function isTestFilePath(path) {
|
|
|
27708
28002
|
var TEST_FILE_PATH_RE;
|
|
27709
28003
|
var init_file_filter = __esm({
|
|
27710
28004
|
"web-src/core/file-filter.ts"() {
|
|
28005
|
+
init_fuzzy_search();
|
|
27711
28006
|
TEST_FILE_PATH_RE = /(^|[/_.])(test|spec|__tests__)([/_.]|$)/i;
|
|
27712
28007
|
}
|
|
27713
28008
|
});
|
|
@@ -27763,16 +28058,65 @@ function filterCallerPaths(env, paths, excludeTests) {
|
|
|
27763
28058
|
(path) => isSafePath(path) && !isGitInternalPath(path) && !isSkippableSearchPath(path, env.omitDirNames, env.excludeNames) && (!excludeTests || !isTestFilePath(path))
|
|
27764
28059
|
);
|
|
27765
28060
|
}
|
|
27766
|
-
|
|
27767
|
-
const
|
|
27768
|
-
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
28061
|
+
function splitCallerPaths(env, paths, excludeTests) {
|
|
28062
|
+
const plain = [];
|
|
28063
|
+
const globs = [];
|
|
28064
|
+
for (const path of paths) {
|
|
28065
|
+
if (!isSafePath(path) || isGitInternalPath(path)) continue;
|
|
28066
|
+
if (isGlobPathQuery(path)) {
|
|
28067
|
+
globs.push(path);
|
|
28068
|
+
continue;
|
|
28069
|
+
}
|
|
28070
|
+
if (isSkippableSearchPath(path, env.omitDirNames, env.excludeNames) || excludeTests && isTestFilePath(path))
|
|
28071
|
+
continue;
|
|
28072
|
+
plain.push(path);
|
|
28073
|
+
}
|
|
28074
|
+
return { plain, globs };
|
|
28075
|
+
}
|
|
28076
|
+
function matchesAnyGlob(path, globs) {
|
|
28077
|
+
return globs.length === 0 || globs.some((glob) => !!globMatchPath(glob, path));
|
|
28078
|
+
}
|
|
28079
|
+
async function fallbackCandidatePaths(env, paths) {
|
|
28080
|
+
if (paths.length === 0) {
|
|
28081
|
+
const tree = await listTreeAsync("worktree", "", env.cwd, {
|
|
28082
|
+
recursive: true,
|
|
28083
|
+
omitDirNames: env.omitDirNames,
|
|
28084
|
+
excludeNames: env.excludeNames
|
|
28085
|
+
});
|
|
28086
|
+
return tree.entries.map((entry) => entry.path);
|
|
28087
|
+
}
|
|
28088
|
+
const candidates = [];
|
|
28089
|
+
for (const path of paths) {
|
|
28090
|
+
const full = safeWorktreePath(env, path);
|
|
28091
|
+
if (!full) continue;
|
|
28092
|
+
let stat4;
|
|
28093
|
+
try {
|
|
28094
|
+
stat4 = await lstat2(full);
|
|
28095
|
+
} catch {
|
|
28096
|
+
continue;
|
|
28097
|
+
}
|
|
28098
|
+
if (!stat4.isDirectory()) {
|
|
28099
|
+
candidates.push(path);
|
|
28100
|
+
continue;
|
|
28101
|
+
}
|
|
28102
|
+
const tree = await listTreeAsync("worktree", path, env.cwd, {
|
|
28103
|
+
recursive: true,
|
|
28104
|
+
omitDirNames: env.omitDirNames,
|
|
28105
|
+
excludeNames: env.excludeNames
|
|
28106
|
+
});
|
|
28107
|
+
for (const entry of tree.entries) {
|
|
28108
|
+
if (entry.type === "blob") candidates.push(entry.path);
|
|
28109
|
+
}
|
|
28110
|
+
}
|
|
28111
|
+
return candidates;
|
|
28112
|
+
}
|
|
28113
|
+
async function grepWorktreeFallback(env, query, max, paths, globs, excludeTests, options, signal) {
|
|
28114
|
+
const candidates = await fallbackCandidatePaths(env, paths);
|
|
27772
28115
|
const matches = [];
|
|
27773
28116
|
for (const path of candidates) {
|
|
27774
28117
|
if (matches.length >= max) break;
|
|
27775
|
-
|
|
28118
|
+
throwIfAborted(signal, "grep aborted");
|
|
28119
|
+
if (!isSafePath(path) || isGitInternalPath(path) || isSkippableSearchPath(path, env.omitDirNames, env.excludeNames) || excludeTests && isTestFilePath(path) || !matchesAnyGlob(path, globs))
|
|
27776
28120
|
continue;
|
|
27777
28121
|
const full = safeWorktreePath(env, path);
|
|
27778
28122
|
if (!full) continue;
|
|
@@ -27796,16 +28140,24 @@ async function grepWorktreeFallback(env, query, max, paths, excludeTests) {
|
|
|
27796
28140
|
path,
|
|
27797
28141
|
data.toString("utf8"),
|
|
27798
28142
|
query,
|
|
27799
|
-
max - matches.length
|
|
28143
|
+
max - matches.length,
|
|
28144
|
+
options
|
|
27800
28145
|
)
|
|
27801
28146
|
);
|
|
27802
28147
|
}
|
|
27803
28148
|
return matches;
|
|
27804
28149
|
}
|
|
28150
|
+
function matchOptions(req) {
|
|
28151
|
+
return {
|
|
28152
|
+
caseSensitive: req.caseSensitive === true,
|
|
28153
|
+
wholeWord: req.wholeWord === true
|
|
28154
|
+
};
|
|
28155
|
+
}
|
|
27805
28156
|
async function grepWorktreeAsync(env, req) {
|
|
27806
28157
|
const excludeTests = req.excludeTests === true;
|
|
27807
|
-
const
|
|
27808
|
-
|
|
28158
|
+
const { plain, globs } = splitCallerPaths(env, req.paths, excludeTests);
|
|
28159
|
+
const paths = filterCallerPaths(env, plain, excludeTests);
|
|
28160
|
+
if (req.paths.length > 0 && paths.length === 0 && globs.length === 0) {
|
|
27809
28161
|
return {
|
|
27810
28162
|
ref: "worktree",
|
|
27811
28163
|
engine: "fallback",
|
|
@@ -27815,7 +28167,7 @@ async function grepWorktreeAsync(env, req) {
|
|
|
27815
28167
|
}
|
|
27816
28168
|
if (await rgAvailableAsync(env.cwd)) {
|
|
27817
28169
|
const safePaths = paths.filter((path) => safeWorktreePath(env, path));
|
|
27818
|
-
if (
|
|
28170
|
+
if (plain.length > 0 && safePaths.length === 0) {
|
|
27819
28171
|
return {
|
|
27820
28172
|
ref: "worktree",
|
|
27821
28173
|
engine: "rg",
|
|
@@ -27830,13 +28182,15 @@ async function grepWorktreeAsync(env, req) {
|
|
|
27830
28182
|
req.regex,
|
|
27831
28183
|
env.omitDirNames,
|
|
27832
28184
|
env.excludeNames,
|
|
27833
|
-
excludeTests
|
|
28185
|
+
excludeTests,
|
|
28186
|
+
{ ...matchOptions(req), pathGlobs: globs }
|
|
27834
28187
|
);
|
|
27835
28188
|
const proc = await spawnTextAsync({
|
|
27836
28189
|
command: commandForExternal("rg"),
|
|
27837
28190
|
args: args.slice(1),
|
|
27838
28191
|
cwd: env.cwd,
|
|
27839
28192
|
timeoutMs: 5e3,
|
|
28193
|
+
signal: req.signal,
|
|
27840
28194
|
abortMessage: "grep aborted",
|
|
27841
28195
|
timeoutMessage: "grep timed out after 5000ms",
|
|
27842
28196
|
rejectOnError: false
|
|
@@ -27874,7 +28228,10 @@ async function grepWorktreeAsync(env, req) {
|
|
|
27874
28228
|
req.query,
|
|
27875
28229
|
req.max,
|
|
27876
28230
|
paths,
|
|
27877
|
-
|
|
28231
|
+
globs,
|
|
28232
|
+
excludeTests,
|
|
28233
|
+
matchOptions(req),
|
|
28234
|
+
req.signal
|
|
27878
28235
|
);
|
|
27879
28236
|
return {
|
|
27880
28237
|
ref: "worktree",
|
|
@@ -27885,8 +28242,9 @@ async function grepWorktreeAsync(env, req) {
|
|
|
27885
28242
|
}
|
|
27886
28243
|
async function grepTreeRefAsync(env, req) {
|
|
27887
28244
|
const excludeTests = req.excludeTests === true;
|
|
27888
|
-
const
|
|
27889
|
-
|
|
28245
|
+
const { plain, globs } = splitCallerPaths(env, req.paths, excludeTests);
|
|
28246
|
+
const safePaths = filterCallerPaths(env, plain, excludeTests);
|
|
28247
|
+
if (req.paths.length > 0 && safePaths.length === 0 && globs.length === 0) {
|
|
27890
28248
|
return {
|
|
27891
28249
|
ref: req.ref,
|
|
27892
28250
|
engine: "git",
|
|
@@ -27894,26 +28252,35 @@ async function grepTreeRefAsync(env, req) {
|
|
|
27894
28252
|
matches: []
|
|
27895
28253
|
};
|
|
27896
28254
|
}
|
|
28255
|
+
const options = matchOptions(req);
|
|
28256
|
+
const pathspecs = [
|
|
28257
|
+
...safePaths,
|
|
28258
|
+
...globs.map(
|
|
28259
|
+
(glob) => glob.includes("/") ? `:(glob)${glob}` : `:(glob)**/${glob}`
|
|
28260
|
+
)
|
|
28261
|
+
];
|
|
27897
28262
|
const args = [
|
|
27898
28263
|
"-c",
|
|
27899
28264
|
"core.quotepath=false",
|
|
27900
28265
|
"grep",
|
|
27901
28266
|
"-n",
|
|
27902
28267
|
"--column",
|
|
27903
|
-
"-i",
|
|
28268
|
+
...options.caseSensitive ? [] : ["-i"],
|
|
28269
|
+
...options.wholeWord ? ["-w"] : [],
|
|
27904
28270
|
req.regex ? "-E" : "-F",
|
|
27905
28271
|
"--no-color",
|
|
27906
28272
|
"-e",
|
|
27907
28273
|
req.query,
|
|
27908
28274
|
req.ref,
|
|
27909
28275
|
"--",
|
|
27910
|
-
...
|
|
28276
|
+
...pathspecs
|
|
27911
28277
|
];
|
|
27912
28278
|
const proc = await spawnTextAsync({
|
|
27913
28279
|
command: commandForExternal("git"),
|
|
27914
28280
|
args,
|
|
27915
28281
|
cwd: env.cwd,
|
|
27916
28282
|
timeoutMs: 5e3,
|
|
28283
|
+
signal: req.signal,
|
|
27917
28284
|
abortMessage: "git grep aborted",
|
|
27918
28285
|
timeoutMessage: "git grep timed out after 5000ms",
|
|
27919
28286
|
rejectOnError: false
|
|
@@ -27993,7 +28360,9 @@ var rgAvailableCache;
|
|
|
27993
28360
|
var init_search_service = __esm({
|
|
27994
28361
|
"web-src/server/search-service.ts"() {
|
|
27995
28362
|
init_file_filter();
|
|
28363
|
+
init_fuzzy_search();
|
|
27996
28364
|
init_command_resolver();
|
|
28365
|
+
init_abort();
|
|
27997
28366
|
init_spawn_runner();
|
|
27998
28367
|
init_git();
|
|
27999
28368
|
init_name_pattern();
|
|
@@ -29512,7 +29881,7 @@ function defaultMcpTools(options = {}) {
|
|
|
29512
29881
|
{
|
|
29513
29882
|
name: "code_viewer_file_history",
|
|
29514
29883
|
title: "code-viewer file history",
|
|
29515
|
-
description: "Returns the same JSON payload `code-viewer file history --json` emits: path, ref, limit, skip, optional query, and result (commits with sha/author/when/subject, hasMore, optional error). Read-only. ref defaults to 'HEAD'. Follows renames for file paths.
|
|
29884
|
+
description: "Returns the same JSON payload `code-viewer file history --json` emits: path, ref, limit, skip, optional query, and result (commits with sha/author/when/subject/refs, hasMore, optional error). Read-only. ref defaults to 'HEAD'. Follows renames for file paths. `query` uses the browser history filter syntax: free words (message), author:<name>, path:<part>, since:/after:<date>, until:/before:<date>, code:<text> (git log -S), merges:no / merges:only; kinds AND together and a lone hex word tries a sha prefix.",
|
|
29516
29885
|
inputSchema: {
|
|
29517
29886
|
type: "object",
|
|
29518
29887
|
properties: {
|
|
@@ -29537,7 +29906,7 @@ function defaultMcpTools(options = {}) {
|
|
|
29537
29906
|
},
|
|
29538
29907
|
query: {
|
|
29539
29908
|
type: "string",
|
|
29540
|
-
description: "Optional git log filter
|
|
29909
|
+
description: "Optional git log filter in the browser history syntax: free words, author:<name>, path:<part>, since:/after:<date>, until:/before:<date>, code:<text>, merges:no|only. Single-line, no NUL."
|
|
29541
29910
|
},
|
|
29542
29911
|
cwd: {
|
|
29543
29912
|
type: "string",
|
|
@@ -29666,12 +30035,20 @@ function defaultMcpTools(options = {}) {
|
|
|
29666
30035
|
paths: {
|
|
29667
30036
|
type: "array",
|
|
29668
30037
|
items: { type: "string" },
|
|
29669
|
-
description: "Optional list of repo-relative
|
|
30038
|
+
description: "Optional list of repo-relative files, directories, or globs (e.g. 'src/**/*.ts') to restrict the search to. Unsafe paths are dropped silently."
|
|
29670
30039
|
},
|
|
29671
30040
|
regex: {
|
|
29672
30041
|
type: "boolean",
|
|
29673
30042
|
description: "Treat `term` as an extended regex instead of a fixed string. Defaults to false."
|
|
29674
30043
|
},
|
|
30044
|
+
caseSensitive: {
|
|
30045
|
+
type: "boolean",
|
|
30046
|
+
description: "Match case. Defaults to false (case-insensitive on every engine)."
|
|
30047
|
+
},
|
|
30048
|
+
wholeWord: {
|
|
30049
|
+
type: "boolean",
|
|
30050
|
+
description: "Match whole words only (rg -w / git grep -w semantics). Defaults to false."
|
|
30051
|
+
},
|
|
29675
30052
|
max: {
|
|
29676
30053
|
type: "integer",
|
|
29677
30054
|
minimum: 1,
|
|
@@ -30574,6 +30951,14 @@ async function runSearchCodeTool(input, defaultCwd, generation2) {
|
|
|
30574
30951
|
return { text: "regex must be a boolean", isError: true };
|
|
30575
30952
|
}
|
|
30576
30953
|
const regex = regexRaw === true;
|
|
30954
|
+
const caseSensitiveRaw = params.caseSensitive;
|
|
30955
|
+
if (caseSensitiveRaw !== void 0 && typeof caseSensitiveRaw !== "boolean") {
|
|
30956
|
+
return { text: "caseSensitive must be a boolean", isError: true };
|
|
30957
|
+
}
|
|
30958
|
+
const wholeWordRaw = params.wholeWord;
|
|
30959
|
+
if (wholeWordRaw !== void 0 && typeof wholeWordRaw !== "boolean") {
|
|
30960
|
+
return { text: "wholeWord must be a boolean", isError: true };
|
|
30961
|
+
}
|
|
30577
30962
|
const maxParsed = validateMcpIntegerLimit(
|
|
30578
30963
|
params.max,
|
|
30579
30964
|
GREP_DEFAULT_MAX,
|
|
@@ -30598,6 +30983,8 @@ async function runSearchCodeTool(input, defaultCwd, generation2) {
|
|
|
30598
30983
|
ref: refParsed.ref,
|
|
30599
30984
|
paths,
|
|
30600
30985
|
regex,
|
|
30986
|
+
caseSensitive: caseSensitiveRaw === true,
|
|
30987
|
+
wholeWord: wholeWordRaw === true,
|
|
30601
30988
|
max: maxParsed.value
|
|
30602
30989
|
});
|
|
30603
30990
|
if (result.ok !== true) {
|
|
@@ -33172,7 +33559,9 @@ async function computePayload(extras, range, pathFilter = "", responseGeneration
|
|
|
33172
33559
|
files.push(...untracked.files);
|
|
33173
33560
|
metaError = untracked.error;
|
|
33174
33561
|
}
|
|
33175
|
-
const filteredFiles = pathFilter ? files.filter(
|
|
33562
|
+
const filteredFiles = pathFilter ? pathFilter.endsWith("/") ? files.filter(
|
|
33563
|
+
(file) => file.path.startsWith(pathFilter) || (file.old_path ?? "").startsWith(pathFilter)
|
|
33564
|
+
) : files.filter(
|
|
33176
33565
|
(file) => file.path === pathFilter || file.old_path === pathFilter
|
|
33177
33566
|
) : files;
|
|
33178
33567
|
filteredFiles.sort(
|
|
@@ -33578,7 +33967,7 @@ async function handleFiles2(url) {
|
|
|
33578
33967
|
}
|
|
33579
33968
|
return json2(result.value);
|
|
33580
33969
|
}
|
|
33581
|
-
async function handleGrep(url) {
|
|
33970
|
+
async function handleGrep(url, signal) {
|
|
33582
33971
|
const responseGeneration = generation;
|
|
33583
33972
|
const query = url.searchParams.get("q") || "";
|
|
33584
33973
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
@@ -33591,17 +33980,26 @@ async function handleGrep(url) {
|
|
|
33591
33980
|
const paths = url.searchParams.getAll("path");
|
|
33592
33981
|
const regex = url.searchParams.get("regex") === "1";
|
|
33593
33982
|
const excludeTests = url.searchParams.get("exclude_tests") === "1";
|
|
33594
|
-
const
|
|
33595
|
-
|
|
33596
|
-
|
|
33983
|
+
const caseSensitive = url.searchParams.get("case") === "1";
|
|
33984
|
+
const wholeWord = url.searchParams.get("word") === "1";
|
|
33985
|
+
let result;
|
|
33986
|
+
try {
|
|
33987
|
+
result = await grepRepoAsync(currentSearchEnv(omitDirNames, excludeNames), {
|
|
33597
33988
|
query,
|
|
33598
33989
|
ref,
|
|
33599
33990
|
paths,
|
|
33600
33991
|
regex,
|
|
33601
33992
|
max,
|
|
33602
|
-
excludeTests
|
|
33603
|
-
|
|
33604
|
-
|
|
33993
|
+
excludeTests,
|
|
33994
|
+
caseSensitive,
|
|
33995
|
+
wholeWord,
|
|
33996
|
+
signal
|
|
33997
|
+
});
|
|
33998
|
+
} catch (err) {
|
|
33999
|
+
if (isAbortLikeError(err, signal))
|
|
34000
|
+
return text("client closed request", 499);
|
|
34001
|
+
throw err;
|
|
34002
|
+
}
|
|
33605
34003
|
if (result.ok !== true) return text(result.error, result.status ?? 400);
|
|
33606
34004
|
return json2({ ...result.value, generation: responseGeneration });
|
|
33607
34005
|
}
|
|
@@ -33615,6 +34013,27 @@ async function handleRefCommits(url) {
|
|
|
33615
34013
|
if (result.error) return text(result.error, result.status ?? 500);
|
|
33616
34014
|
return json2({ commits: result.commits, hasMore: result.hasMore });
|
|
33617
34015
|
}
|
|
34016
|
+
async function handleFileRevisions(url) {
|
|
34017
|
+
const responseGeneration = generation;
|
|
34018
|
+
const path = url.searchParams.get("path") || "";
|
|
34019
|
+
if (!path || !safePath(path)) return text("invalid path", 400);
|
|
34020
|
+
if (isGitInternalPath(path)) return text("forbidden", 403);
|
|
34021
|
+
const ref = url.searchParams.get("ref") || "HEAD";
|
|
34022
|
+
const result = await fileRevisionNeighborsAsync(cwd, { path, ref });
|
|
34023
|
+
if (result.error) return text(result.error, result.status ?? 400);
|
|
34024
|
+
return json2({
|
|
34025
|
+
previous: result.previous,
|
|
34026
|
+
next: result.next,
|
|
34027
|
+
generation: responseGeneration
|
|
34028
|
+
});
|
|
34029
|
+
}
|
|
34030
|
+
async function handleAuthors(url) {
|
|
34031
|
+
const responseGeneration = generation;
|
|
34032
|
+
const ref = url.searchParams.get("ref") || "HEAD";
|
|
34033
|
+
const result = await commitAuthorsAsync(cwd, ref);
|
|
34034
|
+
if (result.error) return text(result.error, result.status ?? 400);
|
|
34035
|
+
return json2({ authors: result.authors, generation: responseGeneration });
|
|
34036
|
+
}
|
|
33618
34037
|
async function handleLog(url) {
|
|
33619
34038
|
const responseGeneration = generation;
|
|
33620
34039
|
const ref = url.searchParams.get("ref") || "HEAD";
|
|
@@ -33622,12 +34041,14 @@ async function handleLog(url) {
|
|
|
33622
34041
|
const limit = Number(url.searchParams.get("limit") || "50");
|
|
33623
34042
|
const path = url.searchParams.get("path") || "";
|
|
33624
34043
|
if (path && !safePath(path)) return text("invalid path", 400);
|
|
34044
|
+
const lines = parseHistoryLineRange(url.searchParams.get("lines"));
|
|
33625
34045
|
const result = await commitHistoryAsync(cwd, {
|
|
33626
34046
|
ref,
|
|
33627
34047
|
skip: Number.isFinite(skip) ? skip : 0,
|
|
33628
34048
|
limit: Number.isFinite(limit) ? limit : 50,
|
|
33629
34049
|
query: url.searchParams.get("q") || "",
|
|
33630
|
-
...path ? { path } : {}
|
|
34050
|
+
...path ? { path } : {},
|
|
34051
|
+
...path && lines ? { lines } : {}
|
|
33631
34052
|
});
|
|
33632
34053
|
if (result.error) return text(result.error, result.status ?? 400);
|
|
33633
34054
|
const wantsWorktreeHead = path && skip === 0 && (ref === "worktree" || url.searchParams.get("worktree") === "1");
|
|
@@ -33646,7 +34067,8 @@ async function handleLog(url) {
|
|
|
33646
34067
|
author: "",
|
|
33647
34068
|
when: "",
|
|
33648
34069
|
parents: [],
|
|
33649
|
-
body: ""
|
|
34070
|
+
body: "",
|
|
34071
|
+
refs: []
|
|
33650
34072
|
},
|
|
33651
34073
|
...commits
|
|
33652
34074
|
];
|
|
@@ -34983,11 +35405,13 @@ var init_preview = __esm({
|
|
|
34983
35405
|
async "web-src/server/preview.ts"() {
|
|
34984
35406
|
init_directory_name();
|
|
34985
35407
|
init_error_detail();
|
|
35408
|
+
init_history();
|
|
34986
35409
|
init_journal();
|
|
34987
35410
|
init_routes();
|
|
34988
35411
|
init_annotations();
|
|
34989
35412
|
init_cache();
|
|
34990
35413
|
init_command_resolver();
|
|
35414
|
+
init_abort();
|
|
34991
35415
|
init_dev_assets();
|
|
34992
35416
|
init_doctor();
|
|
34993
35417
|
init_file_upload();
|
|
@@ -35124,9 +35548,12 @@ var init_preview = __esm({
|
|
|
35124
35548
|
});
|
|
35125
35549
|
if (url.pathname === "/_tree") return await handleTree(url);
|
|
35126
35550
|
if (url.pathname === "/_files") return await handleFiles2(url);
|
|
35127
|
-
if (url.pathname === "/_grep") return await handleGrep(url);
|
|
35551
|
+
if (url.pathname === "/_grep") return await handleGrep(url, req.signal);
|
|
35128
35552
|
if (url.pathname === "/_commits") return await handleRefCommits(url);
|
|
35129
35553
|
if (url.pathname === "/_log") return await handleLog(url);
|
|
35554
|
+
if (url.pathname === "/_authors") return await handleAuthors(url);
|
|
35555
|
+
if (url.pathname === "/_file_revisions")
|
|
35556
|
+
return await handleFileRevisions(url);
|
|
35130
35557
|
if (url.pathname === "/_file_blame") return await handleFileBlame(url);
|
|
35131
35558
|
if (url.pathname === "/file_diff") return await handleFileDiff(url);
|
|
35132
35559
|
if (url.pathname === "/file_range") return handleFileRange(url);
|