diff --git a/src/tools/FileEditTool/utils.test.ts b/src/tools/FileEditTool/utils.test.ts index d317d66569..19a85cdc96 100644 --- a/src/tools/FileEditTool/utils.test.ts +++ b/src/tools/FileEditTool/utils.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from 'bun:test' -import { findWhitespaceAgnosticMatch, adjustNewStringIndentation } from './utils.js' +import { + findWhitespaceAgnosticMatch, + adjustNewStringIndentation, + getSnippetForTwoFileDiff, +} from './utils.js' describe('findWhitespaceAgnosticMatch', () => { test('returns exact match for simple string', () => { @@ -180,3 +184,29 @@ describe('adjustNewStringIndentation', () => { expect(adjustNewStringIndentation(oldString, fileMatch, newString)).toBeNull() }) }) + +describe('getSnippetForTwoFileDiff truncation notice', () => { + test('reports the exact number of dropped lines when cutting at a line boundary', () => { + // A full-replacement diff of an empty file with a large file yields a single + // hunk whose line-numbered snippet far exceeds DIFF_SNIPPET_MAX_BYTES (8192), + // forcing truncation at a line boundary. + const bigFile = + Array.from({ length: 800 }, (_, i) => `line ${i + 1} content here`).join( + '\n', + ) + '\n' + const snippet = getSnippetForTwoFileDiff('', bigFile) + + const match = snippet.match(/\.\.\. \[(\d+) lines truncated\] \.\.\./) + expect(match).not.toBeNull() + const reportedTruncated = Number(match![1]) + + // The snippet body before the marker holds the kept lines. Together with the + // reported truncated count they must sum to the total lines the untruncated + // snippet would have shown — here, the 800 added lines. Before the fix the + // boundary newline was double-counted and this reported 801. + const keptBody = snippet.slice(0, snippet.indexOf('\n\n... [')) + const keptLines = keptBody.split('\n').length + + expect(keptLines + reportedTruncated).toBe(800) + }) +}) diff --git a/src/tools/FileEditTool/utils.ts b/src/tools/FileEditTool/utils.ts index b11bc4e877..3c055c041b 100644 --- a/src/tools/FileEditTool/utils.ts +++ b/src/tools/FileEditTool/utils.ts @@ -399,9 +399,22 @@ export function getSnippetForTwoFileDiff( // Truncate at the last line boundary that fits within the cap. // Marker format matches BashTool/utils.ts. const cutoff = full.lastIndexOf('\n', DIFF_SNIPPET_MAX_BYTES) - const kept = - cutoff > 0 ? full.slice(0, cutoff) : full.slice(0, DIFF_SNIPPET_MAX_BYTES) - const remaining = countCharInString(full, '\n', kept.length) + 1 + let kept: string + let remaining: number + if (cutoff > 0) { + kept = full.slice(0, cutoff) + // `full[cutoff]` is the newline that terminates the last kept line, so + // counting newlines from `cutoff` onward counts that boundary newline plus + // every later one — exactly the number of dropped lines. No `+1`: the + // boundary newline is not itself a dropped line, it stands in for the + // missing trailing newline of the final dropped line. + remaining = countCharInString(full, '\n', cutoff) + } else { + kept = full.slice(0, DIFF_SNIPPET_MAX_BYTES) + // Mid-line cut (no newline within the cap): the partial tail line and every + // following line are dropped, so add 1 for that partial line. + remaining = countCharInString(full, '\n', kept.length) + 1 + } return `${kept}\n\n... [${remaining} lines truncated] ...` }