repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/web/line-match.ts
Rawexport interface LineMatchOptions {
needles: string[];
contextLines?: number;
caseSensitive?: boolean;
maxChars?: number;
}
export interface LineMatchResult {
text: string;
matchCount: number;
truncated: boolean;
}
export function filterLineMatches(
text: string,
options: LineMatchOptions,
): LineMatchResult {
const needles = options.needles.filter((x) => x.length > 0);
if (needles.length === 0)
return { text: "", matchCount: 0, truncated: false };
const lines = text.split(/\r?\n/u);
const haystack = options.caseSensitive
? lines
: lines.map((x) => x.toLowerCase());
const wanted = options.caseSensitive
? needles
: needles.map((x) => x.toLowerCase());
const context = Math.max(0, options.contextLines ?? 0);
const ranges: Array<[number, number]> = [];
let matchCount = 0;
for (let i = 0; i < haystack.length; i++) {
if (!wanted.some((needle) => haystack[i].includes(needle))) continue;
matchCount++;
const start = Math.max(0, i - context);
const end = Math.min(lines.length - 1, i + context);
const last = ranges[ranges.length - 1];
if (last && start <= last[1] + 1) last[1] = Math.max(last[1], end);
else ranges.push([start, end]);
}
if (matchCount === 0) return { text: "", matchCount: 0, truncated: false };
const out: string[] = [];
for (const [start, end] of ranges) {
if (out.length > 0) out.push("--");
for (let i = start; i <= end; i++) out.push(`${i + 1}: ${lines[i]}`);
}
const full = out.join("\n");
const max = options.maxChars ?? 8_000;
if (full.length <= max) return { text: full, matchCount, truncated: false };
return {
text: `${full.slice(0, max)}\n… (matches truncated)`,
matchCount,
truncated: true,
};
}