export type DiffLineType = | "file-header" | "hunk-header" | "added" | "removed" | "context" | "no-newline"; export interface ParsedDiffLine { type: DiffLineType; content: string; oldLineNumber: number | null; newLineNumber: number | null; } const FILE_HEADER_PREFIXES = [ "diff ", "index ", "--- ", "+++ ", "new file", "deleted file", "old mode", "new mode", "rename ", "similarity ", ]; export function parseDiff(diffContent: string): ParsedDiffLine[] { const result: ParsedDiffLine[] = []; let oldLine = 0; let newLine = 0; for (const line of diffContent.split("\n")) { if (FILE_HEADER_PREFIXES.some((prefix) => line.startsWith(prefix))) { result.push({ type: "file-header", content: line, oldLineNumber: null, newLineNumber: null }); } else if (line.startsWith("@@")) { const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/); if (match) { oldLine = parseInt(match[1], 10); newLine = parseInt(match[2], 10); } result.push({ type: "hunk-header", content: line, oldLineNumber: null, newLineNumber: null }); } else if (line.startsWith("+")) { result.push({ type: "added", content: line.slice(1), oldLineNumber: null, newLineNumber: newLine++, }); } else if (line.startsWith("-")) { result.push({ type: "removed", content: line.slice(1), oldLineNumber: oldLine++, newLineNumber: null, }); } else if (line.startsWith(" ")) { result.push({ type: "context", content: line.slice(1), oldLineNumber: oldLine++, newLineNumber: newLine++, }); } else if (line === "\\ No newline at end of file") { result.push({ type: "no-newline", content: line, oldLineNumber: null, newLineNumber: null }); } // Skip empty trailing lines } return result; } const EXTENSION_MAP: Record = { ts: "typescript", tsx: "typescript", js: "javascript", jsx: "javascript", rs: "rust", py: "python", svelte: "xml", css: "css", scss: "scss", less: "less", html: "html", json: "json", md: "markdown", toml: "ini", yaml: "yaml", yml: "yaml", sh: "bash", bash: "bash", go: "go", java: "java", cpp: "cpp", c: "c", rb: "ruby", php: "php", sql: "sql", kt: "kotlin", swift: "swift", cs: "csharp", r: "r", lua: "lua", xml: "xml", }; export function detectLanguage(filePath: string): string { const ext = filePath.split(".").pop()?.toLowerCase() ?? ""; return EXTENSION_MAP[ext] ?? "plaintext"; }