Skip to content

Commit a044883

Browse files
committed
feat(search-replace): enhance search/replace tool UI and messaging
Refactor search/replace tool message structure for better consistency Add dedicated UI component for displaying search/replace operations Add i18n support for search/replace operations in all supported languages Improve partial tool handling in searchAndReplaceTool
1 parent 6bbb150 commit a044883

File tree

19 files changed

+126
-37
lines changed

19 files changed

+126
-37
lines changed

src/core/tools/searchAndReplaceTool.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function validateParams(
2525
relPath: string | undefined,
2626
search: string | undefined,
2727
replace: string | undefined,
28-
pushToolResult: PushToolResult
28+
pushToolResult: PushToolResult,
2929
): Promise<boolean> {
3030
if (!relPath) {
3131
cline.consecutiveMistakeCount++
@@ -77,22 +77,20 @@ export async function searchAndReplaceTool(
7777
const startLine: number | undefined = block.params.start_line ? parseInt(block.params.start_line, 10) : undefined
7878
const endLine: number | undefined = block.params.end_line ? parseInt(block.params.end_line, 10) : undefined
7979

80-
const sharedMessageProps: ClineSayTool = {
81-
tool: "appliedDiff",
82-
path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)),
83-
}
84-
8580
try {
8681
// Handle partial tool use
8782
if (block.partial) {
88-
const partialMessage = JSON.stringify({
89-
...sharedMessageProps,
90-
search,
91-
replace,
92-
use_regex: block.params.use_regex ?? "false",
93-
ignore_case: block.params.ignore_case ?? "false",
94-
})
95-
await cline.ask("tool", partialMessage, block.partial).catch(() => {})
83+
const partialMessageProps = {
84+
tool: "searchAndReplace" as const,
85+
path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)),
86+
search: removeClosingTag("search", search),
87+
replace: removeClosingTag("replace", replace),
88+
useRegex: block.params.use_regex === "true",
89+
ignoreCase: block.params.ignore_case === "true",
90+
startLine,
91+
endLine,
92+
}
93+
await cline.ask("tool", JSON.stringify(partialMessageProps), block.partial).catch(() => {})
9694
return
9795
}
9896

@@ -106,14 +104,25 @@ export async function searchAndReplaceTool(
106104
const validSearch = search as string
107105
const validReplace = replace as string
108106

107+
const sharedMessageProps: ClineSayTool = {
108+
tool: "searchAndReplace",
109+
path: getReadablePath(cline.cwd, validRelPath),
110+
search: validSearch,
111+
replace: validReplace,
112+
useRegex: useRegex,
113+
ignoreCase: ignoreCase,
114+
startLine: startLine,
115+
endLine: endLine,
116+
}
117+
109118
const absolutePath = path.resolve(cline.cwd, validRelPath)
110119
const fileExists = await fileExistsAtPath(absolutePath)
111120

112121
if (!fileExists) {
113122
cline.consecutiveMistakeCount++
114123
cline.recordToolError("search_and_replace")
115124
const formattedError = formatResponse.toolError(
116-
`File does not exist at path: ${absolutePath}\nThe specified file could not be found. Please verify the file path and try again.`
125+
`File does not exist at path: ${absolutePath}\nThe specified file could not be found. Please verify the file path and try again.`,
117126
)
118127
await cline.say("error", formattedError)
119128
pushToolResult(formattedError)
@@ -138,15 +147,15 @@ export async function searchAndReplaceTool(
138147
pushToolResult(formattedError)
139148
return
140149
}
141-
150+
142151
// Create search pattern and perform replacement
143152
const flags = ignoreCase ? "gi" : "g"
144153
const searchPattern = useRegex ? new RegExp(validSearch, flags) : new RegExp(escapeRegExp(validSearch), flags)
145-
154+
146155
let newContent: string
147156
if (startLine !== undefined || endLine !== undefined) {
148157
// Handle line-specific replacement
149-
const lines = fileContent.split('\n')
158+
const lines = fileContent.split("\n")
150159
const start = Math.max((startLine ?? 1) - 1, 0)
151160
const end = Math.min((endLine ?? lines.length) - 1, lines.length - 1)
152161

@@ -155,12 +164,12 @@ export async function searchAndReplaceTool(
155164
const afterLines = lines.slice(end + 1)
156165

157166
// Get and modify target section
158-
const targetContent = lines.slice(start, end + 1).join('\n')
167+
const targetContent = lines.slice(start, end + 1).join("\n")
159168
const modifiedContent = targetContent.replace(searchPattern, validReplace)
160-
const modifiedLines = modifiedContent.split('\n')
169+
const modifiedLines = modifiedContent.split("\n")
161170

162171
// Reconstruct full content
163-
newContent = [...beforeLines, ...modifiedLines, ...afterLines].join('\n')
172+
newContent = [...beforeLines, ...modifiedLines, ...afterLines].join("\n")
164173
} else {
165174
// Global replacement
166175
newContent = fileContent.replace(searchPattern, validReplace)

src/shared/ExtensionMessage.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export interface ClineSayTool {
224224
| "switchMode"
225225
| "newTask"
226226
| "finishTask"
227+
| "searchAndReplace"
227228
path?: string
228229
diff?: string
229230
content?: string
@@ -232,6 +233,12 @@ export interface ClineSayTool {
232233
mode?: string
233234
reason?: string
234235
isOutsideWorkspace?: boolean
236+
search?: string
237+
replace?: string
238+
useRegex?: boolean
239+
ignoreCase?: boolean
240+
startLine?: number
241+
endLine?: number
235242
}
236243

237244
// Must keep in sync with system prompt.

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,49 @@ export const ChatRowContent = ({
295295
/>
296296
</>
297297
)
298+
case "searchAndReplace":
299+
return (
300+
<>
301+
<div className="flex items-center gap-2.5 mb-2.5">
302+
{toolIcon("replace")}
303+
<span className="font-bold">
304+
{message.type === "ask"
305+
? t("chat:fileOperations.wantsToSearchReplace")
306+
: t("chat:fileOperations.didSearchReplace")}
307+
</span>
308+
</div>
309+
<div className="mb-2.5">
310+
<div className="flex items-center gap-2.5 mb-1.5">
311+
<span className="text-vscode-descriptionForeground">Search:</span>
312+
<code>{tool.search}</code>
313+
{tool.useRegex && <span className="text-vscode-descriptionForeground">(regex)</span>}
314+
{tool.ignoreCase && (
315+
<span className="text-vscode-descriptionForeground">(case-insensitive)</span>
316+
)}
317+
</div>
318+
<div className="flex items-center gap-2.5 mb-1.5">
319+
<span className="text-vscode-descriptionForeground">Replace:</span>
320+
<code>{tool.replace}</code>
321+
</div>
322+
{(tool.startLine !== undefined || tool.endLine !== undefined) && (
323+
<div className="flex items-center gap-2.5">
324+
<span className="text-vscode-descriptionForeground">Lines:</span>
325+
<code>
326+
{tool.startLine ?? 1} - {tool.endLine ?? "end"}
327+
</code>
328+
</div>
329+
)}
330+
</div>
331+
<CodeAccordian
332+
progressStatus={message.progressStatus}
333+
isLoading={message.partial}
334+
diff={tool.diff!}
335+
path={tool.path!}
336+
isExpanded={isExpanded}
337+
onToggleExpand={onToggleExpand}
338+
/>
339+
</>
340+
)
298341
case "newFileCreated":
299342
return (
300343
<>

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
629629
return true
630630
}
631631
const tool = JSON.parse(message.text)
632-
return ["editedExistingFile", "appliedDiff", "newFileCreated"].includes(tool.tool)
632+
return ["editedExistingFile", "appliedDiff", "newFileCreated", "searchAndReplace"].includes(tool.tool)
633633
}
634634
return false
635635
}, [])

webview-ui/src/i18n/locales/ca/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@
120120
"didRead": "Roo ha llegit aquest fitxer:",
121121
"wantsToEdit": "Roo vol editar aquest fitxer:",
122122
"wantsToEditOutsideWorkspace": "Roo vol editar aquest fitxer fora de l'espai de treball:",
123-
"wantsToCreate": "Roo vol crear un nou fitxer:"
123+
"wantsToCreate": "Roo vol crear un nou fitxer:",
124+
"wantsToSearchReplace": "Roo vol realitzar cerca i substitució en aquest fitxer:",
125+
"didSearchReplace": "Roo ha realitzat cerca i substitució en aquest fitxer:"
124126
},
125127
"directoryOperations": {
126128
"wantsToViewTopLevel": "Roo vol veure els fitxers de nivell superior en aquest directori:",

webview-ui/src/i18n/locales/de/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@
120120
"didRead": "Roo hat diese Datei gelesen:",
121121
"wantsToEdit": "Roo möchte diese Datei bearbeiten:",
122122
"wantsToEditOutsideWorkspace": "Roo möchte diese Datei außerhalb des Arbeitsbereichs bearbeiten:",
123-
"wantsToCreate": "Roo möchte eine neue Datei erstellen:"
123+
"wantsToCreate": "Roo möchte eine neue Datei erstellen:",
124+
"wantsToSearchReplace": "Roo möchte Suchen und Ersetzen in dieser Datei durchführen:",
125+
"didSearchReplace": "Roo hat Suchen und Ersetzen in dieser Datei durchgeführt:"
124126
},
125127
"directoryOperations": {
126128
"wantsToViewTopLevel": "Roo möchte die Dateien auf oberster Ebene in diesem Verzeichnis anzeigen:",

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@
115115
"didRead": "Roo read this file:",
116116
"wantsToEdit": "Roo wants to edit this file:",
117117
"wantsToEditOutsideWorkspace": "Roo wants to edit this file outside of the workspace:",
118-
"wantsToCreate": "Roo wants to create a new file:"
118+
"wantsToCreate": "Roo wants to create a new file:",
119+
"wantsToSearchReplace": "Roo wants to perform search and replace on this file:",
120+
"didSearchReplace": "Roo performed search and replace on this file:"
119121
},
120122
"directoryOperations": {
121123
"wantsToViewTopLevel": "Roo wants to view the top level files in this directory:",

webview-ui/src/i18n/locales/es/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@
120120
"didRead": "Roo leyó este archivo:",
121121
"wantsToEdit": "Roo quiere editar este archivo:",
122122
"wantsToEditOutsideWorkspace": "Roo quiere editar este archivo fuera del espacio de trabajo:",
123-
"wantsToCreate": "Roo quiere crear un nuevo archivo:"
123+
"wantsToCreate": "Roo quiere crear un nuevo archivo:",
124+
"wantsToSearchReplace": "Roo quiere realizar búsqueda y reemplazo en este archivo:",
125+
"didSearchReplace": "Roo realizó búsqueda y reemplazo en este archivo:"
124126
},
125127
"directoryOperations": {
126128
"wantsToViewTopLevel": "Roo quiere ver los archivos de nivel superior en este directorio:",

webview-ui/src/i18n/locales/fr/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@
117117
"didRead": "Roo a lu ce fichier :",
118118
"wantsToEdit": "Roo veut éditer ce fichier :",
119119
"wantsToEditOutsideWorkspace": "Roo veut éditer ce fichier en dehors de l'espace de travail :",
120-
"wantsToCreate": "Roo veut créer un nouveau fichier :"
120+
"wantsToCreate": "Roo veut créer un nouveau fichier :",
121+
"wantsToSearchReplace": "Roo veut effectuer une recherche et remplacement sur ce fichier :",
122+
"didSearchReplace": "Roo a effectué une recherche et remplacement sur ce fichier :"
121123
},
122124
"instructions": {
123125
"wantsToFetch": "Roo veut récupérer des instructions détaillées pour aider à la tâche actuelle"

webview-ui/src/i18n/locales/hi/chat.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@
120120
"didRead": "Roo ने इस फ़ाइल को पढ़ा:",
121121
"wantsToEdit": "Roo इस फ़ाइल को संपादित करना चाहता है:",
122122
"wantsToEditOutsideWorkspace": "Roo कार्यक्षेत्र के बाहर इस फ़ाइल को संपादित करना चाहता है:",
123-
"wantsToCreate": "Roo एक नई फ़ाइल बनाना चाहता है:"
123+
"wantsToCreate": "Roo एक नई फ़ाइल बनाना चाहता है:",
124+
"wantsToSearchReplace": "Roo इस फ़ाइल में खोज और प्रतिस्थापन करना चाहता है:",
125+
"didSearchReplace": "Roo ने इस फ़ाइल में खोज और प्रतिस्थापन किया:"
124126
},
125127
"directoryOperations": {
126128
"wantsToViewTopLevel": "Roo इस निर्देशिका में शीर्ष स्तर की फ़ाइलें देखना चाहता है:",

0 commit comments

Comments
 (0)