Skip to content

Commit f3bbf95

Browse files
committed
Move to dedicated replacement-engine which is applied at beginning of processing every replacement-block
1 parent cb03f33 commit f3bbf95

File tree

2 files changed

+66
-27
lines changed

2 files changed

+66
-27
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { SearchReplaceContext } from "../multi-search-replace"
2+
3+
export class SuperfluousDuplicatedLineEngine {
4+
public static process(originalContent: string, context: SearchReplaceContext): SearchReplaceContext {
5+
const { startLine, searchContent, replaceContent } = context
6+
7+
// Early detection of superfluous duplicated line pattern
8+
// Check if replace content has more lines than search content
9+
const searchLines = searchContent.split(/\r?\n/)
10+
const replaceLines = replaceContent.split(/\r?\n/)
11+
const originalLines = originalContent.split(/\r?\n/)
12+
13+
if (searchLines.length > 0 && replaceLines.length > searchLines.length && startLine > 0) {
14+
// Check if the first part of replace content is similar to search content
15+
const firstPartOfReplace = replaceLines.slice(0, searchLines.length).join("\n")
16+
17+
// Simple similarity check (we can make this more sophisticated later)
18+
const isFirstPartSimilar = firstPartOfReplace === searchContent
19+
20+
if (isFirstPartSimilar && replaceLines.length > searchLines.length) {
21+
// Get the line that comes after the search block in replace content
22+
const lineAfterSearchInReplace = replaceLines[searchLines.length]
23+
24+
// Get the line that comes after the search block in original content
25+
const lineIndexInOriginal = startLine - 1 + searchLines.length
26+
if (lineIndexInOriginal < originalLines.length) {
27+
const lineAfterSearchInOriginal = originalLines[lineIndexInOriginal]
28+
29+
// If they match, it's likely a superfluous duplicated line scenario
30+
// We can modify the search content to include the extra line
31+
if (lineAfterSearchInReplace.trim() === lineAfterSearchInOriginal.trim()) {
32+
const modifiedSearchContent = searchContent + "\n" + lineAfterSearchInOriginal
33+
return {
34+
startLine,
35+
searchContent: modifiedSearchContent,
36+
replaceContent,
37+
}
38+
}
39+
}
40+
}
41+
}
42+
43+
// No modification needed, return as-is
44+
return context
45+
}
46+
}

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

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { ToolProgressStatus } from "@roo-code/types"
77
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
88
import { ToolUse, DiffStrategy, DiffResult } from "../../../shared/tools"
99
import { normalizeString } from "../../../utils/text-normalization"
10+
import { SuperfluousDuplicatedLineEngine } from "./engines/superfluous-duplicated-line.engine"
11+
12+
export interface SearchReplaceContext {
13+
startLine: number
14+
searchContent: string
15+
replaceContent: string
16+
}
1017

1118
const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches
1219

@@ -406,6 +413,19 @@ Only use a single line of '=======' between search and replacement content, beca
406413
let { searchContent, replaceContent } = replacement
407414
let startLine = replacement.startLine + (replacement.startLine === 0 ? 0 : delta)
408415

416+
// --- Start: Replacement Engine Processing ---
417+
let context = SuperfluousDuplicatedLineEngine.process(originalContent, {
418+
startLine,
419+
searchContent,
420+
replaceContent,
421+
})
422+
423+
// Update variables from engine processing
424+
startLine = context.startLine
425+
searchContent = context.searchContent
426+
replaceContent = context.replaceContent
427+
// --- End: Replacement Engine Processing ---
428+
409429
// First unescape any escaped markers in the content
410430
searchContent = this.unescapeMarkers(searchContent)
411431
replaceContent = this.unescapeMarkers(replaceContent)
@@ -591,33 +611,6 @@ Only use a single line of '=======' between search and replacement content, beca
591611
// Initialize effectiveSearchLinesCount (determines how many lines from original are considered "replaced")
592612
let effectiveSearchLinesCount = searchLines.length // Default
593613

594-
// Heuristic to adjust effectiveSearchLinesCount for superfluous duplicated line pattern
595-
if (searchLines.length > 0 && replaceLines.length > searchLines.length) {
596-
const searchBlockContent = searchLines.join("\n")
597-
// Ensure replaceLines has enough elements before slicing
598-
const firstPartOfReplaceBlock = replaceLines.slice(0, searchLines.length).join("\n")
599-
600-
// Check if the search content is highly similar to the beginning of the replace content
601-
if (getSimilarity(searchBlockContent, firstPartOfReplaceBlock) > 0.95) {
602-
// Ensure there's a line in replaceLines immediately after the part that matches searchLines
603-
if (searchLines.length < replaceLines.length) {
604-
const lineInReplaceAfterPrefix = replaceLines[searchLines.length]
605-
606-
// Ensure there's a line in the original content (resultLines) immediately after the matched search block
607-
if (matchIndex + searchLines.length < resultLines.length) {
608-
const lineInOriginalAfterMatchedSearch = resultLines[matchIndex + searchLines.length]
609-
610-
// If the line in the replace block (after the prefix) is identical (ignoring leading/trailing whitespace)
611-
// to the line in the original content (after the search match),
612-
// it's likely a duplicated line scenario.
613-
if (lineInReplaceAfterPrefix.trim() === lineInOriginalAfterMatchedSearch.trim()) {
614-
effectiveSearchLinesCount = searchLines.length + 1 // Consume the duplicated line from the original
615-
}
616-
}
617-
}
618-
}
619-
}
620-
621614
// Construct the final content
622615
const beforeMatch = resultLines.slice(0, matchIndex)
623616
// Use effectiveSearchLinesCount here to determine the slice point

0 commit comments

Comments
 (0)