Skip to content

Commit 0a3f87d

Browse files
committed
Enhance diff extraction to correctly parse XML file blocks that are not wrapped in markdown code blocks
1 parent 1f4e4cf commit 0a3f87d

11 files changed

Lines changed: 140 additions & 36 deletions

File tree

‎packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/clipboard-parser.spec.ts‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,8 +1236,29 @@ describe('clipboard-parser', () => {
12361236
})
12371237
})
12381238

1239-
it('parses a mix of a new file in file-xml format and a diff in a diff block', () => {
1240-
const test_case = 'diff-mix-new-file-xml-and-diff'
1239+
it('parses a mix of a new file in file-xml format and a diff in a diff block variant-1', () => {
1240+
const test_case = 'diff-mix-new-file-xml-and-diff-variant-1'
1241+
const text = load_test_case_file(test_case, `${test_case}.txt`)
1242+
const result = parse_response({
1243+
response: text,
1244+
is_single_root_folder_workspace: true
1245+
})
1246+
1247+
expect(result).toHaveLength(2)
1248+
expect(result[0]).toMatchObject({
1249+
type: 'diff',
1250+
file_path: 'src/lorem.html',
1251+
content: load_test_case_file(test_case, '1-file.txt')
1252+
})
1253+
expect(result[1]).toMatchObject({
1254+
type: 'diff',
1255+
file_path: 'src/ipsum.ts',
1256+
content: load_test_case_file(test_case, '2-file.txt')
1257+
})
1258+
})
1259+
1260+
it('parses a mix of a new file in file-xml format and a diff in a diff block variant-2', () => {
1261+
const test_case = 'diff-mix-new-file-xml-and-diff-variant-2'
12411262
const text = load_test_case_file(test_case, `${test_case}.txt`)
12421263
const result = parse_response({
12431264
response: text,

‎packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/extract-diff-patches/extract-diffs.ts‎

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,44 @@ const extract_all_code_block_patches = (params: {
554554

555555
code_blocks.sort((a, b) => a.start - b.start)
556556

557+
const xml_blocks: typeof code_blocks = []
558+
const xml_file_tag_start_regex =
559+
/^\s*<([\w-]+)\s+path=(?:["']([^"']+)["']|([^>\s]+))/
560+
let in_xml_block = false
561+
let xml_start = -1
562+
let xml_tag_name = ''
563+
for (let i = 0; i < lines.length; i++) {
564+
const trimmed_line = lines[i].trim()
565+
566+
const is_inside_markdown_block = code_blocks.some(
567+
(b) => i >= b.start && i < b.end
568+
)
569+
if (is_inside_markdown_block) continue
570+
571+
if (!in_xml_block) {
572+
const match = trimmed_line.match(xml_file_tag_start_regex)
573+
if (match) {
574+
in_xml_block = true
575+
xml_start = i
576+
xml_tag_name = match[1]
577+
}
578+
} else {
579+
const xml_file_tag_end_regex = new RegExp(`^\\s*<\\/${xml_tag_name}>`)
580+
if (xml_file_tag_end_regex.test(trimmed_line)) {
581+
xml_blocks.push({ start: xml_start, end: i, type: 'xml_file' })
582+
in_xml_block = false
583+
}
584+
}
585+
}
586+
if (in_xml_block) {
587+
xml_blocks.push({
588+
start: xml_start,
589+
end: lines.length - 1,
590+
type: 'xml_file'
591+
})
592+
}
593+
code_blocks.push(...xml_blocks)
594+
code_blocks.sort((a, b) => a.start - b.start)
557595
// First, identify all files that have explicit diff blocks.
558596
const files_with_diffs = new Set<string>()
559597
const diff_block_patches = new Map<number, Diff[]>()
@@ -620,7 +658,10 @@ const extract_all_code_block_patches = (params: {
620658
// Process each found code block in order of appearance.
621659
for (let i = 0; i < code_blocks.length; i++) {
622660
const block = code_blocks[i]
623-
const block_lines = lines.slice(block.start + 1, block.end)
661+
const block_lines =
662+
block.type == 'xml_file'
663+
? lines.slice(block.start, block.end + 1)
664+
: lines.slice(block.start + 1, block.end)
624665

625666
if (block.type != 'diff' && block.type != 'patch') {
626667
const has_truncation_comment = block_lines.some((line) => {
@@ -639,7 +680,13 @@ const extract_all_code_block_patches = (params: {
639680
}
640681
}
641682

642-
const text_before = lines.slice(last_block_end + 1, block.start).join('\n')
683+
const text_before = lines
684+
.slice(last_block_end + 1, block.start)
685+
.filter((line) => {
686+
const trimmed = line.trim()
687+
return trimmed != '<files>' && trimmed != '</files>'
688+
})
689+
.join('\n')
643690

644691
if (block.type == 'diff' || block.type == 'patch') {
645692
if (text_before.trim()) {
@@ -729,7 +776,13 @@ const extract_all_code_block_patches = (params: {
729776
}
730777

731778
if (last_block_end < lines.length - 1) {
732-
const text_after = lines.slice(last_block_end + 1).join('\n')
779+
const text_after = lines
780+
.slice(last_block_end + 1)
781+
.filter((line) => {
782+
const trimmed = line.trim()
783+
return trimmed != '<files>' && trimmed != '</files>'
784+
})
785+
.join('\n')
733786
if (text_after.trim()) {
734787
items.push({ type: 'text', content: text_after.trim() })
735788
}
@@ -810,40 +863,23 @@ export const extract_diffs = (params: {
810863
code_block_regex.test(line.trim())
811864
)
812865

813-
if (uses_code_blocks) {
866+
const xml_file_tag_start_regex =
867+
/^\s*<([\w-]+)\s+path=(?:["']([^"']+)["']|([^>\s]+))/
868+
const uses_xml_blocks = lines.some((line) =>
869+
xml_file_tag_start_regex.test(line.trim())
870+
)
871+
872+
if (uses_code_blocks || uses_xml_blocks) {
814873
return extract_all_code_block_patches({
815874
normalized_text,
816875
is_single_root: params.is_single_root
817876
})
818-
} else {
819-
const xml_file_tag_start_regex =
820-
/^\s*<([\w-]+)\s+path=(?:["']([^"']+)["']|([^>\s]+))/
821-
const start_match =
822-
lines.length > 0 ? lines[0].trim().match(xml_file_tag_start_regex) : null
823-
824-
if (start_match) {
825-
const tag_name = start_match[1]
826-
const xml_file_tag_end_regex = new RegExp(`^\\s*<\\/${tag_name}>`)
827-
if (
828-
lines.length >= 2 &&
829-
xml_file_tag_end_regex.test(lines[lines.length - 1].trim()) &&
830-
lines.some((l) => l.trim().startsWith('@@'))
831-
) {
832-
const result = convert_code_block_to_new_file_diff({
833-
lines: lines,
834-
is_single_root: params.is_single_root
835-
})
836-
if (result) {
837-
return [result]
838-
}
839-
}
840-
}
841-
842-
return parse_multiple_raw_patches({
843-
all_lines: lines,
844-
is_single_root: params.is_single_root
845-
})
846877
}
878+
879+
return parse_multiple_raw_patches({
880+
all_lines: lines,
881+
is_single_root: params.is_single_root
882+
})
847883
}
848884

849885
export const extract_paths_from_lines = (

packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/test-cases/diff-mix-new-file-xml-and-diff/1-file.txt renamed to packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/test-cases/diff-mix-new-file-and-diff copy/1-file.txt

File renamed without changes.

packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/test-cases/diff-mix-new-file-xml-and-diff/2-file.txt renamed to packages/vscode/src/commands/apply-chat-response-command/utils/clipboard-parser/test-cases/diff-mix-new-file-and-diff copy/2-file.txt

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
```html
2+
// src/lorem.html
3+
<div>lorem ipsum</div>
4+
```
5+
6+
```diff
7+
--- src/ipsum.ts
8+
+++ src/ipsum.ts
9+
@@ -1,3 +1,3 @@
10+
console.log("hello")
11+
-console.log("old ipsum")
12+
+console.log("new ipsum")
13+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
--- /dev/null
2+
+++ b/src/lorem.html
3+
@@ -0,0 +1,1 @@
4+
+<div>lorem ipsum</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
--- a/src/ipsum.ts
2+
+++ b/src/ipsum.ts
3+
@@ -1,3 +1,3 @@
4+
console.log("hello")
5+
-console.log("old ipsum")
6+
+console.log("new ipsum")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<files>
2+
<file path="src/lorem.html">
3+
<![CDATA[
4+
<div>lorem ipsum</div>
5+
]]>
6+
</file>
7+
</files>
8+
9+
```diff
10+
--- src/ipsum.ts
11+
+++ src/ipsum.ts
12+
@@ -1,3 +1,3 @@
13+
console.log("hello")
14+
-console.log("old ipsum")
15+
+console.log("new ipsum")
16+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
--- /dev/null
2+
+++ b/src/lorem.html
3+
@@ -0,0 +1,1 @@
4+
+<div>lorem ipsum</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
--- a/src/ipsum.ts
2+
+++ b/src/ipsum.ts
3+
@@ -1,3 +1,3 @@
4+
console.log("hello")
5+
-console.log("old ipsum")
6+
+console.log("new ipsum")

0 commit comments

Comments
 (0)