pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
97 lines (96 loc) • 3.44 kB
JavaScript
export function partitionSourceFiles(sourceRoot, files, maxFiles = 12) {
if (maxFiles <= 0) {
throw new Error("maxFiles must be greater than zero");
}
return partitionAt(sourceRoot.replace(/\\/g, "/"), normalizeFiles(files), maxFiles, 0);
}
function normalizeFiles(files) {
return [...new Set(files.map((file) => file.replace(/\\/g, "/")))].sort((a, b) => a.localeCompare(b));
}
function partitionAt(sourceRoot, files, maxFiles, depth) {
if (files.length === 0)
return [];
if (files.length <= maxFiles) {
return [{ label: commonLabel(sourceRoot, files, depth), files }];
}
const directFiles = [];
const buckets = new Map();
for (const file of files) {
const relativePath = file.startsWith(`${sourceRoot}/`)
? file.slice(sourceRoot.length + 1)
: file;
const parts = relativePath.split("/");
if (parts.length <= depth + 1) {
directFiles.push(file);
continue;
}
const segment = parts[depth];
if (!segment) {
directFiles.push(file);
continue;
}
const bucket = buckets.get(segment) ?? [];
bucket.push(file);
buckets.set(segment, bucket);
}
if (buckets.size === 0) {
return chunkFiles(currentLabel(sourceRoot, files, depth), files, maxFiles);
}
const groups = chunkFiles(currentLabel(sourceRoot, files, depth), directFiles, maxFiles);
for (const [segment, bucketFiles] of [...buckets.entries()].sort(([left], [right]) => left.localeCompare(right))) {
if (bucketFiles.length <= maxFiles) {
groups.push({
label: `${sourceRoot}/${bucketPrefix(bucketFiles, sourceRoot, depth, segment)}`,
files: bucketFiles,
});
}
else {
groups.push(...partitionAt(sourceRoot, bucketFiles, maxFiles, depth + 1));
}
}
return groups;
}
function chunkFiles(label, files, maxFiles) {
if (files.length === 0)
return [];
if (files.length <= maxFiles)
return [{ label, files }];
const chunks = [];
for (let index = 0; index < files.length; index += maxFiles) {
const part = Math.floor(index / maxFiles) + 1;
chunks.push({
label: `${label}#${part}`,
files: files.slice(index, index + maxFiles),
});
}
return chunks;
}
function currentLabel(sourceRoot, files, depth) {
if (depth === 0)
return sourceRoot;
const first = files[0];
if (!first)
return sourceRoot;
const relativePath = first.startsWith(`${sourceRoot}/`)
? first.slice(sourceRoot.length + 1)
: first;
const parts = relativePath.split("/").slice(0, depth);
return parts.length === 0 ? sourceRoot : `${sourceRoot}/${parts.join("/")}`;
}
function commonLabel(sourceRoot, files, depth) {
if (depth === 0)
return sourceRoot;
if (files.length === 1)
return files[0] ?? sourceRoot;
return currentLabel(sourceRoot, files, depth);
}
function bucketPrefix(files, sourceRoot, depth, segment) {
const first = files[0];
if (!first || depth === 0)
return segment;
const relativePath = first.startsWith(`${sourceRoot}/`)
? first.slice(sourceRoot.length + 1)
: first;
const parts = relativePath.split("/").slice(0, depth);
return [...parts, segment].join("/");
}