|
|
@@ -56,7 +56,7 @@ export class SearchReplaceDiffStrategy implements DiffStrategy {
|
|
|
|
|
|
getToolDescription(cwd: string): string {
|
|
|
return `## apply_diff
|
|
|
-Description: Request to replace existing code using search and replace blocks.
|
|
|
+Description: Request to replace existing code using a search and replace block.
|
|
|
This tool allows for precise, surgical replaces to files by specifying exactly what content to search for and what to replace it with.
|
|
|
The tool will maintain proper indentation and formatting while making changes.
|
|
|
Only a single operation is allowed per tool use.
|
|
|
@@ -65,33 +65,32 @@ If you're not confident in the exact content to search for, use the read_file to
|
|
|
|
|
|
Parameters:
|
|
|
- path: (required) The path of the file to modify (relative to the current working directory ${cwd})
|
|
|
-- diff: (required) The search/replace blocks defining the changes.
|
|
|
-
|
|
|
-Format:
|
|
|
-1. First line must be the file path
|
|
|
-2. Followed by search/replace blocks:
|
|
|
- \`\`\`
|
|
|
- <<<<<<< SEARCH
|
|
|
- [exact content to find including whitespace]
|
|
|
- =======
|
|
|
- [new content to replace with]
|
|
|
- >>>>>>> REPLACE
|
|
|
- \`\`\`
|
|
|
+- diff: (required) The search/replace block defining the changes.
|
|
|
+- start_line: (required) The line number where the search block starts.
|
|
|
+- end_line: (required) The line number where the search block ends.
|
|
|
+
|
|
|
+Diff format:
|
|
|
+\`\`\`
|
|
|
+<<<<<<< SEARCH
|
|
|
+[exact content to find including whitespace]
|
|
|
+=======
|
|
|
+[new content to replace with]
|
|
|
+>>>>>>> REPLACE
|
|
|
+\`\`\`
|
|
|
|
|
|
Example:
|
|
|
|
|
|
Original file:
|
|
|
\`\`\`
|
|
|
-def calculate_total(items):
|
|
|
- total = 0
|
|
|
- for item in items:
|
|
|
- total += item
|
|
|
- return total
|
|
|
+1 | def calculate_total(items):
|
|
|
+2 | total = 0
|
|
|
+3 | for item in items:
|
|
|
+4 | total += item
|
|
|
+5 | return total
|
|
|
\`\`\`
|
|
|
|
|
|
Search/Replace content:
|
|
|
\`\`\`
|
|
|
-main.py
|
|
|
<<<<<<< SEARCH
|
|
|
def calculate_total(items):
|
|
|
total = 0
|
|
|
@@ -111,10 +110,12 @@ Usage:
|
|
|
<diff>
|
|
|
Your search/replace content here
|
|
|
</diff>
|
|
|
+<start_line>1</start_line>
|
|
|
+<end_line>5</end_line>
|
|
|
</apply_diff>`
|
|
|
}
|
|
|
|
|
|
- applyDiff(originalContent: string, diffContent: string): string | false {
|
|
|
+ applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): string | false {
|
|
|
// Extract the search and replace blocks
|
|
|
const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>> REPLACE/);
|
|
|
if (!match) {
|
|
|
@@ -131,11 +132,25 @@ 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
|
|
|
let matchIndex = -1;
|
|
|
let bestMatchScore = 0;
|
|
|
|
|
|
- for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
|
|
|
+ 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');
|
|
|
@@ -169,40 +184,19 @@ Your search/replace content here
|
|
|
|
|
|
// Apply the replacement while preserving exact indentation
|
|
|
const indentedReplaceLines = replaceLines.map((line, i) => {
|
|
|
- // Get the corresponding original and search indentations
|
|
|
- const originalIndent = originalIndents[Math.min(i, originalIndents.length - 1)];
|
|
|
- const searchIndent = searchIndents[Math.min(i, searchIndents.length - 1)];
|
|
|
+ // Get the matched line's exact indentation
|
|
|
+ const matchedIndent = originalIndents[0];
|
|
|
|
|
|
- // Get the current line's indentation
|
|
|
+ // Get the current line's indentation relative to the search content
|
|
|
const currentIndentMatch = line.match(/^[\t ]*/);
|
|
|
const currentIndent = currentIndentMatch ? currentIndentMatch[0] : '';
|
|
|
+ const searchBaseIndent = searchIndents[0] || '';
|
|
|
|
|
|
- // Get the corresponding search line's indentation
|
|
|
- const searchLineIndex = Math.min(i, searchLines.length - 1);
|
|
|
- const searchLineIndent = searchIndents[searchLineIndex];
|
|
|
-
|
|
|
- // Get the corresponding original line's indentation
|
|
|
- const originalLineIndex = Math.min(i, originalIndents.length - 1);
|
|
|
- const originalLineIndent = originalIndents[originalLineIndex];
|
|
|
-
|
|
|
- // If this line has the same indentation as its corresponding search line,
|
|
|
- // use the original indentation
|
|
|
- if (currentIndent === searchLineIndent) {
|
|
|
- return originalLineIndent + line.trim();
|
|
|
- }
|
|
|
-
|
|
|
- // Otherwise, preserve the original indentation structure
|
|
|
- const indentChar = originalLineIndent.charAt(0) || '\t';
|
|
|
- const indentLevel = Math.floor(originalLineIndent.length / indentChar.length);
|
|
|
-
|
|
|
- // Calculate the relative indentation from the search line
|
|
|
- const searchLevel = Math.floor(searchLineIndent.length / indentChar.length);
|
|
|
- const currentLevel = Math.floor(currentIndent.length / indentChar.length);
|
|
|
- const relativeLevel = currentLevel - searchLevel;
|
|
|
-
|
|
|
- // Apply the relative indentation to the original level
|
|
|
- const targetLevel = Math.max(0, indentLevel + relativeLevel);
|
|
|
- return indentChar.repeat(targetLevel) + line.trim();
|
|
|
+ // Calculate the relative indentation from the search content
|
|
|
+ const relativeIndent = currentIndent.slice(searchBaseIndent.length);
|
|
|
+
|
|
|
+ // Apply the matched indentation plus any relative indentation
|
|
|
+ return matchedIndent + relativeIndent + line.trim();
|
|
|
});
|
|
|
|
|
|
// Construct the final content
|