@@ -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
@@ -194,38 +194,6 @@ function convertNewFileToUnifiedDiff(content: string, filePath?: string): string
194194 return diff
195195}
196196
197- /**
198- * Converts Roo's SEARCH/REPLACE format to unified diff format for better readability
199- */
200- function convertSearchReplaceToUnifiedDiff ( content : string , filePath ?: string ) : string {
201- const blockRegex =
202- / < < < < < < ? \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
203-
204- let hasBlocks = false
205- let combinedDiff = ""
206- const fileName = filePath || "file"
207-
208- let match : RegExpExecArray | null
209- while ( ( match = blockRegex . exec ( content ) ) !== null ) {
210- hasBlocks = true
211- const searchContent = ( match [ 1 ] ?? "" ) . replace ( / \n $ / , "" ) // Remove trailing newline
212- const replaceContent = ( match [ 2 ] ?? "" ) . replace ( / \n $ / , "" )
213-
214- // Use the diff library to create a proper unified diff
215- const patch = structuredPatch ( fileName , fileName , searchContent , replaceContent , "" , "" , { context : 3 } )
216-
217- // Convert to unified diff format
218- if ( patch . hunks . length > 0 ) {
219- for ( const hunk of patch . hunks ) {
220- combinedDiff += `@@ -${ hunk . oldStart } ,${ hunk . oldLines } +${ hunk . newStart } ,${ hunk . newLines } @@\n`
221- combinedDiff += hunk . lines . join ( "\n" ) + "\n"
222- }
223- }
224- }
225-
226- return hasBlocks ? combinedDiff : content
227- }
228-
229197export const ChatRowContent = ( {
230198 message,
231199 lastModifiedMessage,
@@ -448,47 +416,31 @@ export const ChatRowContent = ({
448416 // Inline diff stats for edit/apply_diff/insert/search-replace/newFile asks
449417 const diffTextForStats = useMemo ( ( ) => {
450418 if ( ! tool ) return ""
451- let content = ""
452- switch ( tool . tool ) {
453- case "editedExistingFile" :
454- case "appliedDiff" :
455- content = ( tool . content ?? tool . diff ) || ""
456- break
457- case "insertContent" :
458- case "searchAndReplace" :
459- content = tool . diff || ""
460- break
461- case "newFileCreated" :
462- // For new files, convert to unified diff format
463- const newFileContent = tool . content || ""
464- content = convertNewFileToUnifiedDiff ( newFileContent , tool . path )
465- break
466- default :
467- return ""
468- }
469- // Strip CDATA markers for proper parsing
470- return content . replace ( / < ! \[ C D A T A \[ / g, "" ) . replace ( / \] \] > / g, "" )
419+ // Normalize to unified diff using frontend-only capture/surmise helper
420+ return (
421+ extractUnifiedDiff ( {
422+ toolName : tool . tool as string ,
423+ path : tool . path ,
424+ diff : ( tool as any ) . diff ,
425+ content : ( tool as any ) . content ,
426+ } ) || ""
427+ )
471428 } , [ tool ] )
472429
473430 const diffStatsForInline = useMemo ( ( ) => {
474431 return computeDiffStats ( diffTextForStats )
475432 } , [ diffTextForStats ] )
476433
477- // Clean diff content for display (remove CDATA markers and convert to unified diff)
434+ // Clean diff content for display (normalize to unified diff)
478435 const cleanDiffContent = useMemo ( ( ) => {
479436 if ( ! tool ) return undefined
480- const raw = ( tool as any ) . content ?? ( tool as any ) . diff
481- if ( ! raw ) return undefined
482-
483- // Remove CDATA markers
484- const withoutCData = raw . replace ( / < ! \[ C D A T A \[ / g, "" ) . replace ( / \] \] > / g, "" )
485-
486- // Check if it's SEARCH/REPLACE format and convert to unified diff
487- if ( / < < < < < < < ? \s * S E A R C H / i. test ( withoutCData ) ) {
488- return convertSearchReplaceToUnifiedDiff ( withoutCData , tool . path )
489- }
490-
491- return withoutCData
437+ const unified = extractUnifiedDiff ( {
438+ toolName : tool . tool as string ,
439+ path : tool . path ,
440+ diff : ( tool as any ) . diff ,
441+ content : ( tool as any ) . content ,
442+ } )
443+ return unified || undefined
492444 } , [ tool ] )
493445
494446 const followUpData = useMemo ( ( ) => {
0 commit comments