@youtyan/code-viewer 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code-viewer.js +371 -115
- package/package.json +1 -1
- package/web/app.js +292 -117
- package/web/style.css +3 -0
package/dist/code-viewer.js
CHANGED
|
@@ -96,7 +96,7 @@ var init_sqlite_driver = __esm(() => {
|
|
|
96
96
|
|
|
97
97
|
// web-src/server/json-store.ts
|
|
98
98
|
import { randomBytes } from "node:crypto";
|
|
99
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
99
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
100
100
|
import { dirname } from "node:path";
|
|
101
101
|
function isEnoent(err) {
|
|
102
102
|
return err?.code === "ENOENT";
|
|
@@ -140,8 +140,15 @@ function createJsonFileStore(options) {
|
|
|
140
140
|
}
|
|
141
141
|
await mkdir(dirname(file), { recursive: true });
|
|
142
142
|
const tmp = tmpPath(file);
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
try {
|
|
144
|
+
await writeFile(tmp, content, "utf8");
|
|
145
|
+
await rename(tmp, file);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
await unlink(tmp).catch(() => {
|
|
148
|
+
return;
|
|
149
|
+
});
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
145
152
|
}
|
|
146
153
|
async function load(root) {
|
|
147
154
|
const pendingWrite = queues.get(options.filePath(root));
|
|
@@ -841,6 +848,7 @@ function runSync(args, cwd, options = {}) {
|
|
|
841
848
|
encoding: "buffer",
|
|
842
849
|
stdio: ["ignore", "pipe", "pipe"],
|
|
843
850
|
timeout: options.timeout,
|
|
851
|
+
maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
|
|
844
852
|
killSignal: "SIGKILL"
|
|
845
853
|
});
|
|
846
854
|
return {
|
|
@@ -849,20 +857,6 @@ function runSync(args, cwd, options = {}) {
|
|
|
849
857
|
stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
|
|
850
858
|
};
|
|
851
859
|
}
|
|
852
|
-
function runBytesSync(args, cwd, options = {}) {
|
|
853
|
-
const proc = spawnSync(args[0], args.slice(1), {
|
|
854
|
-
cwd,
|
|
855
|
-
encoding: "buffer",
|
|
856
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
857
|
-
timeout: options.timeout,
|
|
858
|
-
killSignal: "SIGKILL"
|
|
859
|
-
});
|
|
860
|
-
return {
|
|
861
|
-
code: proc.status ?? (proc.error ? 1 : 0),
|
|
862
|
-
stdout: new Uint8Array(proc.stdout || new Uint8Array),
|
|
863
|
-
stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
860
|
function spawnDetached(args) {
|
|
867
861
|
const child = spawn(args[0], args.slice(1), {
|
|
868
862
|
detached: true,
|
|
@@ -1035,10 +1029,13 @@ var init_runtime = () => {};
|
|
|
1035
1029
|
|
|
1036
1030
|
// web-src/server/git.ts
|
|
1037
1031
|
import {
|
|
1032
|
+
closeSync,
|
|
1038
1033
|
existsSync,
|
|
1039
1034
|
lstatSync,
|
|
1035
|
+
openSync,
|
|
1040
1036
|
readdirSync,
|
|
1041
1037
|
readFileSync,
|
|
1038
|
+
readSync,
|
|
1042
1039
|
statSync as statSync2
|
|
1043
1040
|
} from "node:fs";
|
|
1044
1041
|
import { join as join3 } from "node:path";
|
|
@@ -1061,11 +1058,6 @@ function run(args, cwd) {
|
|
|
1061
1058
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1062
1059
|
});
|
|
1063
1060
|
}
|
|
1064
|
-
function runBytes(args, cwd) {
|
|
1065
|
-
return runBytesSync(resolveGitArgs(args), cwd, {
|
|
1066
|
-
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
1061
|
function resolveGitArgs(args) {
|
|
1070
1062
|
if (args[0] !== "git")
|
|
1071
1063
|
return args;
|
|
@@ -1135,9 +1127,6 @@ function statusPorcelainForPath(path, cwd) {
|
|
|
1135
1127
|
function show(ref, path, cwd) {
|
|
1136
1128
|
return run(["git", "show", `${ref}:${path}`], cwd);
|
|
1137
1129
|
}
|
|
1138
|
-
function showBytes(ref, path, cwd) {
|
|
1139
|
-
return runBytes(["git", "show", `${ref}:${path}`], cwd);
|
|
1140
|
-
}
|
|
1141
1130
|
function catFileBlobStream(oid, cwd) {
|
|
1142
1131
|
return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
|
|
1143
1132
|
}
|
|
@@ -1961,21 +1950,19 @@ function listTreeResult(ref, path, cwd, options = {}) {
|
|
|
1961
1950
|
function untrackedMeta(cwd) {
|
|
1962
1951
|
return untracked(cwd).flatMap((path) => {
|
|
1963
1952
|
const full = join3(cwd, path);
|
|
1964
|
-
let binary = false;
|
|
1965
|
-
let lines = 0;
|
|
1966
1953
|
let fileExists = false;
|
|
1967
1954
|
try {
|
|
1968
1955
|
fileExists = existsSync(full) && statSync2(full).isFile();
|
|
1969
1956
|
} catch {
|
|
1970
1957
|
fileExists = false;
|
|
1971
1958
|
}
|
|
1959
|
+
let scan;
|
|
1972
1960
|
if (fileExists) {
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
`).length - 1;
|
|
1961
|
+
try {
|
|
1962
|
+
scan = scanFileBinaryAndNewlines(full);
|
|
1963
|
+
} catch {
|
|
1964
|
+
return [];
|
|
1965
|
+
}
|
|
1979
1966
|
} else {
|
|
1980
1967
|
return [];
|
|
1981
1968
|
}
|
|
@@ -1983,14 +1970,40 @@ function untrackedMeta(cwd) {
|
|
|
1983
1970
|
{
|
|
1984
1971
|
path,
|
|
1985
1972
|
status: "A",
|
|
1986
|
-
additions: binary ? 0 :
|
|
1973
|
+
additions: scan.binary ? 0 : scan.newlines,
|
|
1987
1974
|
deletions: 0,
|
|
1988
|
-
binary,
|
|
1975
|
+
binary: scan.binary,
|
|
1989
1976
|
untracked: true
|
|
1990
1977
|
}
|
|
1991
1978
|
];
|
|
1992
1979
|
});
|
|
1993
1980
|
}
|
|
1981
|
+
function scanFileBinaryAndNewlines(full) {
|
|
1982
|
+
const fd = openSync(full, "r");
|
|
1983
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
1984
|
+
let newlines = 0;
|
|
1985
|
+
let inspected = 0;
|
|
1986
|
+
try {
|
|
1987
|
+
while (true) {
|
|
1988
|
+
const read = readSync(fd, buffer, 0, buffer.length, null);
|
|
1989
|
+
if (read <= 0)
|
|
1990
|
+
break;
|
|
1991
|
+
const binaryProbeBytes = Math.min(read, Math.max(0, 8192 - inspected));
|
|
1992
|
+
for (let i = 0;i < binaryProbeBytes; i++) {
|
|
1993
|
+
if (buffer[i] === 0)
|
|
1994
|
+
return { binary: true, newlines: 0 };
|
|
1995
|
+
}
|
|
1996
|
+
inspected += read;
|
|
1997
|
+
for (let i = 0;i < read; i++) {
|
|
1998
|
+
if (buffer[i] === 10)
|
|
1999
|
+
newlines++;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
} finally {
|
|
2003
|
+
closeSync(fd);
|
|
2004
|
+
}
|
|
2005
|
+
return { binary: false, newlines };
|
|
2006
|
+
}
|
|
1994
2007
|
function fileMetaResult(args, cwd, includeUntracked = false) {
|
|
1995
2008
|
const ns = nameStatusResult(args, cwd);
|
|
1996
2009
|
if (ns.error)
|
|
@@ -2052,13 +2065,17 @@ function untrackedFileDiff(extras, path, cwd) {
|
|
|
2052
2065
|
function splitHunks(diffText) {
|
|
2053
2066
|
if (!diffText)
|
|
2054
2067
|
return { header: "", hunks: [] };
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2057
|
-
|
|
2068
|
+
const startsWithHunk = diffText.startsWith("@@");
|
|
2069
|
+
const first = startsWithHunk ? 0 : diffText.indexOf(`
|
|
2070
|
+
@@`);
|
|
2071
|
+
if (first < 0)
|
|
2072
|
+
return { header: diffText, hunks: [] };
|
|
2073
|
+
const hunkStart = startsWithHunk ? 0 : first + 1;
|
|
2074
|
+
if (hunkStart >= diffText.length)
|
|
2058
2075
|
return { header: diffText, hunks: [] };
|
|
2059
|
-
const header = diffText.slice(0,
|
|
2076
|
+
const header = diffText.slice(0, hunkStart);
|
|
2060
2077
|
const hunks = [];
|
|
2061
|
-
let cur =
|
|
2078
|
+
let cur = hunkStart;
|
|
2062
2079
|
while (cur < diffText.length) {
|
|
2063
2080
|
const next = diffText.indexOf(`
|
|
2064
2081
|
@@`, cur + 1);
|
|
@@ -7839,11 +7856,106 @@ function computeFuzzyMatch(query, path) {
|
|
|
7839
7856
|
tier
|
|
7840
7857
|
};
|
|
7841
7858
|
}
|
|
7842
|
-
function rankFuzzyPaths(query, items) {
|
|
7843
|
-
|
|
7859
|
+
function rankFuzzyPaths(query, items, limit) {
|
|
7860
|
+
const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
|
|
7861
|
+
const compare = (a, b) => b.tier - a.tier || b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
7862
|
+
if (!bounded) {
|
|
7863
|
+
return items.map((item) => {
|
|
7864
|
+
const match = computeFuzzyMatch(query, item.path);
|
|
7865
|
+
return match ? { item, score: match.score, ranges: match.ranges, tier: match.tier } : null;
|
|
7866
|
+
}).filter((item) => item !== null).sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
7867
|
+
}
|
|
7868
|
+
const top = [];
|
|
7869
|
+
for (const item of items) {
|
|
7844
7870
|
const match = computeFuzzyMatch(query, item.path);
|
|
7845
|
-
|
|
7846
|
-
|
|
7871
|
+
if (!match)
|
|
7872
|
+
continue;
|
|
7873
|
+
const ranked = {
|
|
7874
|
+
item,
|
|
7875
|
+
score: match.score,
|
|
7876
|
+
ranges: match.ranges,
|
|
7877
|
+
tier: match.tier
|
|
7878
|
+
};
|
|
7879
|
+
pushBoundedTop(top, ranked, bounded, compare);
|
|
7880
|
+
}
|
|
7881
|
+
return top.sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
7882
|
+
}
|
|
7883
|
+
function pushBoundedTop(heap, item, limit, compareBestFirst) {
|
|
7884
|
+
const isWorse = (a, b) => compareBestFirst(a, b) > 0;
|
|
7885
|
+
const siftUp = (index) => {
|
|
7886
|
+
while (index > 0) {
|
|
7887
|
+
const parent = Math.floor((index - 1) / 2);
|
|
7888
|
+
if (!isWorse(heap[index], heap[parent]))
|
|
7889
|
+
break;
|
|
7890
|
+
[heap[index], heap[parent]] = [heap[parent], heap[index]];
|
|
7891
|
+
index = parent;
|
|
7892
|
+
}
|
|
7893
|
+
};
|
|
7894
|
+
const siftDown = (index) => {
|
|
7895
|
+
while (true) {
|
|
7896
|
+
const left = index * 2 + 1;
|
|
7897
|
+
const right = left + 1;
|
|
7898
|
+
let worst = index;
|
|
7899
|
+
if (left < heap.length && isWorse(heap[left], heap[worst]))
|
|
7900
|
+
worst = left;
|
|
7901
|
+
if (right < heap.length && isWorse(heap[right], heap[worst]))
|
|
7902
|
+
worst = right;
|
|
7903
|
+
if (worst === index)
|
|
7904
|
+
break;
|
|
7905
|
+
[heap[index], heap[worst]] = [heap[worst], heap[index]];
|
|
7906
|
+
index = worst;
|
|
7907
|
+
}
|
|
7908
|
+
};
|
|
7909
|
+
if (heap.length < limit) {
|
|
7910
|
+
heap.push(item);
|
|
7911
|
+
siftUp(heap.length - 1);
|
|
7912
|
+
return;
|
|
7913
|
+
}
|
|
7914
|
+
if (compareBestFirst(item, heap[0]) >= 0)
|
|
7915
|
+
return;
|
|
7916
|
+
heap[0] = item;
|
|
7917
|
+
siftDown(0);
|
|
7918
|
+
}
|
|
7919
|
+
function rankGlobPathMatches(query, items, limit) {
|
|
7920
|
+
const matchPath = createGlobPathMatcher(query);
|
|
7921
|
+
if (!matchPath)
|
|
7922
|
+
return [];
|
|
7923
|
+
const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
|
|
7924
|
+
const compare = (a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
7925
|
+
if (!bounded) {
|
|
7926
|
+
return items.map((item) => {
|
|
7927
|
+
const match = matchPath(item.path);
|
|
7928
|
+
return match ? {
|
|
7929
|
+
item,
|
|
7930
|
+
score: match.score,
|
|
7931
|
+
ranges: match.ranges,
|
|
7932
|
+
mode: "glob"
|
|
7933
|
+
} : null;
|
|
7934
|
+
}).filter((item) => item !== null).sort(compare);
|
|
7935
|
+
}
|
|
7936
|
+
const top = [];
|
|
7937
|
+
for (const item of items) {
|
|
7938
|
+
const match = matchPath(item.path);
|
|
7939
|
+
if (!match)
|
|
7940
|
+
continue;
|
|
7941
|
+
const ranked = {
|
|
7942
|
+
item,
|
|
7943
|
+
score: match.score,
|
|
7944
|
+
ranges: match.ranges,
|
|
7945
|
+
mode: "glob"
|
|
7946
|
+
};
|
|
7947
|
+
pushBoundedTop(top, ranked, bounded, compare);
|
|
7948
|
+
}
|
|
7949
|
+
return top.sort(compare);
|
|
7950
|
+
}
|
|
7951
|
+
function rankPathMatches(query, items, limit) {
|
|
7952
|
+
if (isGlobPathQuery(query)) {
|
|
7953
|
+
return rankGlobPathMatches(query, items, limit);
|
|
7954
|
+
}
|
|
7955
|
+
return rankFuzzyPaths(query, items, limit).map((item) => ({
|
|
7956
|
+
...item,
|
|
7957
|
+
mode: "fuzzy"
|
|
7958
|
+
}));
|
|
7847
7959
|
}
|
|
7848
7960
|
function isGlobPathQuery(query) {
|
|
7849
7961
|
return /[*?]/.test(query.trim());
|
|
@@ -7887,49 +7999,37 @@ function globToRegExp(query) {
|
|
|
7887
7999
|
return null;
|
|
7888
8000
|
}
|
|
7889
8001
|
}
|
|
7890
|
-
function
|
|
8002
|
+
function createGlobPathMatcher(query) {
|
|
7891
8003
|
const regex = globToRegExp(query);
|
|
7892
|
-
|
|
7893
|
-
const basename = path.slice(baseStart);
|
|
7894
|
-
if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
8004
|
+
if (!regex)
|
|
7895
8005
|
return null;
|
|
7896
8006
|
const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
7897
|
-
const
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
const
|
|
7901
|
-
if (
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8007
|
+
const suffix = query.replace(/^\*+/, "").toLowerCase();
|
|
8008
|
+
return (path) => {
|
|
8009
|
+
const baseStart = basenameStart(path);
|
|
8010
|
+
const basename = path.slice(baseStart);
|
|
8011
|
+
if (!regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
8012
|
+
return null;
|
|
8013
|
+
const ranges = [];
|
|
8014
|
+
const lowerPath = path.toLowerCase();
|
|
8015
|
+
for (const part of literal) {
|
|
8016
|
+
const start = lowerPath.indexOf(part.toLowerCase());
|
|
8017
|
+
if (start >= 0)
|
|
8018
|
+
ranges.push({ start, end: start + part.length });
|
|
8019
|
+
}
|
|
8020
|
+
ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
8021
|
+
const mergedRanges = [];
|
|
8022
|
+
for (const range of ranges) {
|
|
8023
|
+
const last = mergedRanges[mergedRanges.length - 1];
|
|
8024
|
+
if (last && last.end >= range.start) {
|
|
8025
|
+
last.end = Math.max(last.end, range.end);
|
|
8026
|
+
} else {
|
|
8027
|
+
mergedRanges.push({ ...range });
|
|
8028
|
+
}
|
|
7912
8029
|
}
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
}
|
|
7917
|
-
function rankPathMatches(query, items) {
|
|
7918
|
-
if (isGlobPathQuery(query)) {
|
|
7919
|
-
return items.map((item) => {
|
|
7920
|
-
const match = globMatchPath(query, item.path);
|
|
7921
|
-
return match ? {
|
|
7922
|
-
item,
|
|
7923
|
-
score: match.score,
|
|
7924
|
-
ranges: match.ranges,
|
|
7925
|
-
mode: "glob"
|
|
7926
|
-
} : null;
|
|
7927
|
-
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
7928
|
-
}
|
|
7929
|
-
return rankFuzzyPaths(query, items).map((item) => ({
|
|
7930
|
-
...item,
|
|
7931
|
-
mode: "fuzzy"
|
|
7932
|
-
}));
|
|
8030
|
+
const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(suffix) ? 50 : 0);
|
|
8031
|
+
return { score, ranges: mergedRanges };
|
|
8032
|
+
};
|
|
7933
8033
|
}
|
|
7934
8034
|
|
|
7935
8035
|
// web-src/server/search.ts
|
|
@@ -8020,9 +8120,11 @@ function parseRgOutput(stdout, max, omitDirNames = [], excludeNames = []) {
|
|
|
8020
8120
|
const matches = [];
|
|
8021
8121
|
for (const line of stdout.split(`
|
|
8022
8122
|
`)) {
|
|
8023
|
-
if (!line
|
|
8123
|
+
if (!line)
|
|
8024
8124
|
continue;
|
|
8025
|
-
|
|
8125
|
+
if (matches.length >= max)
|
|
8126
|
+
break;
|
|
8127
|
+
const parsed = /^(.*?):(\d+):(\d+):(.*)$/.exec(line);
|
|
8026
8128
|
if (!parsed)
|
|
8027
8129
|
continue;
|
|
8028
8130
|
const path = parsed[1];
|
|
@@ -13308,10 +13410,10 @@ function hasControlCharacter(value) {
|
|
|
13308
13410
|
|
|
13309
13411
|
// web-src/server/database/discovery.ts
|
|
13310
13412
|
import {
|
|
13311
|
-
closeSync,
|
|
13413
|
+
closeSync as closeSync2,
|
|
13312
13414
|
existsSync as existsSync6,
|
|
13313
|
-
openSync,
|
|
13314
|
-
readSync,
|
|
13415
|
+
openSync as openSync2,
|
|
13416
|
+
readSync as readSync2,
|
|
13315
13417
|
realpathSync as realpathSync4,
|
|
13316
13418
|
statSync as statSync4
|
|
13317
13419
|
} from "node:fs";
|
|
@@ -13323,11 +13425,11 @@ function isSqliteFile(fullPath) {
|
|
|
13323
13425
|
if (!stat2.isFile() || stat2.size < 16)
|
|
13324
13426
|
return false;
|
|
13325
13427
|
const buf = Buffer.alloc(16);
|
|
13326
|
-
const fd =
|
|
13428
|
+
const fd = openSync2(fullPath, "r");
|
|
13327
13429
|
try {
|
|
13328
|
-
|
|
13430
|
+
readSync2(fd, buf, 0, 16, 0);
|
|
13329
13431
|
} finally {
|
|
13330
|
-
|
|
13432
|
+
closeSync2(fd);
|
|
13331
13433
|
}
|
|
13332
13434
|
return buf.toString("utf8", 0, 16) === SQLITE_MAGIC;
|
|
13333
13435
|
} catch {
|
|
@@ -20643,6 +20745,21 @@ function rgAvailable(cwd) {
|
|
|
20643
20745
|
rgAvailableCache = proc.code === 0;
|
|
20644
20746
|
return rgAvailableCache;
|
|
20645
20747
|
}
|
|
20748
|
+
async function rgAvailableAsync(cwd) {
|
|
20749
|
+
if (rgAvailableCache !== null)
|
|
20750
|
+
return rgAvailableCache;
|
|
20751
|
+
const proc = await spawnTextAsync({
|
|
20752
|
+
command: commandForExternal("rg"),
|
|
20753
|
+
args: ["--version"],
|
|
20754
|
+
cwd,
|
|
20755
|
+
timeoutMs: 5000,
|
|
20756
|
+
abortMessage: "rg version aborted",
|
|
20757
|
+
timeoutMessage: "rg version timed out after 5000ms",
|
|
20758
|
+
rejectOnError: false
|
|
20759
|
+
});
|
|
20760
|
+
rgAvailableCache = proc.code === 0;
|
|
20761
|
+
return rgAvailableCache;
|
|
20762
|
+
}
|
|
20646
20763
|
function isExcludedScopePath(path, excludeNames) {
|
|
20647
20764
|
return path.split(/[\\/]+/).some((part) => excludeNames.some((name) => part.toLowerCase() === name.toLowerCase()));
|
|
20648
20765
|
}
|
|
@@ -20744,6 +20861,44 @@ function grepWorktree(env, req) {
|
|
|
20744
20861
|
matches
|
|
20745
20862
|
};
|
|
20746
20863
|
}
|
|
20864
|
+
async function grepWorktreeAsync(env, req) {
|
|
20865
|
+
const paths = filterCallerPaths(env, req.paths);
|
|
20866
|
+
if (await rgAvailableAsync(env.cwd)) {
|
|
20867
|
+
const safePaths = paths.filter((path) => safeWorktreePath(env, path));
|
|
20868
|
+
const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
|
|
20869
|
+
const proc = await spawnTextAsync({
|
|
20870
|
+
command: commandForExternal("rg"),
|
|
20871
|
+
args: args.slice(1),
|
|
20872
|
+
cwd: env.cwd,
|
|
20873
|
+
timeoutMs: 5000,
|
|
20874
|
+
abortMessage: "grep aborted",
|
|
20875
|
+
timeoutMessage: "grep timed out after 5000ms",
|
|
20876
|
+
rejectOnError: false
|
|
20877
|
+
});
|
|
20878
|
+
const matches2 = parseRgOutput(proc.stdout, req.max, env.omitDirNames, env.excludeNames).filter((match) => isSafePath(match.path) && !isGitInternalPath(match.path) && !isSkippableSearchPath(match.path, env.omitDirNames, env.excludeNames) && !!safeWorktreePath(env, match.path));
|
|
20879
|
+
return {
|
|
20880
|
+
ref: "worktree",
|
|
20881
|
+
engine: "rg",
|
|
20882
|
+
truncated: matches2.length >= req.max,
|
|
20883
|
+
matches: matches2
|
|
20884
|
+
};
|
|
20885
|
+
}
|
|
20886
|
+
if (req.regex) {
|
|
20887
|
+
return {
|
|
20888
|
+
ref: "worktree",
|
|
20889
|
+
engine: "fallback",
|
|
20890
|
+
truncated: false,
|
|
20891
|
+
matches: []
|
|
20892
|
+
};
|
|
20893
|
+
}
|
|
20894
|
+
const matches = grepWorktreeFallback(env, req.query, req.max, paths);
|
|
20895
|
+
return {
|
|
20896
|
+
ref: "worktree",
|
|
20897
|
+
engine: "fallback",
|
|
20898
|
+
truncated: matches.length >= req.max,
|
|
20899
|
+
matches
|
|
20900
|
+
};
|
|
20901
|
+
}
|
|
20747
20902
|
function grepTreeRef(env, req) {
|
|
20748
20903
|
const safePaths = filterCallerPaths(env, req.paths);
|
|
20749
20904
|
const args = [
|
|
@@ -20772,6 +20927,40 @@ function grepTreeRef(env, req) {
|
|
|
20772
20927
|
matches
|
|
20773
20928
|
};
|
|
20774
20929
|
}
|
|
20930
|
+
async function grepTreeRefAsync(env, req) {
|
|
20931
|
+
const safePaths = filterCallerPaths(env, req.paths);
|
|
20932
|
+
const args = [
|
|
20933
|
+
"-c",
|
|
20934
|
+
"core.quotepath=false",
|
|
20935
|
+
"grep",
|
|
20936
|
+
"-n",
|
|
20937
|
+
"--column",
|
|
20938
|
+
"-i",
|
|
20939
|
+
req.regex ? "-E" : "-F",
|
|
20940
|
+
"--no-color",
|
|
20941
|
+
"-e",
|
|
20942
|
+
req.query,
|
|
20943
|
+
req.ref,
|
|
20944
|
+
"--",
|
|
20945
|
+
...safePaths
|
|
20946
|
+
];
|
|
20947
|
+
const proc = await spawnTextAsync({
|
|
20948
|
+
command: commandForExternal("git"),
|
|
20949
|
+
args,
|
|
20950
|
+
cwd: env.cwd,
|
|
20951
|
+
timeoutMs: 5000,
|
|
20952
|
+
abortMessage: "git grep aborted",
|
|
20953
|
+
timeoutMessage: "git grep timed out after 5000ms",
|
|
20954
|
+
rejectOnError: false
|
|
20955
|
+
});
|
|
20956
|
+
const matches = parseGitGrepOutput(proc.stdout, req.ref, req.max, env.omitDirNames, env.excludeNames).slice(0, req.max);
|
|
20957
|
+
return {
|
|
20958
|
+
ref: req.ref,
|
|
20959
|
+
engine: "git",
|
|
20960
|
+
truncated: matches.length >= req.max,
|
|
20961
|
+
matches
|
|
20962
|
+
};
|
|
20963
|
+
}
|
|
20775
20964
|
function grepRepo(env, req) {
|
|
20776
20965
|
const isWorktree = req.ref === "worktree" || req.ref === "";
|
|
20777
20966
|
if (!isWorktree) {
|
|
@@ -20800,6 +20989,34 @@ function grepRepo(env, req) {
|
|
|
20800
20989
|
}
|
|
20801
20990
|
return { ok: true, value: grepTreeRef(env, req) };
|
|
20802
20991
|
}
|
|
20992
|
+
async function grepRepoAsync(env, req) {
|
|
20993
|
+
const isWorktree = req.ref === "worktree" || req.ref === "";
|
|
20994
|
+
if (!isWorktree) {
|
|
20995
|
+
const refCheck = verifyTreeRefResult(req.ref, env.cwd);
|
|
20996
|
+
if (refCheck.ok !== true) {
|
|
20997
|
+
return {
|
|
20998
|
+
ok: false,
|
|
20999
|
+
error: refCheck.error,
|
|
21000
|
+
status: refCheck.status
|
|
21001
|
+
};
|
|
21002
|
+
}
|
|
21003
|
+
}
|
|
21004
|
+
if (!req.query.trim()) {
|
|
21005
|
+
return {
|
|
21006
|
+
ok: true,
|
|
21007
|
+
value: {
|
|
21008
|
+
ref: req.ref,
|
|
21009
|
+
engine: req.ref === "worktree" ? "fallback" : "git",
|
|
21010
|
+
truncated: false,
|
|
21011
|
+
matches: []
|
|
21012
|
+
}
|
|
21013
|
+
};
|
|
21014
|
+
}
|
|
21015
|
+
if (isWorktree) {
|
|
21016
|
+
return { ok: true, value: await grepWorktreeAsync(env, req) };
|
|
21017
|
+
}
|
|
21018
|
+
return { ok: true, value: await grepTreeRefAsync(env, req) };
|
|
21019
|
+
}
|
|
20803
21020
|
function listRepoFiles(env, ref, generation) {
|
|
20804
21021
|
if (ref !== "worktree" && ref !== "") {
|
|
20805
21022
|
const refCheck = verifyTreeRefResult(ref, env.cwd);
|
|
@@ -20829,6 +21046,7 @@ function listRepoFiles(env, ref, generation) {
|
|
|
20829
21046
|
var rgAvailableCache = null;
|
|
20830
21047
|
var init_search_service = __esm(() => {
|
|
20831
21048
|
init_command_resolver();
|
|
21049
|
+
init_spawn_runner();
|
|
20832
21050
|
init_git();
|
|
20833
21051
|
init_runtime();
|
|
20834
21052
|
init_search();
|
|
@@ -22391,12 +22609,12 @@ var init_state_route = __esm(() => {
|
|
|
22391
22609
|
// web-src/server/preview.ts
|
|
22392
22610
|
var exports_preview = {};
|
|
22393
22611
|
import {
|
|
22394
|
-
closeSync as
|
|
22612
|
+
closeSync as closeSync3,
|
|
22395
22613
|
constants as constants3,
|
|
22396
22614
|
existsSync as existsSync8,
|
|
22397
22615
|
lstatSync as lstatSync5,
|
|
22398
22616
|
mkdirSync as mkdirSync4,
|
|
22399
|
-
openSync as
|
|
22617
|
+
openSync as openSync3,
|
|
22400
22618
|
readFileSync as readFileSync9,
|
|
22401
22619
|
realpathSync as realpathSync6,
|
|
22402
22620
|
renameSync,
|
|
@@ -23076,9 +23294,15 @@ function handleFiles2(url) {
|
|
|
23076
23294
|
if (result.ok !== true)
|
|
23077
23295
|
return text(result.error, result.status ?? 400);
|
|
23078
23296
|
fileListCache.set(key, { generation, body: result.value });
|
|
23297
|
+
while (fileListCache.size > MAX_TIMED_CACHE_ENTRIES) {
|
|
23298
|
+
const oldest = fileListCache.keys().next().value;
|
|
23299
|
+
if (oldest === undefined)
|
|
23300
|
+
break;
|
|
23301
|
+
fileListCache.delete(oldest);
|
|
23302
|
+
}
|
|
23079
23303
|
return json2(result.value);
|
|
23080
23304
|
}
|
|
23081
|
-
function handleGrep(url) {
|
|
23305
|
+
async function handleGrep(url) {
|
|
23082
23306
|
const query = url.searchParams.get("q") || "";
|
|
23083
23307
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
23084
23308
|
const max = normalizeGrepMax(url.searchParams.get("max"));
|
|
@@ -23090,7 +23314,7 @@ function handleGrep(url) {
|
|
|
23090
23314
|
const excludeNames = scopeExcludeNamesFromQuery(url);
|
|
23091
23315
|
const paths = url.searchParams.getAll("path");
|
|
23092
23316
|
const regex = url.searchParams.get("regex") === "1";
|
|
23093
|
-
const result =
|
|
23317
|
+
const result = await grepRepoAsync(currentSearchEnv(omitDirNames, excludeNames), {
|
|
23094
23318
|
query,
|
|
23095
23319
|
ref,
|
|
23096
23320
|
paths,
|
|
@@ -23269,16 +23493,16 @@ function handleFileDiff(url) {
|
|
|
23269
23493
|
if (isUntracked) {
|
|
23270
23494
|
const res = untrackedFileDiff(extras, path, cwd);
|
|
23271
23495
|
diffText = res.stdout || "";
|
|
23272
|
-
if (res.code !== 0) {
|
|
23273
|
-
errText = res.stderr;
|
|
23274
|
-
errStatus = res.status;
|
|
23496
|
+
if (res.code !== 0 && !(res.code === 1 && diffText)) {
|
|
23497
|
+
errText = res.stderr || "diff failed";
|
|
23498
|
+
errStatus = res.status ?? 500;
|
|
23275
23499
|
}
|
|
23276
23500
|
} else {
|
|
23277
23501
|
const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
|
|
23278
23502
|
diffText = res.stdout || "";
|
|
23279
23503
|
if (res.code !== 0) {
|
|
23280
|
-
errText = res.stderr;
|
|
23281
|
-
errStatus = res.status;
|
|
23504
|
+
errText = res.stderr || "diff failed";
|
|
23505
|
+
errStatus = res.status ?? 500;
|
|
23282
23506
|
}
|
|
23283
23507
|
}
|
|
23284
23508
|
if (!errText)
|
|
@@ -23454,6 +23678,7 @@ async function handleFileRange(url) {
|
|
|
23454
23678
|
const full = safeWorktreePath2(path);
|
|
23455
23679
|
if (!full)
|
|
23456
23680
|
return text("no file", 404);
|
|
23681
|
+
const responseGeneration = generation;
|
|
23457
23682
|
const result = await collectIndexedWorktreeLineRange(full, start, end);
|
|
23458
23683
|
const body = {
|
|
23459
23684
|
path,
|
|
@@ -23463,10 +23688,11 @@ async function handleFileRange(url) {
|
|
|
23463
23688
|
lines: result.lines,
|
|
23464
23689
|
total: result.total,
|
|
23465
23690
|
complete: result.complete,
|
|
23466
|
-
generation
|
|
23691
|
+
generation: responseGeneration
|
|
23467
23692
|
};
|
|
23468
23693
|
return json2(body);
|
|
23469
23694
|
} else {
|
|
23695
|
+
const responseGeneration = generation;
|
|
23470
23696
|
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
23471
23697
|
if (refCheck.ok !== true)
|
|
23472
23698
|
return text(refCheck.error, refCheck.status ?? 400);
|
|
@@ -23487,34 +23713,64 @@ async function handleFileRange(url) {
|
|
|
23487
23713
|
lines: result.lines,
|
|
23488
23714
|
total: result.total,
|
|
23489
23715
|
complete: result.complete,
|
|
23490
|
-
generation
|
|
23716
|
+
generation: responseGeneration
|
|
23491
23717
|
};
|
|
23492
23718
|
return json2(body);
|
|
23493
23719
|
}
|
|
23494
23720
|
}
|
|
23495
|
-
function handleRawFile(req, url) {
|
|
23721
|
+
async function handleRawFile(req, url) {
|
|
23496
23722
|
const path = url.searchParams.get("path") || "";
|
|
23497
23723
|
if (!safePath(path))
|
|
23498
23724
|
return text("forbidden", 403);
|
|
23499
23725
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
23500
|
-
let body;
|
|
23501
23726
|
if (ref !== "worktree" && ref !== "") {
|
|
23502
23727
|
const refCheck = verifyTreeRefResult(ref, cwd);
|
|
23503
23728
|
if (refCheck.ok !== true)
|
|
23504
23729
|
return text(refCheck.error, refCheck.status ?? 400);
|
|
23505
|
-
const
|
|
23506
|
-
if (
|
|
23730
|
+
const oid = objectId(ref, path, cwd);
|
|
23731
|
+
if (oid.code !== 0 || !oid.oid)
|
|
23507
23732
|
return text("not in ref", 404);
|
|
23733
|
+
const sizeResult = objectByteSize(oid.oid, cwd);
|
|
23734
|
+
if (sizeResult.code !== 0)
|
|
23735
|
+
return text("cannot read ref", 500);
|
|
23736
|
+
const size = sizeResult.size;
|
|
23508
23737
|
const metadata = gitFileMetadata(ref, path, size);
|
|
23738
|
+
const rangeResult = req.headers.get("range") ? parseHttpByteRange(req.headers.get("range"), size) : null;
|
|
23739
|
+
if (rangeResult?.kind === "unsatisfiable") {
|
|
23740
|
+
return new Response(null, {
|
|
23741
|
+
status: 416,
|
|
23742
|
+
headers: {
|
|
23743
|
+
...rawFileHeaders(path, { size, metadata }),
|
|
23744
|
+
"Content-Range": `bytes */${size}`,
|
|
23745
|
+
"Content-Length": "0"
|
|
23746
|
+
}
|
|
23747
|
+
});
|
|
23748
|
+
}
|
|
23749
|
+
if (rangeResult?.kind === "range") {
|
|
23750
|
+
const range = rangeResult.range;
|
|
23751
|
+
if (req.method === "HEAD") {
|
|
23752
|
+
return new Response(null, {
|
|
23753
|
+
status: 206,
|
|
23754
|
+
headers: rawFileHeaders(path, { size, range, metadata })
|
|
23755
|
+
});
|
|
23756
|
+
}
|
|
23757
|
+
const shown2 = catFileBlobStream(oid.oid, cwd);
|
|
23758
|
+
const bytes = await collectByteRangeFromStream(shown2.stream, range.start, range.end + 1);
|
|
23759
|
+
const code = await shown2.exited;
|
|
23760
|
+
if (code !== 0)
|
|
23761
|
+
return text("not in ref", 404);
|
|
23762
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
23763
|
+
return new Response(body, {
|
|
23764
|
+
status: 206,
|
|
23765
|
+
headers: rawFileHeaders(path, { size, range, metadata })
|
|
23766
|
+
});
|
|
23767
|
+
}
|
|
23509
23768
|
if (req.method === "HEAD")
|
|
23510
23769
|
return new Response(null, {
|
|
23511
23770
|
headers: rawFileHeaders(path, { size, metadata })
|
|
23512
23771
|
});
|
|
23513
|
-
const
|
|
23514
|
-
|
|
23515
|
-
return text("not in ref", 404);
|
|
23516
|
-
body = res.stdout.buffer.slice(res.stdout.byteOffset, res.stdout.byteOffset + res.stdout.byteLength);
|
|
23517
|
-
return new Response(body, {
|
|
23772
|
+
const shown = catFileBlobStream(oid.oid, cwd);
|
|
23773
|
+
return new Response(shown.stream, {
|
|
23518
23774
|
headers: rawFileHeaders(path, { size, metadata })
|
|
23519
23775
|
});
|
|
23520
23776
|
} else {
|
|
@@ -23664,11 +23920,11 @@ async function handleUploadFiles(req) {
|
|
|
23664
23920
|
const written = [];
|
|
23665
23921
|
try {
|
|
23666
23922
|
for (const upload of uploads) {
|
|
23667
|
-
const fd =
|
|
23923
|
+
const fd = openSync3(upload.target, uploadOpenFlags(), 420);
|
|
23668
23924
|
try {
|
|
23669
23925
|
writeFileSync2(fd, new Uint8Array(await upload.file.arrayBuffer()));
|
|
23670
23926
|
} finally {
|
|
23671
|
-
|
|
23927
|
+
closeSync3(fd);
|
|
23672
23928
|
}
|
|
23673
23929
|
written.push(upload.target);
|
|
23674
23930
|
}
|
|
@@ -24666,7 +24922,7 @@ var init_preview = __esm(async () => {
|
|
|
24666
24922
|
if (url.pathname === "/_files")
|
|
24667
24923
|
return handleFiles2(url);
|
|
24668
24924
|
if (url.pathname === "/_grep")
|
|
24669
|
-
return handleGrep(url);
|
|
24925
|
+
return await handleGrep(url);
|
|
24670
24926
|
if (url.pathname === "/_commits")
|
|
24671
24927
|
return handleRefCommits(url);
|
|
24672
24928
|
if (url.pathname === "/_log")
|
|
@@ -24678,7 +24934,7 @@ var init_preview = __esm(async () => {
|
|
|
24678
24934
|
if (url.pathname === "/file_range")
|
|
24679
24935
|
return handleFileRange(url);
|
|
24680
24936
|
if (url.pathname === "/_file")
|
|
24681
|
-
return handleRawFile(req, url);
|
|
24937
|
+
return await handleRawFile(req, url);
|
|
24682
24938
|
if (url.pathname === "/_open_path")
|
|
24683
24939
|
return handleOpenPath(req);
|
|
24684
24940
|
if (url.pathname === "/_trash_path")
|