Something went wrong. Try again.
Github-style local code review for working in agents in jujutsu repos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168import type { DiffFile, DiffHunk } from '$lib/types/index';
export function parseDiff(raw: string): DiffFile[] { if (!raw.trim()) return [];
const files: DiffFile[] = []; const fileChunks = raw.split(/^diff --git /m).filter(Boolean);
for (const chunk of fileChunks) { const lines = chunk.split('\n'); let path = ''; let oldPath: string | undefined;
// Parse file header const headerMatch = lines[0]?.match(/a\/(.+?) b\/(.+)/); if (headerMatch) { oldPath = headerMatch[1]; path = headerMatch[2]; if (oldPath === path) oldPath = undefined; }
const language = getLanguage(path); const hunks: DiffHunk[] = []; let currentHunk: DiffHunk | null = null; let oldLineNum = 0; let newLineNum = 0;
for (const line of lines) { const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)/); if (hunkMatch) { const oldStart = parseInt(hunkMatch[1], 10); const oldLines = hunkMatch[2] ? parseInt(hunkMatch[2], 10) : 1; const newStart = parseInt(hunkMatch[3], 10); const newLines = hunkMatch[4] ? parseInt(hunkMatch[4], 10) : 1; currentHunk = { header: line, oldStart, oldLines, newStart, newLines, lines: [] }; hunks.push(currentHunk); oldLineNum = oldStart; newLineNum = newStart; continue; }
if (!currentHunk) continue;
if (line.startsWith('+')) { currentHunk.lines.push({ type: 'add', content: line.substring(1), newNumber: newLineNum++ }); } else if (line.startsWith('-')) { currentHunk.lines.push({ type: 'del', content: line.substring(1), oldNumber: oldLineNum++ }); } else if (line.startsWith(' ') || line === '') { // Only treat as context if we're inside a hunk and it looks like a context line if (line.startsWith(' ')) { currentHunk.lines.push({ type: 'context', content: line.substring(1), oldNumber: oldLineNum++, newNumber: newLineNum++ }); } } else if (line.startsWith('\\')) { // "No newline at end of file" marker — skip } }
if (path) { files.push({ path, oldPath, hunks, language }); } }
return files;}
export function getLanguage(filePath: string): string { const ext = filePath.split('.').pop()?.toLowerCase() || ''; const map: Record<string, string> = { ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx', py: 'python', rb: 'ruby', go: 'go', rs: 'rust', java: 'java', kt: 'kotlin', kts: 'kotlin', swift: 'swift', cs: 'csharp', fs: 'fsharp', php: 'php', r: 'r', lua: 'lua', dart: 'dart', scala: 'scala', groovy: 'groovy', pl: 'perl', pm: 'perl', ex: 'elixir', exs: 'elixir', erl: 'erlang', hs: 'haskell', clj: 'clojure', css: 'css', scss: 'scss', less: 'less', html: 'html', htm: 'html', xml: 'xml', svg: 'xml', svelte: 'svelte', vue: 'vue', json: 'json', jsonc: 'jsonc', yaml: 'yaml', yml: 'yaml', toml: 'toml', ini: 'ini', md: 'markdown', mdx: 'mdx', sh: 'bash', bash: 'bash', zsh: 'bash', fish: 'fish', ps1: 'powershell', sql: 'sql', graphql: 'graphql', gql: 'graphql', proto: 'proto', c: 'c', cpp: 'cpp', cc: 'cpp', cxx: 'cpp', h: 'c', hpp: 'cpp', m: 'objective-c', mm: 'objective-cpp', zig: 'zig', nim: 'nim', tf: 'hcl', dockerfile: 'dockerfile', makefile: 'makefile', cmake: 'cmake' }; // Also check filename (no extension) const filename = filePath.split('/').pop()?.toLowerCase() || ''; const filenameMap: Record<string, string> = { dockerfile: 'dockerfile', makefile: 'makefile', gemfile: 'ruby', rakefile: 'ruby', podfile: 'ruby' }; return map[ext] || filenameMap[filename] || 'text';}