diff --git a/packages/core/src/tools/edit.test.ts b/packages/core/src/tools/edit.test.ts index 4077a6cd41..84086bbd69 100644 --- a/packages/core/src/tools/edit.test.ts +++ b/packages/core/src/tools/edit.test.ts @@ -511,6 +511,42 @@ function doIt() { expect(result.newContent).toBe(expectedContent); }); + it('should preserve trailing newlines in flexible replacement (regression)', async () => { + const content = ' line1\n line2\n line3\n'; + const result = await calculateReplacement(mockConfig, { + params: { + file_path: 'test.txt', + old_string: 'line1\nline2', + new_string: 'line1-replaced\nline2-replaced', + }, + currentContent: content, + abortSignal, + }); + + expect(result.newContent).toBe( + ' line1-replaced\n line2-replaced\n line3\n', + ); + }); + + it('should correctly increment loop index in flexible replacement when allow_multiple is true (regression)', async () => { + const content = ' match1\n match2\n match1\n match2\n'; + const result = await calculateReplacement(mockConfig, { + params: { + file_path: 'test.txt', + old_string: 'match1\nmatch2', + new_string: 'replaced1\nreplaced2\nreplaced3', + allow_multiple: true, + }, + currentContent: content, + abortSignal, + }); + + expect(result.occurrences).toBe(2); + expect(result.newContent).toBe( + ' replaced1\n replaced2\n replaced3\n replaced1\n replaced2\n replaced3\n', + ); + }); + it('should correctly rebase indentation in flexible replacement without double-indenting', async () => { const content = ' if (a) {\n foo();\n }\n'; // old_string and new_string are unindented. They should be rebased to 4-space. diff --git a/packages/core/src/tools/edit.ts b/packages/core/src/tools/edit.ts index 3f6d5d9f62..c00ea4c0da 100644 --- a/packages/core/src/tools/edit.ts +++ b/packages/core/src/tools/edit.ts @@ -202,15 +202,19 @@ async function calculateFlexibleReplacement( const indentationMatch = firstLineInMatch.match(/^([ \t]*)/); const indentation = indentationMatch ? indentationMatch[1] : ''; const newBlockWithIndent = applyIndentation(replaceLines, indentation); - sourceLines.splice( - i, - searchLinesStripped.length, - newBlockWithIndent.join('\n'), - ); - i += replaceLines.length; - } else { - i++; + + let replacementText = newBlockWithIndent.join('\n'); + if ( + new_string !== '' && + window[window.length - 1].endsWith('\n') && + !replacementText.endsWith('\n') + ) { + replacementText += '\n'; + } + + sourceLines.splice(i, searchLinesStripped.length, replacementText); } + i++; } if (flexibleOccurrences > 0) {