Skip to content
Open
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
37 changes: 37 additions & 0 deletions src/core/liveComments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,41 @@ describe("live comment helpers", () => {
expect(range.newRange[0]).toBeLessThanOrEqual(1);
expect(range.newRange[1]).toBeGreaterThanOrEqual(2);
});

// Regression: a hunk with one addition surrounded by lots of context used to report
// newRange = [start, start] (additions-only), so a comment anchored past the leading
// context fell outside the hunk's range, annotationOverlapsHunk returned false, and
// the hunk silently disappeared from getAnnotatedHunkIndices / annotated-cursor lists.
// Fix: hunkLineRange uses additionCount/deletionCount (header total, includes context)
// instead of additionLines/deletionLines (just '+' / '-' counts).
test("includes context lines when one hunk has many context rows around few changes", () => {
// 12 leading + 1 added + many trailing context lines on the new side.
const beforeLines = Array.from({ length: 25 }, (_, i) => `line${i + 1}`);
const afterLines = [
...beforeLines.slice(0, 12),
"INSERTED",
...beforeLines.slice(12),
];

const file = createTestDiffFile({
before: lines(...beforeLines),
after: lines(...afterLines),
context: 100,
id: "file:context-heavy",
path: "src/sparse.ts",
previousPath: "src/sparse.ts",
});

expect(file.metadata.hunks.length).toBe(1);
const hunk = file.metadata.hunks[0]!;
expect(hunk.additionLines).toBe(1);

const range = hunkLineRange(hunk);

// The range must cover the inserted line at position 13 — additions-only bounds
// would put newEnd at additionStart and miss it.
expect(range.newRange[1]).toBeGreaterThanOrEqual(13);
expect(findHunkIndexForLine(file, "new", 13)).toBe(0);
expect(findHunkIndexForLine(file, "new", 20)).toBe(0);
});
});
16 changes: 13 additions & 3 deletions src/core/liveComments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ export interface ResolvedCommentTarget {
line: number;
}

/** Compute the inclusive old/new line spans touched by one hunk. */
/**
* Compute the inclusive old/new line spans for the visible extent of a hunk.
*
* Use the per-side `*Count` from the hunk header (`-X,count` / `+X,count`),
* which includes both context and changed lines, not the `*Lines` count which
* is only the `+` / `-` lines. Comments anchored at a context-region line
* (e.g. resolved by `firstCommentTargetForHunk` walking past leading context)
* fall outside the additions-only range and silently disappear from
* `getAnnotatedHunkIndices` / `findHunkIndexForLine` if those use the wrong
* extent.
*/
export function hunkLineRange(hunk: Hunk) {
const newEnd = Math.max(
hunk.additionStart,
hunk.additionStart + Math.max(hunk.additionLines, 1) - 1,
hunk.additionStart + Math.max(hunk.additionCount, 1) - 1,
);
const oldEnd = Math.max(
hunk.deletionStart,
hunk.deletionStart + Math.max(hunk.deletionLines, 1) - 1,
hunk.deletionStart + Math.max(hunk.deletionCount, 1) - 1,
);

return {
Expand Down
18 changes: 1 addition & 17 deletions src/ui/lib/agentAnnotations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Hunk } from "@pierre/diffs";
import type { AgentAnnotation, DiffFile } from "../../core/types";
import { hunkLineRange } from "../../core/liveComments";
import { fileLabel } from "./files";

export interface VisibleAgentNote {
Expand All @@ -17,23 +18,6 @@ function overlap(rangeA: [number, number], rangeB: [number, number]) {
return rangeA[0] <= rangeB[1] && rangeB[0] <= rangeA[1];
}

/** Compute the old/new line ranges covered by a hunk, including single-line edge cases. */
function hunkLineRange(hunk: Hunk) {
const newEnd = Math.max(
hunk.additionStart,
hunk.additionStart + Math.max(hunk.additionLines, 1) - 1,
);
const oldEnd = Math.max(
hunk.deletionStart,
hunk.deletionStart + Math.max(hunk.deletionLines, 1) - 1,
);

return {
oldRange: [hunk.deletionStart, oldEnd] as [number, number],
newRange: [hunk.additionStart, newEnd] as [number, number],
};
}

/** Check whether an annotation belongs to the visible span of a hunk. */
function annotationOverlapsHunk(annotation: AgentAnnotation, hunk: Hunk) {
const hunkRange = hunkLineRange(hunk);
Expand Down