Prioritize exact line matches in search/replace

This commit is contained in:
Matt Rubens
2024-12-15 15:19:58 -05:00
parent 130c5ff779
commit e4c23fb61d
2 changed files with 133 additions and 25 deletions

View File

@@ -610,6 +610,95 @@ function two() {
function three() {
return 3;
}`)
})
it('should prioritize exact line match over expanded search', () => {
const originalContent = `
function one() {
return 1;
}
function process() {
return "old";
}
function process() {
return "old";
}
function two() {
return 2;
}`
const diffContent = `test.ts
<<<<<<< SEARCH
function process() {
return "old";
}
=======
function process() {
return "new";
}
>>>>>>> REPLACE`
// Should match the second instance exactly at lines 10-12
// even though the first instance at 6-8 is within the expanded search range
const result = strategy.applyDiff(originalContent, diffContent, 10, 12)
expect(result).toBe(`
function one() {
return 1;
}
function process() {
return "old";
}
function process() {
return "new";
}
function two() {
return 2;
}`)
})
it('should fall back to expanded search only if exact match fails', () => {
const originalContent = `
function one() {
return 1;
}
function process() {
return "target";
}
function two() {
return 2;
}`.trim()
const diffContent = `test.ts
<<<<<<< SEARCH
function process() {
return "target";
}
=======
function process() {
return "updated";
}
>>>>>>> REPLACE`
// Specify wrong line numbers (3-5), but content exists at 6-8
// Should still find and replace it since it's within the expanded range
const result = strategy.applyDiff(originalContent, diffContent, 3, 5)
expect(result).toBe(`function one() {
return 1;
}
function process() {
return "updated";
}
function two() {
return 2;
}`)
})
})

View File

@@ -132,41 +132,60 @@ Your search/replace content here
const replaceLines = replaceContent.split(/\r?\n/);
const originalLines = originalContent.split(/\r?\n/);
// Determine search range based on provided line numbers
let searchStartIndex = 0;
let searchEndIndex = originalLines.length;
if (startLine !== undefined || endLine !== undefined) {
// Convert to 0-based index and add buffer
if (startLine !== undefined) {
searchStartIndex = Math.max(0, startLine - 6);
}
if (endLine !== undefined) {
searchEndIndex = Math.min(originalLines.length, endLine + 5);
}
}
// Find the search content in the original using fuzzy matching
// First try exact line range if provided
let matchIndex = -1;
let bestMatchScore = 0;
for (let i = searchStartIndex; i <= searchEndIndex - searchLines.length; i++) {
// Join the lines and calculate overall similarity
const originalChunk = originalLines.slice(i, i + searchLines.length).join('\n');
if (startLine !== undefined && endLine !== undefined) {
// Convert to 0-based index
const exactStartIndex = startLine - 1;
const exactEndIndex = endLine - 1;
// Check exact range first
const originalChunk = originalLines.slice(exactStartIndex, exactEndIndex + 1).join('\n');
const searchChunk = searchLines.join('\n');
const similarity = getSimilarity(originalChunk, searchChunk);
if (similarity > bestMatchScore) {
if (similarity >= this.fuzzyThreshold) {
matchIndex = exactStartIndex;
bestMatchScore = similarity;
matchIndex = i;
}
}
// If no match found in exact range, try expanded range
if (matchIndex === -1) {
let searchStartIndex = 0;
let searchEndIndex = originalLines.length;
if (startLine !== undefined || endLine !== undefined) {
// Convert to 0-based index and add buffer
if (startLine !== undefined) {
searchStartIndex = Math.max(0, startLine - 6);
}
if (endLine !== undefined) {
searchEndIndex = Math.min(originalLines.length, endLine + 5);
}
}
// Find the search content in the expanded range using fuzzy matching
for (let i = searchStartIndex; i <= searchEndIndex - searchLines.length; i++) {
// Join the lines and calculate overall similarity
const originalChunk = originalLines.slice(i, i + searchLines.length).join('\n');
const searchChunk = searchLines.join('\n');
const similarity = getSimilarity(originalChunk, searchChunk);
if (similarity > bestMatchScore) {
bestMatchScore = similarity;
matchIndex = i;
}
}
}
// Require similarity to meet threshold
if (matchIndex === -1 || bestMatchScore < this.fuzzyThreshold) {
return false;
}
// Get the matched lines from the original content
const matchedLines = originalLines.slice(matchIndex, matchIndex + searchLines.length);
@@ -175,13 +194,13 @@ Your search/replace content here
const match = line.match(/^[\t ]*/);
return match ? match[0] : '';
});
// Get the exact indentation of each line in the search block
const searchIndents = searchLines.map(line => {
const match = line.match(/^[\t ]*/);
return match ? match[0] : '';
});
// Apply the replacement while preserving exact indentation
const indentedReplaceLines = replaceLines.map((line, i) => {
// Get the matched line's exact indentation
@@ -198,7 +217,7 @@ Your search/replace content here
// Apply the matched indentation plus any relative indentation
return matchedIndent + relativeIndent + line.trim();
});
// Construct the final content
const beforeMatch = originalLines.slice(0, matchIndex);
const afterMatch = originalLines.slice(matchIndex + searchLines.length);