@@ -2,14 +2,19 @@ import * as vscode from 'vscode'
22import * as fs from 'fs'
33import { Logger } from '@shared/utils/logger'
44
5+ interface ReplaceLine {
6+ content : string
7+ search_index : number | null
8+ }
9+
510class SearchBlock {
611 public search_lines : string [ ]
7- public replace_lines : string [ ]
12+ public replace_lines : ReplaceLine [ ]
813 public search_block_start_index : number
914
1015 public constructor (
1116 search_lines : string [ ] ,
12- replace_lines : string [ ] ,
17+ replace_lines : ReplaceLine [ ] ,
1318 search_block_start_index : number
1419 ) {
1520 this . search_lines = search_lines
@@ -28,13 +33,15 @@ class SearchBlock {
2833export const process_diff = async ( params : {
2934 file_path : string
3035 diff_patch_path : string
36+ use_strict_whitespace ?: boolean
3137} ) : Promise < string > => {
3238 const file_content = fs . readFileSync ( params . file_path , 'utf8' )
3339 const diff_patch_content = fs . readFileSync ( params . diff_patch_path , 'utf8' )
3440
3541 const result = apply_diff ( {
3642 original_code : file_content ,
37- diff_patch : diff_patch_content
43+ diff_patch : diff_patch_content ,
44+ use_strict_whitespace : params . use_strict_whitespace
3845 } )
3946
4047 try {
@@ -55,6 +62,7 @@ export const process_diff = async (params: {
5562export const apply_diff = ( params : {
5663 original_code : string
5764 diff_patch : string
65+ use_strict_whitespace ?: boolean
5866} ) : string => {
5967 try {
6068 const original_code_normalized = params . original_code . replace ( / \r \n / g, '\n' )
@@ -79,12 +87,15 @@ export const apply_diff = (params: {
7987 . replace ( / \r / g, '' )
8088 . replace ( / \n / g, '' )
8189
82- const line_normalized_2 = line_normalized . replace ( / \s + / g, '' )
83- const line_normalized_3 = line_normalized_2 . toLowerCase ( )
90+ let line_normalized_processed = line_normalized . replace ( / \s + / g, '' )
91+
92+ if ( params . use_strict_whitespace ) {
93+ line_normalized_processed = line_normalized
94+ }
8495
8596 original_code_lines_normalized . push ( {
8697 key : line_count ,
87- value : line_normalized_3
98+ value : line_normalized_processed
8899 } )
89100
90101 line_count ++
@@ -118,96 +129,122 @@ export const apply_diff = (params: {
118129 . replace ( / \r \n / g, '' )
119130 . replace ( / \r / g, '' )
120131 . replace ( / \n / g, '' )
121- if ( line_normalized . startsWith ( ' ' ) ) {
122- line_normalized = line_normalized . replace ( / ^ \s + / , '~' )
132+
133+ let line_normalized_processed = ''
134+
135+ if ( params . use_strict_whitespace ) {
136+ if ( line_normalized . startsWith ( ' ' ) ) {
137+ line_normalized_processed = '~' + line_normalized . substring ( 1 )
138+ } else {
139+ line_normalized_processed = line_normalized
140+ }
141+ } else {
142+ if ( line_normalized . startsWith ( ' ' ) ) {
143+ line_normalized = line_normalized . replace ( / ^ \s + / , '~' )
144+ }
145+ line_normalized_processed = line_normalized . replace ( / \s + / g, '' )
123146 }
124147
125- const line_normalized_2 = line_normalized . replace ( / \s + / g, '' )
126- const line_normalized_3 = line_normalized_2 . toLowerCase ( )
127- patch_lines_normalized . push ( line_normalized_3 )
148+ patch_lines_normalized . push ( line_normalized_processed )
128149 }
129150
130- const search_replace_blocks = [ ]
131- let search_chunks = [ ]
132- let replace_chunks = [ ]
151+ const search_replace_blocks : SearchBlock [ ] = [ ]
152+ let search_chunks : string [ ] = [ ]
153+ let replace_chunks : ReplaceLine [ ] = [ ]
133154
134155 let inside_replace_block = false
156+ let current_block_has_changes = false
135157
136158 for ( let i = 0 ; i < patch_lines_normalized . length ; i ++ ) {
137159 const line = patch_lines_normalized [ i ]
138160 const line_original = patch_lines_original [ i ]
139161
140162 if ( line . startsWith ( '@@' ) ) {
141163 if ( search_chunks . length > 0 || replace_chunks . length > 0 ) {
142- search_replace_blocks . push (
143- new SearchBlock ( search_chunks , replace_chunks , - 1 )
144- )
164+ if ( current_block_has_changes ) {
165+ search_replace_blocks . push (
166+ new SearchBlock ( search_chunks , replace_chunks , - 1 )
167+ )
168+ }
145169 }
146170 search_chunks = [ ]
147171 replace_chunks = [ ]
148172 inside_replace_block = false
173+ current_block_has_changes = false
149174 }
150175
151176 if ( line . startsWith ( '-' ) || line . startsWith ( '~' ) ) {
152177 if ( inside_replace_block ) {
153178 inside_replace_block = false
154179
155180 if ( search_chunks . length > 0 || replace_chunks . length > 0 ) {
156- search_replace_blocks . push (
157- new SearchBlock ( search_chunks , replace_chunks , - 1 )
158- )
181+ if ( current_block_has_changes ) {
182+ search_replace_blocks . push (
183+ new SearchBlock ( search_chunks , replace_chunks , - 1 )
184+ )
185+ }
159186 }
160187
161188 search_chunks = [ ]
162189 replace_chunks = [ ]
190+ current_block_has_changes = false
163191 }
164192
165- if ( line . startsWith ( '-- ' ) ) {
166- // Remove the leading '-' from the search line only if there are two -
167- // Fix for files with lines that start with - eg. markdown
168- search_chunks . push ( line . substring ( 1 , line . length ) )
169- } else if ( line . startsWith ( '~nnn' ) || line . startsWith ( '-~nnn' ) ) {
193+ if ( line . startsWith ( '-' ) ) {
194+ current_block_has_changes = true
195+ }
196+
197+ if ( line . startsWith ( '~nnn' ) || line . startsWith ( '-~nnn' ) ) {
170198 search_chunks . push ( '~nnn' )
171199 } else {
172200 search_chunks . push ( line . replace ( / ^ - / , '' ) . replace ( / ^ ~ / , '' ) )
173201 }
174202
175- // Also replace unchanged lines
176203 if ( line . startsWith ( '~nnn' ) ) {
177- replace_chunks . push ( line_original . replace ( / ^ ~ n n n / , '' ) + '\n' )
204+ replace_chunks . push ( {
205+ content : line_original . replace ( / ^ ~ n n n / , '' ) + '\n' ,
206+ search_index : search_chunks . length - 1
207+ } )
178208 } else if ( line . startsWith ( '~' ) ) {
179- replace_chunks . push ( line_original . replace ( / ^ ~ / , '' ) + '\n' )
209+ replace_chunks . push ( {
210+ content : line_original + '\n' ,
211+ search_index : search_chunks . length - 1
212+ } )
180213 }
181214
182215 continue
183216 }
184217
185218 if ( line . startsWith ( '+' ) ) {
186219 inside_replace_block = true
220+ current_block_has_changes = true
187221
188- if ( line . startsWith ( '++ ' ) ) {
189- // Remove the leading '+' from the search line only if there are two +
190- replace_chunks . push ( line_original . substring ( 1 , line . length ) + '\n' )
191- } else if ( line . startsWith ( '+~nnn' ) ) {
192- replace_chunks . push ( line_original . replace ( / ^ \+ ~ n n n / , '' ) + '\n' )
222+ if ( line . startsWith ( '+~nnn ' ) ) {
223+ replace_chunks . push ( {
224+ content : line_original . replace ( / ^ \+ ~ n n n / , '' ) + '\n' ,
225+ search_index : null
226+ } )
193227 } else {
194- replace_chunks . push ( line_original . replace ( / ^ \+ / , '' ) + '\n' )
228+ replace_chunks . push ( {
229+ content : line_original . replace ( / ^ \+ / , '' ) + '\n' ,
230+ search_index : null
231+ } )
195232 }
196233 }
197234 }
198235
199236 if ( search_chunks . length > 0 ) {
200- // A extra trailing new line is sometimes added by the AI model
201237 if ( search_chunks [ search_chunks . length - 1 ] == '~nnn' ) {
202238 search_chunks . pop ( )
203239 replace_chunks . pop ( )
204240 }
205241
206242 if ( search_chunks . length != 0 || replace_chunks . length != 0 ) {
207- // This is crucial for diffs that only have deletions
208- search_replace_blocks . push (
209- new SearchBlock ( search_chunks , replace_chunks , - 1 )
210- )
243+ if ( current_block_has_changes ) {
244+ search_replace_blocks . push (
245+ new SearchBlock ( search_chunks , replace_chunks , - 1 )
246+ )
247+ }
211248 }
212249 }
213250
@@ -216,20 +253,19 @@ export const apply_diff = (params: {
216253 const search_replace_block = search_replace_blocks [ i ]
217254 const search_string = search_replace_block . search_lines . join ( '' )
218255
219- // We start search from the previous found index to ensure duplicate code is not found at wrong index
220256 let found = false
221257 for (
222258 let j = previous_found_index ;
223259 j < original_code_lines_normalized . length ;
224260 j ++
225261 ) {
226- // This is a special case when a patch hunk starts with one or more + lines and no context lines
227262 if (
228263 search_replace_block . search_lines . length == 0 &&
229264 previous_found_index == 0
230265 ) {
231266 search_replace_block . search_block_start_index = - 1
232267 found = true
268+ break
233269 } else {
234270 const chunk = original_code_lines_normalized . slice (
235271 j ,
@@ -246,7 +282,8 @@ export const apply_diff = (params: {
246282 search_replace_block . search_block_start_index = chunk [ 0 ] . key
247283 found = true
248284
249- previous_found_index = chunk [ 0 ] . key
285+ previous_found_index =
286+ chunk [ 0 ] . key + search_replace_block . search_lines . length
250287 break
251288 }
252289 }
@@ -270,7 +307,20 @@ export const apply_diff = (params: {
270307 const start_index =
271308 block . get_start_index ( ) == - 1 ? 0 : block . get_start_index ( )
272309 const search_count = block . get_search_count ( )
273- const replacement_content = block . replace_lines
310+ const replacement_content = block . replace_lines . map ( ( line ) => {
311+ if (
312+ line . search_index !== null &&
313+ start_index + line . search_index < original_code_lines . length
314+ ) {
315+ let originalContent =
316+ original_code_lines [ start_index + line . search_index ]
317+ if ( ! originalContent . endsWith ( '\n' ) ) {
318+ originalContent += '\n'
319+ }
320+ return originalContent
321+ }
322+ return line . content
323+ } )
274324
275325 if ( start_index < 0 || start_index > result_lines . length ) {
276326 continue
0 commit comments