improve multiline text editing reliability by tracking positions and applying edits in reverse order

This commit is contained in:
Marc Goodner 2024-12-04 07:27:02 -08:00
commit 1ec75e87fc

View file

@ -216,6 +216,29 @@ async function searchFiles(
return results; return results;
} }
interface Position {
start: number;
end: number;
lineNumber: number;
}
function findTextPosition(content: string, searchText: string): Position {
// Handle different line endings
const normalized = content.replace(/\r\n/g, '\n');
const searchNormalized = searchText.replace(/\r\n/g, '\n');
const pos = normalized.indexOf(searchNormalized);
if (pos === -1) {
throw new Error(`Text not found:\n${searchText}`);
}
return {
start: pos,
end: pos + searchText.length,
lineNumber: normalized.slice(0, pos).split('\n').length
};
}
// Edit preview type // Edit preview type
interface EditPreview { interface EditPreview {
original: string; original: string;
@ -225,37 +248,39 @@ interface EditPreview {
} }
// File editing utilities // File editing utilities
async function applyFileEdits(filePath: string, edits: Array<{oldText: string, newText: string}>, dryRun: boolean = false): Promise<string | EditPreview[]> { async function applyFileEdits(filePath: string, edits: Array<{oldText: string, newText: string}>, dryRun = false): Promise<string | EditPreview[]> {
let content = await fs.readFile(filePath, 'utf-8'); let content = await fs.readFile(filePath, 'utf-8');
const previews: EditPreview[] = []; const previews: EditPreview[] = [];
for (const edit of edits) { // Find all positions first
const pos = content.indexOf(edit.oldText); const positions = edits.map(edit => ({
if (pos === -1) { edit,
throw new Error( position: findTextPosition(content, edit.oldText)
`Search text not found in ${filePath}:\n${edit.oldText}` }));
);
} // Sort by position in reverse order
positions.sort((a, b) => b.position.start - a.position.start);
const lineNumber = content.slice(0, pos).split(/\r?\n/).length;
// Apply edits from end to start
for (const {edit, position} of positions) {
const preview = [ const preview = [
`@@ line ${lineNumber} @@`, `@@ line ${position.lineNumber} @@`,
'<<<<<<< ORIGINAL', '<<<<<<< ORIGINAL',
edit.oldText, edit.oldText,
'=======', '=======',
edit.newText, edit.newText,
'>>>>>>> MODIFIED' '>>>>>>> MODIFIED'
].join('\n'); ].join('\n');
previews.push({ previews.push({
original: edit.oldText, original: edit.oldText,
modified: edit.newText, modified: edit.newText,
lineNumber, lineNumber: position.lineNumber,
preview preview
}); });
if (!dryRun) { if (!dryRun) {
content = content.slice(0, pos) + edit.newText + content.slice(pos + edit.oldText.length); content = content.slice(0, position.start) + edit.newText + content.slice(position.end);
} }
} }