Skip to content

Commit e4c23fb

Browse files
committed
Prioritize exact line matches in search/replace
1 parent 130c5ff commit e4c23fb

File tree

2 files changed

+133
-25
lines changed

2 files changed

+133
-25
lines changed

src/core/diff/strategies/__tests__/search-replace.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,95 @@ function two() {
610610
611611
function three() {
612612
return 3;
613+
}`)
614+
})
615+
616+
it('should prioritize exact line match over expanded search', () => {
617+
const originalContent = `
618+
function one() {
619+
return 1;
620+
}
621+
622+
function process() {
623+
return "old";
624+
}
625+
626+
function process() {
627+
return "old";
628+
}
629+
630+
function two() {
631+
return 2;
632+
}`
633+
const diffContent = `test.ts
634+
<<<<<<< SEARCH
635+
function process() {
636+
return "old";
637+
}
638+
=======
639+
function process() {
640+
return "new";
641+
}
642+
>>>>>>> REPLACE`
643+
644+
// Should match the second instance exactly at lines 10-12
645+
// even though the first instance at 6-8 is within the expanded search range
646+
const result = strategy.applyDiff(originalContent, diffContent, 10, 12)
647+
expect(result).toBe(`
648+
function one() {
649+
return 1;
650+
}
651+
652+
function process() {
653+
return "old";
654+
}
655+
656+
function process() {
657+
return "new";
658+
}
659+
660+
function two() {
661+
return 2;
662+
}`)
663+
})
664+
665+
it('should fall back to expanded search only if exact match fails', () => {
666+
const originalContent = `
667+
function one() {
668+
return 1;
669+
}
670+
671+
function process() {
672+
return "target";
673+
}
674+
675+
function two() {
676+
return 2;
677+
}`.trim()
678+
const diffContent = `test.ts
679+
<<<<<<< SEARCH
680+
function process() {
681+
return "target";
682+
}
683+
=======
684+
function process() {
685+
return "updated";
686+
}
687+
>>>>>>> REPLACE`
688+
689+
// Specify wrong line numbers (3-5), but content exists at 6-8
690+
// Should still find and replace it since it's within the expanded range
691+
const result = strategy.applyDiff(originalContent, diffContent, 3, 5)
692+
expect(result).toBe(`function one() {
693+
return 1;
694+
}
695+
696+
function process() {
697+
return "updated";
698+
}
699+
700+
function two() {
701+
return 2;
613702
}`)
614703
})
615704
})

src/core/diff/strategies/search-replace.ts

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -132,41 +132,60 @@ Your search/replace content here
132132
const replaceLines = replaceContent.split(/\r?\n/);
133133
const originalLines = originalContent.split(/\r?\n/);
134134

135-
// Determine search range based on provided line numbers
136-
let searchStartIndex = 0;
137-
let searchEndIndex = originalLines.length;
138-
139-
if (startLine !== undefined || endLine !== undefined) {
140-
// Convert to 0-based index and add buffer
141-
if (startLine !== undefined) {
142-
searchStartIndex = Math.max(0, startLine - 6);
143-
}
144-
if (endLine !== undefined) {
145-
searchEndIndex = Math.min(originalLines.length, endLine + 5);
146-
}
147-
}
148-
149-
// Find the search content in the original using fuzzy matching
135+
// First try exact line range if provided
150136
let matchIndex = -1;
151137
let bestMatchScore = 0;
152138

153-
for (let i = searchStartIndex; i <= searchEndIndex - searchLines.length; i++) {
154-
// Join the lines and calculate overall similarity
155-
const originalChunk = originalLines.slice(i, i + searchLines.length).join('\n');
139+
if (startLine !== undefined && endLine !== undefined) {
140+
// Convert to 0-based index
141+
const exactStartIndex = startLine - 1;
142+
const exactEndIndex = endLine - 1;
143+
144+
// Check exact range first
145+
const originalChunk = originalLines.slice(exactStartIndex, exactEndIndex + 1).join('\n');
156146
const searchChunk = searchLines.join('\n');
157147

158148
const similarity = getSimilarity(originalChunk, searchChunk);
159-
if (similarity > bestMatchScore) {
149+
if (similarity >= this.fuzzyThreshold) {
150+
matchIndex = exactStartIndex;
160151
bestMatchScore = similarity;
161-
matchIndex = i;
162152
}
163153
}
164-
154+
155+
// If no match found in exact range, try expanded range
156+
if (matchIndex === -1) {
157+
let searchStartIndex = 0;
158+
let searchEndIndex = originalLines.length;
159+
160+
if (startLine !== undefined || endLine !== undefined) {
161+
// Convert to 0-based index and add buffer
162+
if (startLine !== undefined) {
163+
searchStartIndex = Math.max(0, startLine - 6);
164+
}
165+
if (endLine !== undefined) {
166+
searchEndIndex = Math.min(originalLines.length, endLine + 5);
167+
}
168+
}
169+
170+
// Find the search content in the expanded range using fuzzy matching
171+
for (let i = searchStartIndex; i <= searchEndIndex - searchLines.length; i++) {
172+
// Join the lines and calculate overall similarity
173+
const originalChunk = originalLines.slice(i, i + searchLines.length).join('\n');
174+
const searchChunk = searchLines.join('\n');
175+
176+
const similarity = getSimilarity(originalChunk, searchChunk);
177+
if (similarity > bestMatchScore) {
178+
bestMatchScore = similarity;
179+
matchIndex = i;
180+
}
181+
}
182+
}
183+
165184
// Require similarity to meet threshold
166185
if (matchIndex === -1 || bestMatchScore < this.fuzzyThreshold) {
167186
return false;
168187
}
169-
188+
170189
// Get the matched lines from the original content
171190
const matchedLines = originalLines.slice(matchIndex, matchIndex + searchLines.length);
172191

@@ -175,13 +194,13 @@ Your search/replace content here
175194
const match = line.match(/^[\t ]*/);
176195
return match ? match[0] : '';
177196
});
178-
197+
179198
// Get the exact indentation of each line in the search block
180199
const searchIndents = searchLines.map(line => {
181200
const match = line.match(/^[\t ]*/);
182201
return match ? match[0] : '';
183202
});
184-
203+
185204
// Apply the replacement while preserving exact indentation
186205
const indentedReplaceLines = replaceLines.map((line, i) => {
187206
// Get the matched line's exact indentation
@@ -198,7 +217,7 @@ Your search/replace content here
198217
// Apply the matched indentation plus any relative indentation
199218
return matchedIndent + relativeIndent + line.trim();
200219
});
201-
220+
202221
// Construct the final content
203222
const beforeMatch = originalLines.slice(0, matchIndex);
204223
const afterMatch = originalLines.slice(matchIndex + searchLines.length);

0 commit comments

Comments
 (0)