From d480015b16a88e33671b66c8753d92387cd82e53 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:17:52 -0800 Subject: [PATCH] Get diff based editing working! Uses a new EDIT FILES section in system prompt and combination of write_to_file and replace_in_file --- src/core/Cline.ts | 85 +++++--- src/core/assistant-message/diff.ts | 9 + src/core/assistant-message/index.ts | 7 + .../parse-assistant-message.ts | 20 +- src/core/prompts/system.ts | 196 +++++++++++------- src/integrations/editor/DiffViewProvider.ts | 20 +- 6 files changed, 214 insertions(+), 123 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 85ba079972..cfe8eaaee7 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -13,12 +13,14 @@ import { DiffViewProvider } from "../integrations/editor/DiffViewProvider" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" import { extractTextFromFile } from "../integrations/misc/extract-text" import { TerminalManager } from "../integrations/terminal/TerminalManager" +import { BrowserSession } from "../services/browser/BrowserSession" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" import { regexSearchFiles } from "../services/ripgrep" import { parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter" import { ApiConfiguration } from "../shared/api" import { findLastIndex } from "../shared/array" +import { AutoApprovalSettings } from "../shared/AutoApprovalSettings" import { combineApiRequests } from "../shared/combineApiRequests" import { combineCommandSequences, COMMAND_REQ_APP_STRING } from "../shared/combineCommandSequences" import { @@ -40,15 +42,13 @@ import { ClineAskResponse } from "../shared/WebviewMessage" import { calculateApiCost } from "../utils/cost" import { fileExistsAtPath } from "../utils/fs" import { arePathsEqual, getReadablePath } from "../utils/path" -import { parseMentions } from "./mentions" import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message" +import { constructNewFileContent } from "./assistant-message/diff" +import { parseMentions } from "./mentions" import { formatResponse } from "./prompts/responses" import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system" import { truncateHalfConversation } from "./sliding-window" import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider" -import { BrowserSession } from "../services/browser/BrowserSession" -import { constructNewFileContent } from "./assistant-message/diff" -import { AutoApprovalSettings } from "../shared/AutoApprovalSettings" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -621,7 +621,7 @@ export class Cline { text: `[TASK RESUMPTION] This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful and assess whether you should retry. If the last tool was a browser_action, the browser has been closed and you must launch a new browser if needed.${ wasRecent - ? "\n\nIMPORTANT: If the last tool use was a write_to_file that was interrupted, the file was reverted back to its original state before the interrupted edit, and you do NOT need to re-read the file as you already have its up-to-date contents." + ? "\n\nIMPORTANT: If the last tool use was a replace_in_file or write_to_file that was interrupted, the file was reverted back to its original state before the interrupted edit, and you do NOT need to re-read the file as you already have its up-to-date contents." : "" }` + (responseText @@ -896,6 +896,8 @@ export class Cline { return `[${block.name} for '${block.params.path}']` case "write_to_file": return `[${block.name} for '${block.params.path}']` + case "replace_in_file": + return `[${block.name} for '${block.params.path}']` case "search_files": return `[${block.name} for '${block.params.regex}'${ block.params.file_pattern ? ` in '${block.params.file_pattern}'` : "" @@ -1035,11 +1037,15 @@ export class Cline { } switch (block.name) { - case "write_to_file": { + case "write_to_file": + case "replace_in_file": { + console.log("editing file with", block.name) + const relPath: string | undefined = block.params.path - let diff: string | undefined = block.params.diff - if (!relPath || !diff) { - // checking for diff ensure relPath is complete + let content: string | undefined = block.params.content // for write_to_file + let diff: string | undefined = block.params.diff // for replace_in_file + if (!relPath || (!content && !diff)) { + // checking for content/diff ensures relPath is complete // wait so we can determine if it's a new file or editing an existing file break } @@ -1053,26 +1059,30 @@ export class Cline { this.diffViewProvider.editType = fileExists ? "modify" : "create" } - const sharedMessageProps: ClineSayTool = { - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(cwd, removeClosingTag("path", relPath)), - } try { // Construct newContent from diff - let newContent = await constructNewFileContent( - diff, - this.diffViewProvider.originalContent || "", - !block.partial, - ) + let newContent: string + if (diff) { + newContent = await constructNewFileContent( + diff, + this.diffViewProvider.originalContent || "", + !block.partial, + ) + } else if (content) { + newContent = content - // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) - // if (newContent.startsWith("```")) { - // // this handles cases where it includes language specifiers like ```python ```js - // newContent = newContent.split("\n").slice(1).join("\n").trim() - // } - // if (newContent.endsWith("```")) { - // newContent = newContent.split("\n").slice(0, -1).join("\n").trim() - // } + // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) + if (newContent.startsWith("```")) { + // this handles cases where it includes language specifiers like ```python ```js + newContent = newContent.split("\n").slice(1).join("\n").trim() + } + if (newContent.endsWith("```")) { + newContent = newContent.split("\n").slice(0, -1).join("\n").trim() + } + } else { + // can't happen, since we already checked for content/diff above. but need to do this for type error + break + } if (!this.api.getModel().id.includes("claude")) { // it seems not just llama models are doing this, but also gemini and potentially others @@ -1090,6 +1100,13 @@ export class Cline { newContent = newContent.trimEnd() // remove any trailing newlines, since it's automatically inserted by the editor + const sharedMessageProps: ClineSayTool = { + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cwd, removeClosingTag("path", relPath)), + content: fileExists ? undefined : newContent, + diff: fileExists ? diff : undefined, + } + if (block.partial) { // update gui message const partialMessage = JSON.stringify(sharedMessageProps) @@ -1105,13 +1122,19 @@ export class Cline { } else { if (!relPath) { this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "path")) + pushToolResult(await this.sayAndCreateMissingParamError(block.name, "path")) await this.diffViewProvider.reset() break } - if (!diff) { + if (block.name === "replace_in_file" && !diff) { this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "diff")) + pushToolResult(await this.sayAndCreateMissingParamError("replace_in_file", "diff")) + await this.diffViewProvider.reset() + break + } + if (block.name === "write_to_file" && !content) { + this.consecutiveMistakeCount++ + pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content")) await this.diffViewProvider.reset() break } @@ -1163,11 +1186,11 @@ export class Cline { `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:\n\n` + `\n${finalContent}\n\n\n` + - `IMPORTANT: If you need to make further changes to this file, use this final_file_content as the new baseline for your changes, as it is now the current state of the file (including the user's edits and any auto-formatting done by the system). \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.` + + `4. If you need to make further changes to this file, use this final_file_content as the new reference for your SEARCH/REPLACE operations, as it is now the current state of the file (including the user's edits and any auto-formatting done by the system).\n` + `${newProblemsMessage}`, ) } else { @@ -1175,7 +1198,7 @@ export class Cline { `The content was successfully saved to ${relPath.toPosix()}.\n\n` + `Here is the full, updated content of the file:\n\n` + `\n${finalContent}\n\n\n` + - `IMPORTANT: If you need to make further changes to this file, use this final_file_content as the new baseline for your changes, as it is now the current state of the file (including any auto-formatting done by the system). \n\n` + + `Please note: If you need to make further changes to this file, use this final_file_content as the new reference for your SEARCH/REPLACE operations, as it is now the current state of the file (including any auto-formatting done by the system).\n\n` + `${newProblemsMessage}`, ) } diff --git a/src/core/assistant-message/diff.ts b/src/core/assistant-message/diff.ts index b7af645f29..34c1deb387 100644 --- a/src/core/assistant-message/diff.ts +++ b/src/core/assistant-message/diff.ts @@ -180,6 +180,15 @@ export async function constructNewFileContent( searchEndIndex = originalContent.length } } else { + // Add check for inefficient full-file search + // if (currentSearchContent.trim() === originalContent.trim()) { + // throw new Error( + // "The SEARCH block contains the entire file content. Please either:\n" + + // "1. Use an empty SEARCH block to replace the entire file, or\n" + + // "2. Make focused changes to specific parts of the file that need modification.", + // ) + // } + // Exact search match scenario const exactIndex = originalContent.indexOf(currentSearchContent, lastProcessedIndex) if (exactIndex !== -1) { diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index c18eba3f61..7ad2c27d7b 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -12,6 +12,7 @@ export const toolUseNames = [ "execute_command", "read_file", "write_to_file", + "replace_in_file", "search_files", "list_files", "list_code_definition_names", @@ -29,6 +30,7 @@ export const toolParamNames = [ "command", "requires_approval", "path", + "content", "diff", "regex", "file_pattern", @@ -68,6 +70,11 @@ export interface ReadFileToolUse extends ToolUse { export interface WriteToFileToolUse extends ToolUse { name: "write_to_file" + params: Partial, "path" | "content">> +} + +export interface ReplaceInFileToolUse extends ToolUse { + name: "replace_in_file" params: Partial, "path" | "diff">> } diff --git a/src/core/assistant-message/parse-assistant-message.ts b/src/core/assistant-message/parse-assistant-message.ts index d8e52227e7..e38e8f6458 100644 --- a/src/core/assistant-message/parse-assistant-message.ts +++ b/src/core/assistant-message/parse-assistant-message.ts @@ -61,16 +61,18 @@ export function parseAssistantMessage(assistantMessage: string) { // there's no current param, and not starting a new param - // special case for write_to_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting diff tag and the LAST diff tag. - const diffParamName: ToolParamName = "diff" - if (currentToolUse.name === "write_to_file" && accumulator.endsWith(``)) { + // special case for write_to_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting content tag and the LAST content tag. + const contentParamName: ToolParamName = "content" + if (currentToolUse.name === "write_to_file" && accumulator.endsWith(``)) { const toolContent = accumulator.slice(currentToolUseStartIndex) - const diffStartTag = `<${diffParamName}>` - const diffEndTag = `` - const diffStartIndex = toolContent.indexOf(diffStartTag) + diffStartTag.length - const diffEndIndex = toolContent.lastIndexOf(diffEndTag) - if (diffStartIndex !== -1 && diffEndIndex !== -1 && diffEndIndex > diffStartIndex) { - currentToolUse.params[diffParamName] = toolContent.slice(diffStartIndex, diffEndIndex).trim() + const contentStartTag = `<${contentParamName}>` + const contentEndTag = `` + const contentStartIndex = toolContent.indexOf(contentStartTag) + contentStartTag.length + const contentEndIndex = toolContent.lastIndexOf(contentEndTag) + if (contentStartIndex !== -1 && contentEndIndex !== -1 && contentEndIndex > contentStartIndex) { + currentToolUse.params[contentParamName] = toolContent + .slice(contentStartIndex, contentEndIndex) + .trim() } } diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 5435572177..8329883d1a 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -56,41 +56,51 @@ Usage: ## write_to_file -Description: Request to write to a file using search/replace blocks that define exact changes. Creates new files or modifies existing ones with precise control. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. +Description: Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file. Parameters: - path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()}) -- diff: (required) One or more search/replace blocks following this exact format: +- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. +Usage: + +File path here + +Your file content here + + + +## replace_in_file +Description: Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file. This tool should be used when you need to make targeted changes to specific parts of a file. +Parameters: +- path: (required) The path of the file to modify (relative to the current working directory ${cwd.toPosix()}) +- diff: (required) One or more SEARCH/REPLACE blocks following this exact format: \`\`\` <<<<<<< SEARCH [exact content to find] ======= - [content to replace with] + [new content to replace with] >>>>>>> REPLACE \`\`\` - Each block must follow these critical rules: - 1. SEARCH section must match existing file content EXACTLY: + Critical rules: + 1. SEARCH content must match the associated file section to find EXACTLY: * Match character-for-character including whitespace, indentation, line endings * Include all comments, docstrings, etc. - 2. Only the first match occurrence is replaced - * Use multiple blocks for multiple occurrences - * Include enough context lines to ensure unique matches - 3. Keep blocks focused: - * Break large changes into multiple small blocks - * Include only changing lines and minimal context - * Do not include long runs of unchanged lines - * Each block should modify one logical chunk + 2. SEARCH/REPLACE blocks will ONLY replace the first match occurrence. + * Including multiple unique SEARCH/REPLACE blocks if you need to make multiple changes. + * Include *just* enough lines in each SEARCH section to uniquely match each set of lines that need to change. + 3. Keep SEARCH/REPLACE blocks concise: + * Break large SEARCH/REPLACE blocks into a series of smaller blocks that each change a small portion of the file. + * Include just the changing lines, and a few surrounding lines if needed for uniqueness. + * Do not include long runs of unchanging lines in SEARCH/REPLACE blocks. 4. Special operations: - * New files: Use empty SEARCH section - * Completely replace existing file's contents: Use empty SEARCH section - * Moving code: Use two blocks (delete from original + insert at new location) - * Code deletion: Use empty REPLACE section + * To move code: Use two SEARCH/REPLACE blocks (one to delete from original + one to insert at new location) + * To delete code: Use empty REPLACE section Usage: - + File path here Search and replace blocks here - + ## search_files Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. @@ -247,42 +257,36 @@ Your final result description here ## Example 4: Requesting to create a new file -src/utils/types.ts - -<<<<<<< SEARCH -======= -export interface User { - id: string; - name: string; - email: string; +src/frontend-config.json + +{ + "apiEndpoint": "https://api.example.com", + "theme": { + "primaryColor": "#007bff", + "secondaryColor": "#6c757d", + "fontFamily": "Arial, sans-serif" + }, + "features": { + "darkMode": true, + "notifications": true, + "analytics": false + }, + "version": "1.0.0" } ->>>>>>> REPLACE - + -## Example 5: Requesting to modify existing content +## Example 6: Requesting to make targeted edits to a file - -src/services/UserService.ts - -<<<<<<< SEARCH - async getUser(id: string) { - return await this.users.findOne(id); - } -======= - async getUser(id: string) { - const user = await this.users.findOne(id); - if (!user) throw new Error(\`User \${id} not found\`); - return user; - } ->>>>>>> REPLACE - - -## Example 6: Requesting to move two blocks of code - - + src/components/App.tsx +<<<<<<< SEARCH +import React from 'react'; +======= +import React, { useState } from 'react'; +>>>>>>> REPLACE + <<<<<<< SEARCH function handleSubmit() { saveData(); @@ -305,26 +309,7 @@ return (
>>>>>>> REPLACE - - -## Example 7: Requesting to make multiple focused changes in a file - - -src/config.ts - -<<<<<<< SEARCH -const maxRetries = 3; -======= -const maxRetries = 5; ->>>>>>> REPLACE - -<<<<<<< SEARCH -export const timeout = 1000; -======= -export const timeout = process.env.TIMEOUT || 1000; ->>>>>>> REPLACE - - + # Tool Use Guidelines @@ -742,7 +727,7 @@ The user may ask to add tools or resources that may make sense to add to an exis .getServers() .map((server) => server.name) .join(", ") || "(None running currently)" -}, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use write_to_file to make changes to the files. +}, e.g. if it would use the same API. This would be possible if you can locate the MCP server repository on the user's system by looking at the server arguments for a filepath. You might then use list_files and read_file to explore the files in the repository, and use replace_in_file to make changes to the files. However some MCP servers may be running from installed packages rather than a local repository, in which case it may make more sense to create a new MCP server. @@ -752,17 +737,78 @@ The user may not always request the use or creation of MCP servers. Instead, the Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks. +==== + +EDITING FILES + +You have access to two tools for working with files: **write_to_file** and **replace_in_file**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications. + +# write_to_file + +## Purpose + +- Create a new file, or overwrite the entire contents of an existing file. + +## When to Use + +- Initial file creation, such as when scaffolding a new project. +- Overwriting large boilerplate files where you want to replace the entire content at once. +- When the complexity or number of changes would make replace_in_file unwieldy or error-prone. +- When you need to completely restructure a file's content or change its fundamental organization. + +## Important Considerations + +- Using write_to_file requires providing the file’s complete final content. +- If you only need to make small changes to an existing file, consider using replace_in_file instead to avoid unnecessarily rewriting the entire file. +- While write_to_file should not be your default choice, don't hesitate to use it when the situation truly calls for it. + +# replace_in_file + +## Purpose + +- Make targeted edits to specific parts of an existing file without overwriting the entire file. + +## When to Use + +- Small, localized changes like updating a few lines, function implementations, changing variable names, modifying a section of text, etc. +- Targeted improvements where only specific portions of the file’s content needs to be altered. +- Especially useful for long files where much of the file will remain unchanged. + +## Advantages + +- More efficient for minor edits, since you don’t need to supply the entire file content. +- Reduces the chance of errors that can occur when overwriting large files. + +# Choosing the Appropriate Tool + +- **Default to replace_in_file** for most changes. It's the safer, more precise option that minimizes potential issues. +- **Use write_to_file** when: + - Creating new files + - The changes are so extensive that using replace_in_file would be more complex or risky + - You need to completely reorganize or restructure a file + - The file is relatively small and the changes affect most of its content + - You're generating boilerplate or template files + +# Workflow Tips + +1. Before editing, assess the scope of your changes and decide which tool to use. +2. For targeted edits, apply replace_in_file with carefully crafted SEARCH/REPLACE blocks. If you need multiple changes, you can stack multiple SEARCH/REPLACE blocks within a single replace_in_file call. +3. For major overhauls or initial file creation, rely on write_to_file. +4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes. + +By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient. + ==== CAPABILITIES - You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${ supportsComputerUse ? ", use the browser" : "" -}, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. +}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. - When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. - You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. - You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task. - - For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed. + - For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed. - You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${ supportsComputerUse ? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser." @@ -778,11 +824,11 @@ RULES - You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path. - Do not use the ~ character or $HOME to refer to the home directory. - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. -- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. -- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using replace_in_file to make informed changes. +- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when creating files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. - When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices. -- When you want to modify a file, use the write_to_file tool directly with the desired content. You do not need to display the content before using the tool. +- When you want to modify a file, use the replace_in_file tool directly with the desired changes. You do not need to display the changes before using the tool. - Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again. - You are only allowed to ask the user questions using the ask_followup_question tool. Use this tool only when you need additional details to complete a task, and be sure to use a clear and concise question that will help you move forward with the task. However if you can use the available tools to avoid having to ask the user questions, you should do so. For example, if the user mentions a file that may be in an outside directory like the Desktop, you should use the list_files tool to list the files in the Desktop and check if the file they are talking about is there, rather than asking the user to provide the file path themselves. - When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you. diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index f5ef662bd4..5eb6a56b8f 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -149,13 +149,16 @@ export class DiffViewProvider { const absolutePath = path.resolve(this.cwd, this.relPath) const updatedDocument = this.activeDiffEditor.document + // get the contents before save operation which may do auto-formatting + const preSaveContent = updatedDocument.getText() + if (updatedDocument.isDirty) { await updatedDocument.save() } // await delay(100) - // Need to get text after save in case there is any auto-formatting done by the editor - const editedContent = updatedDocument.getText() + // get text after save in case there is any auto-formatting done by the editor + const postSaveContent = updatedDocument.getText() await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), { preview: false }) await this.closeAllDiffViews() @@ -190,20 +193,21 @@ export class DiffViewProvider { // If the edited content has different EOL characters, we don't want to show a diff with all the EOL differences. const newContentEOL = this.newContent.includes("\r\n") ? "\r\n" : "\n" - const normalizedEditedContent = editedContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL // trimEnd to fix issue where editor adds in extra new line automatically + const normalizedPreSaveContent = preSaveContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL // trimEnd to fix issue where editor adds in extra new line automatically + const normalizedPostSaveContent = postSaveContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL // this is the final content we return to the model to use as the new baseline for future edits // just in case the new content has a mix of varying EOL characters const normalizedNewContent = this.newContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL - if (normalizedEditedContent !== normalizedNewContent) { - // user made changes before approving edit + if (normalizedPreSaveContent !== normalizedNewContent) { + // user made changes before approving edit. let the model know about user made changes (not including post-save auto-formatting changes) const userEdits = formatResponse.createPrettyPatch( this.relPath.toPosix(), normalizedNewContent, - normalizedEditedContent, + normalizedPreSaveContent, ) - return { newProblemsMessage, userEdits, finalContent: normalizedEditedContent } + return { newProblemsMessage, userEdits, finalContent: normalizedPostSaveContent } } else { // no changes to cline's edits - return { newProblemsMessage, userEdits: undefined, finalContent: normalizedEditedContent } + return { newProblemsMessage, userEdits: undefined, finalContent: normalizedPostSaveContent } } }