11type ReplaceLine = {
22 content : string
33 search_index : number | null
4+ insert_after_search_index : number
45}
56
67type SearchBlock = {
@@ -156,6 +157,7 @@ export const apply_diff = (params: {
156157
157158 let inside_replace_block = false
158159 let current_block_has_changes = false
160+ let current_search_index_tracker = - 1
159161
160162 const push_block = ( ) => {
161163 if ( search_chunks . length > 0 || replace_chunks . length > 0 ) {
@@ -175,6 +177,7 @@ export const apply_diff = (params: {
175177 after_search_chunks = [ ]
176178 inside_replace_block = false
177179 current_block_has_changes = false
180+ current_search_index_tracker = - 1
178181 }
179182
180183 for ( let i = 0 ; i < patch_lines_normalized . length ; i ++ ) {
@@ -202,6 +205,8 @@ export const apply_diff = (params: {
202205 } else {
203206 search_chunks . push ( line . replace ( / ^ - / , '' ) . replace ( / ^ ~ / , '' ) )
204207 }
208+
209+ current_search_index_tracker = search_chunks . length - 1
205210
206211 if ( ! line . startsWith ( '-' ) ) {
207212 if ( line . startsWith ( '~nnn' ) ) {
@@ -214,12 +219,14 @@ export const apply_diff = (params: {
214219 if ( line . startsWith ( '~nnn' ) ) {
215220 replace_chunks . push ( {
216221 content : line_original . replace ( / ^ ~ n n n / , '' ) + '\n' ,
217- search_index : search_chunks . length - 1
222+ search_index : search_chunks . length - 1 ,
223+ insert_after_search_index : current_search_index_tracker
218224 } )
219225 } else if ( line . startsWith ( '~' ) ) {
220226 replace_chunks . push ( {
221227 content : line_original + '\n' ,
222- search_index : search_chunks . length - 1
228+ search_index : search_chunks . length - 1 ,
229+ insert_after_search_index : current_search_index_tracker
223230 } )
224231 }
225232
@@ -233,13 +240,15 @@ export const apply_diff = (params: {
233240 if ( line . startsWith ( '+~nnn' ) ) {
234241 replace_chunks . push ( {
235242 content : line_original . replace ( / ^ \+ ~ n n n / , '' ) + '\n' ,
236- search_index : null
243+ search_index : null ,
244+ insert_after_search_index : current_search_index_tracker
237245 } )
238246 after_search_chunks . push ( '~nnn' )
239247 } else {
240248 replace_chunks . push ( {
241249 content : line_original . replace ( / ^ \+ / , '' ) + '\n' ,
242- search_index : null
250+ search_index : null ,
251+ insert_after_search_index : current_search_index_tracker
243252 } )
244253 after_search_chunks . push ( line . replace ( / ^ \+ / , '' ) )
245254 }
@@ -315,6 +324,7 @@ export const apply_diff = (params: {
315324 } | null = null
316325 let used_trim_top = 0
317326
327+ // Pass 1: Strict Match
318328 for ( let trim_cnt = 0 ; trim_cnt <= max_trim_top ; trim_cnt ++ ) {
319329 const current_search_lines = block . search_lines . slice ( trim_cnt )
320330 if ( current_search_lines . length == 0 ) continue
@@ -347,9 +357,6 @@ export const apply_diff = (params: {
347357 search_ptr ++
348358 original_ptr ++
349359 } else if ( o_val == '~nnn' ) {
350- matched_indices . push (
351- original_code_lines_normalized [ original_ptr ] . key
352- )
353360 original_ptr ++
354361 } else if ( s_val == '~nnn' ) {
355362 search_ptr ++
@@ -360,8 +367,8 @@ export const apply_diff = (params: {
360367
361368 if ( search_ptr == current_search_lines . length ) {
362369 matched_info = {
363- start_index : matched_indices [ 0 ] ,
364- actual_count : matched_indices . length ,
370+ start_index : matched_indices . length > 0 ? matched_indices [ 0 ] : original_code_lines_normalized [ j ] . key ,
371+ actual_count : matched_indices . length > 0 ? matched_indices [ matched_indices . length - 1 ] - matched_indices [ 0 ] + 1 : 0 ,
365372 map : current_search_to_original
366373 }
367374 found = true
@@ -375,6 +382,80 @@ export const apply_diff = (params: {
375382 }
376383 }
377384
385+ // Pass 2: Loose / Gap Match (if strict match wasn't found)
386+ if ( ! found ) {
387+ for ( let trim_cnt = 0 ; trim_cnt <= max_trim_top ; trim_cnt ++ ) {
388+ const current_search_lines = block . search_lines . slice ( trim_cnt )
389+ if ( current_search_lines . length == 0 ) continue
390+
391+ for (
392+ let j = previous_found_index ;
393+ j < original_code_lines_normalized . length ;
394+ j ++
395+ ) {
396+ let search_ptr = 0
397+ let original_ptr = j
398+ const matched_indices : number [ ] = [ ]
399+ const current_search_to_original = new Map < number , number > ( )
400+ let match_failed = false
401+
402+ while (
403+ search_ptr < current_search_lines . length &&
404+ original_ptr < original_code_lines_normalized . length
405+ ) {
406+ const s_val = current_search_lines [ search_ptr ]
407+ const o_val = original_code_lines_normalized [ original_ptr ] . value
408+
409+ if ( s_val == o_val ) {
410+ current_search_to_original . set (
411+ search_ptr + trim_cnt ,
412+ original_code_lines_normalized [ original_ptr ] . key
413+ )
414+ matched_indices . push (
415+ original_code_lines_normalized [ original_ptr ] . key
416+ )
417+ search_ptr ++
418+ original_ptr ++
419+ } else if ( o_val == '~nnn' ) {
420+ original_ptr ++
421+ } else if ( s_val == '~nnn' ) {
422+ search_ptr ++
423+ } else {
424+ // Loose gap skipping (look ahead in original code up to 50 lines for the missing match)
425+ let found_ahead = false
426+ const max_lookahead = 50
427+ for ( let k = 1 ; k <= max_lookahead && original_ptr + k < original_code_lines_normalized . length ; k ++ ) {
428+ if ( original_code_lines_normalized [ original_ptr + k ] . value == s_val ) {
429+ found_ahead = true
430+ original_ptr += k
431+ break
432+ }
433+ }
434+ if ( ! found_ahead ) {
435+ match_failed = true
436+ break
437+ }
438+ }
439+ }
440+
441+ if ( ! match_failed && search_ptr == current_search_lines . length ) {
442+ matched_info = {
443+ start_index : matched_indices . length > 0 ? matched_indices [ 0 ] : original_code_lines_normalized [ j ] . key ,
444+ actual_count : matched_indices . length > 0 ? matched_indices [ matched_indices . length - 1 ] - matched_indices [ 0 ] + 1 : 0 ,
445+ map : current_search_to_original
446+ }
447+ found = true
448+ break
449+ }
450+ }
451+
452+ if ( found ) {
453+ used_trim_top = trim_cnt
454+ break
455+ }
456+ }
457+ }
458+
378459 if ( found && matched_info ) {
379460 block . search_block_start_index = matched_info . start_index
380461 block . actual_original_line_count = matched_info . actual_count
@@ -387,7 +468,6 @@ export const apply_diff = (params: {
387468 )
388469 }
389470
390- // Check if the patch is already applied by matching the after-side
391471 const has_additions = block . replace_lines . some (
392472 ( l ) => l . search_index === null
393473 )
@@ -488,10 +568,7 @@ export const apply_diff = (params: {
488568 const original_idx = block . search_to_original_map . get ( line . search_index )
489569 if ( original_idx != undefined ) {
490570 for ( let skip = last_original_idx + 1 ; skip < original_idx ; skip ++ ) {
491- if (
492- original_code_lines_normalized [ skip ] . value == '~nnn' &&
493- ! matched_original_indices . has ( skip )
494- ) {
571+ if ( ! matched_original_indices . has ( skip ) ) {
495572 replacement_content . push ( original_code_lines [ skip ] )
496573 }
497574 }
@@ -507,6 +584,23 @@ export const apply_diff = (params: {
507584 last_original_idx = original_idx
508585 }
509586 } else {
587+ let target_original_idx = last_original_idx
588+ if ( line . insert_after_search_index != null && line . insert_after_search_index >= 0 ) {
589+ const mapped_idx = block . search_to_original_map . get ( line . insert_after_search_index )
590+ if ( mapped_idx != undefined ) {
591+ target_original_idx = mapped_idx
592+ }
593+ }
594+
595+ for ( let skip = last_original_idx + 1 ; skip <= target_original_idx ; skip ++ ) {
596+ if ( ! matched_original_indices . has ( skip ) ) {
597+ replacement_content . push ( original_code_lines [ skip ] )
598+ }
599+ }
600+ if ( target_original_idx > last_original_idx ) {
601+ last_original_idx = target_original_idx
602+ }
603+
510604 let content = line . content
511605 if (
512606 is_last_replace_line &&
@@ -521,10 +615,7 @@ export const apply_diff = (params: {
521615 }
522616
523617 for ( let skip = last_original_idx + 1 ; skip <= end_original_idx ; skip ++ ) {
524- if (
525- original_code_lines_normalized [ skip ] . value == '~nnn' &&
526- ! matched_original_indices . has ( skip )
527- ) {
618+ if ( ! matched_original_indices . has ( skip ) ) {
528619 replacement_content . push ( original_code_lines [ skip ] )
529620 }
530621 }
0 commit comments