Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/diffs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,8 @@ export type ConflictResolverTypes = 'current' | 'incoming' | 'both';

export interface DiffAcceptRejectHunkConfig {
type: DiffAcceptRejectHunkType;
changeIndex: number;
changeIndex?: number;
trimContextLines?: boolean | number;
}

/**
Expand Down
39 changes: 27 additions & 12 deletions packages/diffs/src/utils/diffAcceptRejectHunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ type DiffAcceptRejectHunkOptions =
| DiffAcceptRejectHunkType
| DiffAcceptRejectHunkConfig;

function normalizeTrimContextLines(
trimContextLines: DiffAcceptRejectHunkConfig['trimContextLines']
): number | undefined {
if (trimContextLines === true) {
return 3;
}
if (typeof trimContextLines === 'number') {
return trimContextLines;
}
return undefined;
}

export function diffAcceptRejectHunk(
diff: FileDiffMetadata,
hunkIndex: number,
Expand All @@ -21,20 +33,23 @@ export function diffAcceptRejectHunk(
throw new Error('diffAcceptRejectHunk: Invalid hunk index');
}

const startContentIndex =
typeof options === 'object' && options.changeIndex != null
? options.changeIndex
: 0;
const endContentIndex =
typeof options === 'object' && options.changeIndex != null
? options.changeIndex
: Math.max(0, (hunk.hunkContent.length ?? 1) - 1);

return resolveRegion(diff, {
resolution: normalizeDiffResolution(options),
hunkIndex,
...(() => {
if (typeof options === 'object') {
return {
startContentIndex: options.changeIndex,
endContentIndex: options.changeIndex,
};
}
return {
startContentIndex: 0,
endContentIndex: Math.max(0, (hunk.hunkContent.length ?? 1) - 1),
};
})(),
startContentIndex,
endContentIndex,
trimContextLines:
typeof options === 'object'
? normalizeTrimContextLines(options.trimContextLines)
: undefined,
});
}
5 changes: 4 additions & 1 deletion packages/diffs/src/utils/parseDiffFromFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export function parseDiffFromFile(
newFile.contents,
oldFile.header,
newFile.header,
options
// NOTE(amadeus): By default, git defaults to 3 context lines, but for some
// reason this library does 4. I want this to _feel_ like git by default,
// and it can always be changed if people want to override
{ ...options, context: options?.context ?? 3 }
);

const fileData = processFile(patch, {
Expand Down
2 changes: 1 addition & 1 deletion packages/diffs/src/utils/parseMergeConflictDiffFromFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export function getMergeConflictActionAnchor(
// 3. Result assembly — joins line arrays, builds marker rows for the UI
export function parseMergeConflictDiffFromFile(
file: FileContents,
maxContextLines: number = 6
maxContextLines: number = 3
): ParseMergeConflictDiffFromFileResult {
// Never allow maxContextLines to drop below 1 or else things break.
maxContextLines = Math.max(maxContextLines, 1);
Expand Down
129 changes: 111 additions & 18 deletions packages/diffs/src/utils/resolveRegion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import type {
FileDiffMetadata,
Hunk,
} from '../types';
import { trimResolvedHunk } from './trimResolvedHunk';

interface RegionResolutionTarget {
hunkIndex: number;
startContentIndex: number;
endContentIndex: number;
resolution: 'deletions' | 'additions' | 'both';
indexesToDelete?: Set<number>;
trimContextLines?: number;
}

interface CursorState {
Expand All @@ -22,6 +24,28 @@ interface CursorState {
unifiedLineCount: number;
}

interface EmissionState {
nextAdditionStart: number;
nextDeletionStart: number;
splitLineCount: number;
unifiedLineCount: number;
}

type EmitReadyHunk = Pick<
Hunk,
| 'additionStart'
| 'additionCount'
| 'additionLines'
| 'additionLineIndex'
| 'deletionStart'
| 'deletionCount'
| 'deletionLines'
| 'deletionLineIndex'
| 'splitLineCount'
| 'unifiedLineCount'
| 'hunkContent'
>;

export function resolveRegion(
diff: FileDiffMetadata,
target: RegionResolutionTarget
Expand All @@ -32,6 +56,7 @@ export function resolveRegion(
startContentIndex,
endContentIndex,
indexesToDelete = new Set(),
trimContextLines,
} = target;
const currentHunk = diff.hunks[hunkIndex];
if (currentHunk == null) {
Expand Down Expand Up @@ -59,7 +84,7 @@ export function resolveRegion(
unifiedLineCount: 0,
cacheKey:
diff.cacheKey != null
? `${diff.cacheKey}:${resolution[0]}-${hunkIndex}:${startContentIndex}-${endContentIndex}`
? `${diff.cacheKey}:${resolution[0]}-${hunkIndex}:${startContentIndex}-${endContentIndex}${trimContextLines != null ? `:t-${trimContextLines}` : ''}`
: undefined,
};

Expand All @@ -71,6 +96,12 @@ export function resolveRegion(
splitLineCount: 0,
unifiedLineCount: 0,
};
const emissionState: EmissionState = {
nextAdditionStart: 1,
nextDeletionStart: 1,
splitLineCount: 0,
unifiedLineCount: 0,
};
const updatesEOFState =
hunkIndex === hunks.length - 1 &&
endContentIndex === currentHunk.hunkContent.length - 1;
Expand All @@ -87,22 +118,7 @@ export function resolveRegion(
shouldProcessCollapsedContext
);

const newHunk: Hunk = {
...hunk,
hunkContent: [],
additionStart: cursor.nextAdditionStart,
deletionStart: cursor.nextDeletionStart,
additionLineIndex: cursor.nextAdditionLineIndex,
deletionLineIndex: cursor.nextDeletionLineIndex,
additionCount: 0,
deletionCount: 0,
deletionLines: 0,
additionLines: 0,
splitLineStart: cursor.splitLineCount,
unifiedLineStart: cursor.unifiedLineCount,
splitLineCount: 0,
unifiedLineCount: 0,
};
const newHunk = createScannedHunk(hunk, cursor);

for (const [contentIndex, content] of hunk.hunkContent.entries()) {
// If we are outside of the targeted hunk or content region
Expand Down Expand Up @@ -185,7 +201,20 @@ export function resolveRegion(
newHunk.noEOFCRDeletions = noEOFCR;
}

resolvedDiff.hunks.push(newHunk);
const emittedHunks =
index === hunkIndex && trimContextLines != null
? trimResolvedHunk(newHunk, trimContextLines)
: [newHunk];

emitResolvedHunks(resolvedDiff, newHunk, emittedHunks, emissionState);
}

if (resolvedDiff.hunks.length === 0) {
resolvedDiff.deletionLines = [];
resolvedDiff.additionLines = [];
resolvedDiff.splitLineCount = 0;
resolvedDiff.unifiedLineCount = 0;
return resolvedDiff;
}

const finalHunk = hunks.at(-1);
Expand All @@ -211,6 +240,70 @@ export function resolveRegion(
return resolvedDiff;
}

function createScannedHunk(hunk: Hunk, cursor: CursorState): Hunk {
return {
...hunk,
collapsedBefore: 0,
hunkContent: [],
additionStart: cursor.nextAdditionStart,
deletionStart: cursor.nextDeletionStart,
additionLineIndex: cursor.nextAdditionLineIndex,
deletionLineIndex: cursor.nextDeletionLineIndex,
additionCount: 0,
deletionCount: 0,
deletionLines: 0,
additionLines: 0,
splitLineStart: 0,
unifiedLineStart: 0,
splitLineCount: 0,
unifiedLineCount: 0,
};
}

function emitResolvedHunks(
diff: FileDiffMetadata,
sourceHunk: Hunk,
hunks: EmitReadyHunk[],
emissionState: EmissionState
) {
for (const [index, hunk] of hunks.entries()) {
const collapsedBefore = Math.max(
hunk.additionStart - emissionState.nextAdditionStart,
0
);
const emittedHunk: Hunk = {
...sourceHunk,
...hunk,
collapsedBefore,
splitLineStart: emissionState.splitLineCount + collapsedBefore,
unifiedLineStart: emissionState.unifiedLineCount + collapsedBefore,
hunkSpecs: createHunkSpecs(hunk, sourceHunk.hunkContext),
noEOFCRAdditions:
index === hunks.length - 1 ? sourceHunk.noEOFCRAdditions : false,
noEOFCRDeletions:
index === hunks.length - 1 ? sourceHunk.noEOFCRDeletions : false,
};

diff.hunks.push(emittedHunk);
emissionState.nextAdditionStart =
emittedHunk.additionStart + emittedHunk.additionCount;
emissionState.nextDeletionStart =
emittedHunk.deletionStart + emittedHunk.deletionCount;
emissionState.splitLineCount =
emittedHunk.splitLineStart + emittedHunk.splitLineCount;
emissionState.unifiedLineCount =
emittedHunk.unifiedLineStart + emittedHunk.unifiedLineCount;
}
}

function createHunkSpecs(hunk: EmitReadyHunk, hunkContext: string | undefined) {
return `@@ -${formatHunkRange(hunk.deletionStart, hunk.deletionCount)} +${formatHunkRange(hunk.additionStart, hunk.additionCount)} @@${hunkContext != null ? ` ${hunkContext}` : ''}\n`;
}

function formatHunkRange(start: number, count: number): string {
return count === 1 ? `${start}` : `${start},${count}`;
}

function pushCollapsedContextLines(
diff: FileDiffMetadata,
deletionLines: string[],
Expand Down
2 changes: 1 addition & 1 deletion packages/diffs/src/utils/trimPatchContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type ContextFlushMode = 'before-change' | 'leading' | 'trailing';
* well as be able to create new hunks where necessary if there's excessive
* context between changes
*/
export function trimPatchContext(patch: string, contextSize = 10): string {
export function trimPatchContext(patch: string, contextSize = 3): string {
const lines: string[] = [];

let currentHunk: CurrentHunk | undefined;
Expand Down
Loading
Loading