Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/web/line-match.ts

Raw
export 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,
	};
}