@@ -3,7 +3,6 @@ import { useSize } from "react-use"
33import { useTranslation , Trans } from "react-i18next"
44import deepEqual from "fast-deep-equal"
55import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
6- import { structuredPatch } from "diff"
76
87import type { ClineMessage , FollowUpData , SuggestionItem } from "@roo-code/types"
98import { Mode } from "@roo/modes"
@@ -25,6 +24,7 @@ import { ReasoningBlock } from "./ReasoningBlock"
2524import Thumbnails from "../common/Thumbnails"
2625import ImageBlock from "../common/ImageBlock"
2726import ErrorRow from "./ErrorRow"
27+ import { extractUnifiedDiff } from "../../utils/diffUtils"
2828
2929import McpResourceRow from "../mcp/McpResourceRow"
3030
@@ -193,38 +193,6 @@ function convertNewFileToUnifiedDiff(content: string, filePath?: string): string
193193 return diff
194194}
195195
196- /**
197- * Converts Roo's SEARCH/REPLACE format to unified diff format for better readability
198- */
199- function convertSearchReplaceToUnifiedDiff ( content : string , filePath ?: string ) : string {
200- const blockRegex =
201- / < < < < < < ? \s * S E A R C H [ \s \S ] * ?(?: ^ : s t a r t _ l i n e : .* \n ) ? (?: ^ : e n d _ l i n e : .* \n ) ? (?: ^ - - - - - - - \s * \n ) ? ( [ \s \S ] * ?) ^ (?: = = = = = = = \s * \n ) ( [ \s \S ] * ?) ^ (?: > > > > > > > R E P L A C E ) / gim
202-
203- let hasBlocks = false
204- let combinedDiff = ""
205- const fileName = filePath || "file"
206-
207- let match : RegExpExecArray | null
208- while ( ( match = blockRegex . exec ( content ) ) !== null ) {
209- hasBlocks = true
210- const searchContent = ( match [ 1 ] ?? "" ) . replace ( / \n $ / , "" ) // Remove trailing newline
211- const replaceContent = ( match [ 2 ] ?? "" ) . replace ( / \n $ / , "" )
212-
213- // Use the diff library to create a proper unified diff
214- const patch = structuredPatch ( fileName , fileName , searchContent , replaceContent , "" , "" , { context : 3 } )
215-
216- // Convert to unified diff format
217- if ( patch . hunks . length > 0 ) {
218- for ( const hunk of patch . hunks ) {
219- combinedDiff += `@@ -${ hunk . oldStart } ,${ hunk . oldLines } +${ hunk . newStart } ,${ hunk . newLines } @@\n`
220- combinedDiff += hunk . lines . join ( "\n" ) + "\n"
221- }
222- }
223- }
224-
225- return hasBlocks ? combinedDiff : content
226- }
227-
228196export const ChatRowContent = ( {
229197 message,
230198 lastModifiedMessage,
@@ -447,47 +415,31 @@ export const ChatRowContent = ({
447415 // Inline diff stats for edit/apply_diff/insert/search-replace/newFile asks
448416 const diffTextForStats = useMemo ( ( ) => {
449417 if ( ! tool ) return ""
450- let content = ""
451- switch ( tool . tool ) {
452- case "editedExistingFile" :
453- case "appliedDiff" :
454- content = ( tool . content ?? tool . diff ) || ""
455- break
456- case "insertContent" :
457- case "searchAndReplace" :
458- content = tool . diff || ""
459- break
460- case "newFileCreated" :
461- // For new files, convert to unified diff format
462- const newFileContent = tool . content || ""
463- content = convertNewFileToUnifiedDiff ( newFileContent , tool . path )
464- break
465- default :
466- return ""
467- }
468- // Strip CDATA markers for proper parsing
469- return content . replace ( / < ! \[ C D A T A \[ / g, "" ) . replace ( / \] \] > / g, "" )
418+ // Normalize to unified diff using frontend-only capture/surmise helper
419+ return (
420+ extractUnifiedDiff ( {
421+ toolName : tool . tool as string ,
422+ path : tool . path ,
423+ diff : ( tool as any ) . diff ,
424+ content : ( tool as any ) . content ,
425+ } ) || ""
426+ )
470427 } , [ tool ] )
471428
472429 const diffStatsForInline = useMemo ( ( ) => {
473430 return computeDiffStats ( diffTextForStats )
474431 } , [ diffTextForStats ] )
475432
476- // Clean diff content for display (remove CDATA markers and convert to unified diff)
433+ // Clean diff content for display (normalize to unified diff)
477434 const cleanDiffContent = useMemo ( ( ) => {
478435 if ( ! tool ) return undefined
479- const raw = ( tool as any ) . content ?? ( tool as any ) . diff
480- if ( ! raw ) return undefined
481-
482- // Remove CDATA markers
483- const withoutCData = raw . replace ( / < ! \[ C D A T A \[ / g, "" ) . replace ( / \] \] > / g, "" )
484-
485- // Check if it's SEARCH/REPLACE format and convert to unified diff
486- if ( / < < < < < < < ? \s * S E A R C H / i. test ( withoutCData ) ) {
487- return convertSearchReplaceToUnifiedDiff ( withoutCData , tool . path )
488- }
489-
490- return withoutCData
436+ const unified = extractUnifiedDiff ( {
437+ toolName : tool . tool as string ,
438+ path : tool . path ,
439+ diff : ( tool as any ) . diff ,
440+ content : ( tool as any ) . content ,
441+ } )
442+ return unified || undefined
491443 } , [ tool ] )
492444
493445 const followUpData = useMemo ( ( ) => {
0 commit comments