diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index 93c6bc5090..2c637bc219 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -9,6 +9,7 @@ import { Task } from "../task/Task" import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { unescapeHtmlEntities } from "../../utils/text-normalization" @@ -162,8 +163,7 @@ export async function applyDiffTool( return } - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges() + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { @@ -178,13 +178,33 @@ export async function applyDiffTool( partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` } - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists) - - if (partFailHint) { - pushToolResult(partFailHint + message) + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + partFailHint + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using this updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) } else { - pushToolResult(message) + pushToolResult( + `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + partFailHint, + ) } await cline.diffViewProvider.reset() diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index 0963bc78cc..910bed5fe4 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -136,8 +136,7 @@ export async function insertContentTool( return } - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges() + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { @@ -146,14 +145,34 @@ export async function insertContentTool( cline.didEditFile = true - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult( - cline, - cline.cwd, - false, // Always false for insert_content + if (!userEdits) { + pushToolResult( + `The content was successfully inserted in ${relPath.toPosix()} at line ${lineNumber}.${newProblemsMessage}`, + ) + await cline.diffViewProvider.reset() + return + } + + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: "insertContent", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + lineNumber: lineNumber, + } satisfies ClineSayTool), ) - pushToolResult(message) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + + `\n${finalContent}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using this updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) await cline.diffViewProvider.reset() } catch (error) { diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 58d246b133..de98fcafea 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -219,8 +219,7 @@ export async function searchAndReplaceTool( return } - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges() + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { @@ -229,14 +228,34 @@ export async function searchAndReplaceTool( cline.didEditFile = true - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult( - cline, - cline.cwd, - false, // Always false for search_and_replace + if (!userEdits) { + pushToolResult(`The content was successfully replaced in ${relPath}.${newProblemsMessage}`) + await cline.diffViewProvider.reset() + return + } + + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: "appliedDiff", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), ) - pushToolResult(message) + // Format and send response with user's updates + const resultMessage = [ + `The user made the following updates to your content:\n\n${userEdits}\n\n`, + `The updated content has been successfully saved to ${validRelPath.toPosix()}. Here is the full, updated content of the file:\n\n`, + `\n${finalContent}\n\n\n`, + `Please note:\n`, + `1. You do not need to re-write the file with these changes, as they have already been applied.\n`, + `2. Proceed with the task using the updated file content as the new baseline.\n`, + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.`, + newProblemsMessage, + ].join("") + + pushToolResult(resultMessage) // Record successful tool usage and cleanup cline.recordToolUsage("search_and_replace") diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 63191acb7e..946e0d63dd 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -8,7 +8,7 @@ import { formatResponse } from "../prompts/responses" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" import { fileExistsAtPath } from "../../utils/fs" -import { stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/misc/extract-text" +import { addLineNumbers, stripLineNumbers, everyLineHasLineNumbers } from "../../integrations/misc/extract-text" import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { detectCodeOmission } from "../../integrations/editor/detect-omission" @@ -208,8 +208,7 @@ export async function writeToFileTool( return } - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges() + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { @@ -218,10 +217,31 @@ export async function writeToFileTool( cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists) + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) - pushToolResult(message) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using this updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult(`The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`) + } await cline.diffViewProvider.reset() diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index 3ab0419618..adc19ff014 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -3,14 +3,11 @@ import * as path from "path" import * as fs from "fs/promises" import * as diff from "diff" import stripBom from "strip-bom" -import { XMLBuilder } from "fast-xml-parser" import { createDirectoriesForFile } from "../../utils/fs" -import { arePathsEqual, getReadablePath } from "../../utils/path" +import { arePathsEqual } from "../../utils/path" import { formatResponse } from "../../core/prompts/responses" import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics" -import { ClineSayTool } from "../../shared/ExtensionMessage" -import { Task } from "../../core/task/Task" import { DecorationController } from "./DecorationController" @@ -18,9 +15,6 @@ export const DIFF_VIEW_URI_SCHEME = "cline-diff" // TODO: https://github.com/cline/cline/pull/3354 export class DiffViewProvider { - // Properties to store the results of saveChanges - newProblemsMessage?: string - userEdits?: string editType?: "create" | "modify" isEditing = false originalContent: string | undefined @@ -244,91 +238,13 @@ export class DiffViewProvider { normalizedEditedContent, ) - // Store the results as class properties for formatFileWriteResponse to use - this.newProblemsMessage = newProblemsMessage - this.userEdits = userEdits - return { newProblemsMessage, userEdits, finalContent: normalizedEditedContent } } else { // No changes to Roo's edits. - // Store the results as class properties for formatFileWriteResponse to use - this.newProblemsMessage = newProblemsMessage - this.userEdits = undefined - return { newProblemsMessage, userEdits: undefined, finalContent: normalizedEditedContent } } } - /** - * Formats a standardized XML response for file write operations - * - * @param cwd Current working directory for path resolution - * @param isNewFile Whether this is a new file or an existing file being modified - * @returns Formatted message and say object for UI feedback - */ - async pushToolWriteResult(task: Task, cwd: string, isNewFile: boolean): Promise { - if (!this.relPath) { - throw new Error("No file path available in DiffViewProvider") - } - - // Only send user_feedback_diff if userEdits exists - if (this.userEdits) { - // Create say object for UI feedback - const say: ClineSayTool = { - tool: isNewFile ? "newFileCreated" : "editedExistingFile", - path: getReadablePath(cwd, this.relPath), - diff: this.userEdits, - } - - // Send the user feedback - await task.say("user_feedback_diff", JSON.stringify(say)) - } - - // Build XML response - const xmlObj = { - file_write_result: { - path: this.relPath, - operation: isNewFile ? "created" : "modified", - user_edits: this.userEdits ? this.userEdits : undefined, - problems: this.newProblemsMessage || undefined, - notice: { - i: [ - "You do not need to re-read the file, as you have seen all changes", - "Proceed with the task using these changes as the new baseline.", - ...(this.userEdits - ? [ - "If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.", - ] - : []), - ], - }, - }, - } - - const builder = new XMLBuilder({ - format: true, - indentBy: "", - suppressEmptyNode: true, - processEntities: false, - tagValueProcessor: (name, value) => { - if (typeof value === "string") { - // Only escape <, >, and & characters - return value.replace(/&/g, "&").replace(//g, ">") - } - return value - }, - attributeValueProcessor: (name, value) => { - if (typeof value === "string") { - // Only escape <, >, and & characters - return value.replace(/&/g, "&").replace(//g, ">") - } - return value - }, - }) - - return builder.build(xmlObj) - } - async revertChanges(): Promise { if (!this.relPath || !this.activeDiffEditor) { return