Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions src/core/prompts/tools/insert-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,35 @@ export function getInsertContentDescription(args: ToolArgs): string {
Description: Inserts content at specific line positions in a file. This is the primary tool for adding new content and code (functions/methods/classes, imports, attributes etc.) as it allows for precise insertions without overwriting existing content. The tool uses an efficient line-based insertion system that maintains file integrity and proper ordering of multiple insertions. Beware to use the proper indentation. This tool is the preferred way to add new content and code to files.
Parameters:
- path: (required) The path of the file to insert content into (relative to the current workspace directory ${args.cwd.toPosix()})
- operations: (required) A JSON array of insertion operations. Each operation is an object with:
* start_line: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content.
* content: (required) The content to insert at the specified position. IMPORTANT NOTE: If the content is a single line, it can be a string. If it's a multi-line content, it should be a string with newline characters (\n) for line breaks. Make sure to include the correct indentation for the content.
- operations: (required) An XML list of insertion operations. Each operation is defined with:
* <operation>: Container for each insertion operation
* <start_line>: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content.
* <content>: (required) The content to insert at the specified position. Can be single line or multi-line content. Make sure to include the correct indentation for the content.
Usage:
<insert_content>
<path>File path here</path>
<operations>[
{
"start_line": 10,
"content": "Your content here"
}
]</operations>
<operations>
<operation>
<start_line>10</start_line>
<content>Your content here</content>
</operation>
</operations>
</insert_content>
Example: Insert a new function and its import statement
<insert_content>
<path>File path here</path>
<operations>[
{
"start_line": 1,
"content": "import { sum } from './utils';"
},
{
"start_line": 10,
"content": "function calculateTotal(items: number[]): number {\n return items.reduce((sum, item) => sum + item, 0);\n}"
}
]</operations>
<operations>
<operation>
<start_line>1</start_line>
<content>import { sum } from './utils';</content>
</operation>
<operation>
<start_line>10</start_line>
<content>function calculateTotal(items: number[]): number {
return items.reduce((sum, item) => sum + item, 0);
}
</content>
</operation>
</operations>
</insert_content>`
}
76 changes: 50 additions & 26 deletions src/core/prompts/tools/search-and-replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ export function getSearchAndReplaceDescription(args: ToolArgs): string {
Description: Request to perform search and replace operations on a file. Each operation can specify a search pattern (string or regex) and replacement text, with optional line range restrictions and regex flags. Shows a diff preview before applying changes.
Parameters:
- path: (required) The path of the file to modify (relative to the current workspace directory ${args.cwd.toPosix()})
- operations: (required) A JSON array of search/replace operations. Each operation is an object with:
- operations: (required) A list of search/replace operations as XML elements. Each operation can have these elements:
* search: (required) The text or pattern to search for
* replace: (required) The text to replace matches with. If multiple lines need to be replaced, use "\n" for newlines
* replace: (required) The text to replace matches with.
* start_line: (optional) Starting line number for restricted replacement
* end_line: (optional) Ending line number for restricted replacement
* use_regex: (optional) Whether to treat search as a regex pattern
Expand All @@ -16,37 +16,61 @@ Parameters:
Usage:
<search_and_replace>
<path>File path here</path>
<operations>[
{
"search": "text to find",
"replace": "replacement text",
"start_line": 1,
"end_line": 10
}
]</operations>
<operations>
<operation>
<search>text to find</search>
<replace>replacement text</replace>
<start_line>1</start_line>
<end_line>10</end_line>
</operation>
</operations>
</search_and_replace>
Example: Replace "foo" with "bar" in lines 1-10 of example.ts
<search_and_replace>
<path>example.ts</path>
<operations>[
{
"search": "foo",
"replace": "bar",
"start_line": 1,
"end_line": 10
}
]</operations>
<operations>
<operation>
<search>foo</search>
<replace>bar</replace>
<start_line>1</start_line>
<end_line>10</end_line>
</operation>
</operations>
</search_and_replace>
Example: Replace all occurrences of "old" with "new" using regex
<search_and_replace>
<path>example.ts</path>
<operations>[
{
"search": "old\\w+",
"replace": "new$&",
"use_regex": true,
"ignore_case": true
}
]</operations>
<operations>
<operation>
<search>old\\w+</search>
<replace>new$&</replace>
<use_regex>true</use_regex>
<ignore_case>true</ignore_case>
</operation>
</operations>
</search_and_replace>

Example: Multiple replacements in a JavaScript file
<search_and_replace>
<path>app.js</path>
<operations>
<operation>
<search>const</search>
<replace>let</replace>
<start_line>1</start_line>
<end_line>100</end_line>
</operation>
<operation>
<search>function\\s+(\\w+)</search>
<replace>const $1 = function</replace>
<use_regex>true</use_regex>
</operation>
<operation>
<search>console\\.log</search>
<replace>logger.info</replace>
<use_regex>true</use_regex>
<ignore_case>true</ignore_case>
</operation>
</operations>
</search_and_replace>`
}
23 changes: 15 additions & 8 deletions src/core/tools/insertContentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getReadablePath } from "../../utils/path"
import { Cline } from "../Cline"
import { ToolUse } from "../assistant-message"
import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types"
import { parseXml } from "../../utils/xml"
import { formatResponse } from "../prompts/responses"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import path from "path"
Expand Down Expand Up @@ -58,23 +59,29 @@ export async function insertContentTool(
return
}

let parsedOperations: Array<{
type Operation = {
start_line: number
content: string
}>
}
let parsedOperations: {
operation: Operation[] | Operation
}

try {
parsedOperations = JSON.parse(operations)
if (!Array.isArray(parsedOperations)) {
throw new Error("Operations must be an array")
parsedOperations = parseXml(operations) as {
operation: Operation[] | Operation
}
} catch (error) {
cline.consecutiveMistakeCount++
await cline.say("error", `Failed to parse operations JSON: ${error.message}`)
pushToolResult(formatResponse.toolError("Invalid operations JSON format"))
await cline.say("error", `Failed to parse operations: ${error.message}`)
pushToolResult(formatResponse.toolError("Invalid operations XML format"))
return
}

const normalizedOperations = Array.isArray(parsedOperations?.operation)
? parsedOperations.operation
: [parsedOperations?.operation].filter((op): op is Operation => op !== undefined)

cline.consecutiveMistakeCount = 0

// Read the file
Expand All @@ -85,7 +92,7 @@ export async function insertContentTool(

const updatedContent = insertGroups(
lines,
parsedOperations.map((elem) => {
normalizedOperations.map((elem) => {
return {
index: elem.start_line - 1,
elements: elem.content.split("\n"),
Expand Down
27 changes: 19 additions & 8 deletions src/core/tools/searchAndReplaceTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Cline } from "../Cline"
import { ToolUse } from "../assistant-message"
import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types"
import { formatResponse } from "../prompts/responses"
import { parseXml } from "../../utils/xml"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { getReadablePath } from "../../utils/path"
import path from "path"
Expand Down Expand Up @@ -57,35 +58,45 @@ export async function searchAndReplaceTool(
return
}

let parsedOperations: Array<{
type Operation = {
search: string
replace: string
start_line?: number
end_line?: number
use_regex?: boolean
ignore_case?: boolean
regex_flags?: string
}>
}
let parsedOperations: {
operation: Operation[] | Operation
}

try {
parsedOperations = JSON.parse(operations)
if (!Array.isArray(parsedOperations)) {
throw new Error("Operations must be an array")
parsedOperations = parseXml(operations, [
"operation.search",
"operation.replace",
"operation.regex_flags",
]) as {
operation: Operation[] | Operation
}
} catch (error) {
cline.consecutiveMistakeCount++
await cline.say("error", `Failed to parse operations JSON: ${error.message}`)
pushToolResult(formatResponse.toolError("Invalid operations JSON format"))
await cline.say("error", `Failed to parse operations: ${error.message}`)
pushToolResult(formatResponse.toolError("Invalid operations XML format"))
return
}

const normalizedOperations = Array.isArray(parsedOperations?.operation)
? parsedOperations.operation
: [parsedOperations?.operation].filter((op): op is Operation => op !== undefined)

// Read the original file content
const fileContent = await fs.readFile(absolutePath, "utf-8")
cline.diffViewProvider.editType = "modify"
cline.diffViewProvider.originalContent = fileContent
let lines = fileContent.split("\n")

for (const op of parsedOperations) {
for (const op of normalizedOperations) {
const flags = op.regex_flags ?? (op.ignore_case ? "gi" : "g")
const multilineFlags = flags.includes("m") ? flags : flags + "m"

Expand Down
7 changes: 5 additions & 2 deletions src/utils/xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ export function parseXml(xmlString: string, stopNodes?: string[]): unknown {
const _stopNodes = stopNodes ?? []
try {
const parser = new XMLParser({
// Preserve attribute types (don't convert numbers/booleans)
ignoreAttributes: false,
attributeNamePrefix: "@_",
parseAttributeValue: false,
parseTagValue: false,
// Parse numbers and booleans in text nodes
parseAttributeValue: true,
parseTagValue: true,
// Trim whitespace from text nodes
trimValues: true,
stopNodes: _stopNodes,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrubens this config to escape xml content

})
Expand Down
Loading