Skip to content

Commit d3b61f7

Browse files
committed
Update diff processor to track insertion indices for accurate line placement
1 parent 12e701a commit d3b61f7

5 files changed

Lines changed: 152 additions & 18 deletions

File tree

‎apps/editor/src/commands/apply-chat-response-command/utils/edit-formats/diffs/diff-processor.spec.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ describe('diff-processor', () => {
195195
expect(result).toBe(expected)
196196
})
197197

198+
it('applies diff with generic test case 9 correctly', async () => {
199+
const test_case = 'generic-9'
200+
const original = load_test_case_file('', test_case, 'original.txt')
201+
const diff = load_test_case_file('', test_case, 'diff.txt')
202+
const expected = load_test_case_file('', test_case, 'expected.txt')
203+
204+
const result = apply_diff({
205+
original_code: original,
206+
diff_patch: diff
207+
})
208+
209+
expect(result).toBe(expected)
210+
})
211+
198212
it('applies diff replacing a line with the exact same line', async () => {
199213
const test_case = 'same-line-replace'
200214
const original = load_test_case_file('', test_case, 'original.txt')

‎apps/editor/src/commands/apply-chat-response-command/utils/edit-formats/diffs/diff-processor.ts‎

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
type ReplaceLine = {
22
content: string
33
search_index: number | null
4+
insert_after_search_index: number
45
}
56

67
type 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(/^~nnn/, '') + '\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(/^\+~nnn/, '') + '\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
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--- a/apps/editor/src/views/panel/backend/message-handlers/index.ts
2+
+++ b/apps/editor/src/views/panel/backend/message-handlers/index.ts
3+
@@ -1,5 +1,5 @@
4+
-export * from './handle-create-preset-group-or-separator'
5+
-export * from './handle-delete-preset-group-or-separator'
6+
-export * from './handle-duplicate-preset-group-or-separator'
7+
+export * from './handle-create-preset'
8+
+export * from './handle-delete-preset'
9+
+export * from './handle-duplicate-preset'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export * from './handle-copy-prompt'
2+
export * from './handle-apply-response-from-history'
3+
export * from './handle-create-checkpoint'
4+
export * from './handle-create-preset'
5+
export * from './handle-delete-preset'
6+
export * from './handle-duplicate-preset'
7+
export * from './handle-get-connection-status'
8+
export * from './handle-get-history'
9+
export * from './handle-get-instructions'
10+
export * from './handle-preview-preset'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export * from './handle-copy-prompt'
2+
export * from './handle-apply-response-from-history'
3+
export * from './handle-create-preset-group-or-separator'
4+
export * from './handle-create-checkpoint'
5+
export * from './handle-delete-preset-group-or-separator'
6+
export * from './handle-duplicate-preset-group-or-separator'
7+
export * from './handle-get-connection-status'
8+
export * from './handle-get-history'
9+
export * from './handle-get-instructions'
10+
export * from './handle-preview-preset'

0 commit comments

Comments
 (0)