Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | 289x 289x 65x 63x 63x 63x 63x 289x 289x 65x 8x 65x 224x 165x 164x 164x 63x 57x 63x 14x 29x 21x 21x | /**
* Unified diff parser - extracts hunks from patch strings
*/
export interface DiffHunk {
/** Original file start line */
oldStart: number;
/** Original file line count */
oldCount: number;
/** New file start line */
newStart: number;
/** New file line count */
newCount: number;
/** Optional header (function/class name) */
header?: string;
/** The raw hunk content including the @@ line */
content: string;
/** Just the changed lines (without @@ header) */
lines: string[];
}
export interface ParsedDiff {
/** File path */
filename: string;
/** File status */
status: 'added' | 'removed' | 'modified' | 'renamed';
/** Individual hunks in this file */
hunks: DiffHunk[];
/** The full patch string */
rawPatch: string;
}
/**
* Parse a unified diff hunk header.
* Format: @@ -oldStart,oldCount +newStart,newCount @@ optional header
*/
function parseHunkHeader(line: string): Omit<DiffHunk, 'content' | 'lines'> | null {
const match = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/);
if (!match || !match[1] || !match[3]) return null;
return {
oldStart: parseInt(match[1], 10),
oldCount: parseInt(match[2] ?? '1', 10),
newStart: parseInt(match[3], 10),
newCount: parseInt(match[4] ?? '1', 10),
header: match[5]?.trim() || undefined,
};
}
/** Intermediate hunk structure for parsing */
interface HunkBuilder {
oldStart: number;
oldCount: number;
newStart: number;
newCount: number;
header?: string;
contentParts: string[];
lines: string[];
}
/**
* Parse a unified diff patch into hunks.
*/
export function parsePatch(patch: string): DiffHunk[] {
const lines = patch.split('\n');
const hunks: DiffHunk[] = [];
let currentHunk: HunkBuilder | null = null;
for (const line of lines) {
const header = parseHunkHeader(line);
if (header) {
// Save previous hunk if exists
if (currentHunk) {
hunks.push({
...currentHunk,
content: currentHunk.contentParts.join('\n'),
});
}
// Start new hunk with array-based content builder
currentHunk = {
...header,
contentParts: [line],
lines: [],
};
} else if (currentHunk) {
// Add line to current hunk (skip diff metadata lines)
if (!line.startsWith('diff --git') &&
!line.startsWith('index ') &&
!line.startsWith('--- ') &&
!line.startsWith('+++ ') &&
!line.startsWith('\\ No newline')) {
currentHunk.contentParts.push(line);
currentHunk.lines.push(line);
}
}
}
// Don't forget the last hunk
if (currentHunk) {
hunks.push({
...currentHunk,
content: currentHunk.contentParts.join('\n'),
});
}
return hunks;
}
/**
* Parse a file's patch into a structured diff object.
*/
export function parseFileDiff(
filename: string,
patch: string,
status: 'added' | 'removed' | 'modified' | 'renamed' = 'modified'
): ParsedDiff {
return {
filename,
status,
hunks: parsePatch(patch),
rawPatch: patch,
};
}
/**
* Get the line range covered by a hunk (in the new file).
*/
export function getHunkLineRange(hunk: DiffHunk): { start: number; end: number } {
return {
start: hunk.newStart,
end: hunk.newStart + hunk.newCount - 1,
};
}
/**
* Get an expanded line range for context.
*/
export function getExpandedLineRange(
hunk: DiffHunk,
contextLines = 20
): { start: number; end: number } {
const range = getHunkLineRange(hunk);
return {
start: Math.max(1, range.start - contextLines),
end: range.end + contextLines,
};
}
|