// Renders a grep tool result grouped by file. Ported from the pi-fabric grep // preview (src/ui/core-tool-render.ts renderGrep/renderGrepLine): match lines // (`path:line: text`) and context lines (`path-line- text`) are parsed, each // file gets one header line, and the whole preview obeys the shared 2000-char // bound. import type { Theme } from "@earendil-works/pi-coding-agent"; import { boundPreviewLines } from "./bound.ts"; import { previewStyle } from "./style.ts"; export interface GrepPreviewInput { /** The raw grep result text (one `path:line: text` line per match). */ readonly output: string; } const GREP_MATCH_PATTERN = /^(.+):(\d+):\s?(.*)$/; const GREP_CONTEXT_PATTERN = /^(.+)-(\d+)-\s?(.*)$/; const CRLF_PATTERN = /\r\n/g; const CR_PATTERN = /\r/g; const TRAILING_NEWLINE_PATTERN = /\n$/; export function renderGrepPreview( input: GrepPreviewInput, theme?: Theme ): string[] { const output = input.output .replace(CRLF_PATTERN, "\n") .replace(CR_PATTERN, "\n") .replace(TRAILING_NEWLINE_PATTERN, ""); if (output.length === 0 || output === "No matches found") { return [previewStyle(theme, "muted", "No matches found")]; } const lines: string[] = []; let currentFile: string | undefined; for (const raw of output.split("\n")) { const match = raw.match(GREP_MATCH_PATTERN) ?? raw.match(GREP_CONTEXT_PATTERN); if (match === null) { lines.push(previewStyle(theme, "toolOutput", raw)); currentFile = undefined; continue; } const file = match[1] ?? ""; const lineNumber = match[2] ?? ""; const code = match[3] ?? ""; if (file !== currentFile) { lines.push(previewStyle(theme, "accent", file)); currentFile = file; } const isMatch = GREP_MATCH_PATTERN.test(raw); const number = isMatch ? previewStyle(theme, "accent", lineNumber.padStart(4, " ")) : previewStyle(theme, "dim", lineNumber.padStart(4, " ")); const marker = isMatch ? previewStyle(theme, "warning", "│") : previewStyle(theme, "dim", "┆"); lines.push( `${previewStyle(theme, "dim", " ")}${number} ${marker} ${previewStyle( theme, "toolOutput", code )}` ); } return [...boundPreviewLines(lines).lines]; }