Skip to content

Commit 5721230

Browse files
committed
Refactor diff processor to use type objects instead of classes and add test case for inline function extraction
1 parent 6a6b9b5 commit 5721230

5 files changed

Lines changed: 829 additions & 49 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,19 @@ describe('diff-processor', () => {
8484

8585
expect(result).toBe(expected)
8686
})
87+
88+
it('applies diff with generic test case 3 correctly', async () => {
89+
const test_case = 'generic-3'
90+
const original = load_test_case_file('', test_case, 'original.txt')
91+
const diff = load_test_case_file('', test_case, 'diff.txt')
92+
const expected = load_test_case_file('', test_case, 'expected.txt')
93+
94+
const result = apply_diff({
95+
original_code: original,
96+
diff_patch: diff
97+
})
98+
99+
expect(result).toBe(expected)
100+
})
87101
})
88102
})

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

Lines changed: 39 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
11
import * as vscode from 'vscode'
22
import * as fs from 'fs'
33

4-
interface ReplaceLine {
4+
type ReplaceLine = {
55
content: string
66
search_index: number | null
77
}
88

9-
class SearchBlock {
10-
public search_lines: string[]
11-
public replace_lines: ReplaceLine[]
12-
public search_block_start_index: number
13-
public actual_original_line_count: number = 0
14-
public search_to_original_map: Map<number, number> = new Map()
15-
16-
public constructor(
17-
search_lines: string[],
18-
replace_lines: ReplaceLine[],
19-
search_block_start_index: number
20-
) {
21-
this.search_lines = search_lines
22-
this.replace_lines = replace_lines
23-
this.search_block_start_index = search_block_start_index
24-
}
25-
26-
get_start_index() {
27-
return this.search_block_start_index
28-
}
29-
get_search_count() {
30-
return this.actual_original_line_count || this.search_lines.length
31-
}
9+
type SearchBlock = {
10+
search_lines: string[]
11+
replace_lines: ReplaceLine[]
12+
search_block_start_index: number
13+
actual_original_line_count: number
14+
search_to_original_map: Map<number, number>
3215
}
3316

3417
export const process_diff = async (params: {
@@ -109,11 +92,11 @@ export const apply_diff = (params: {
10992
continue
11093
}
11194

112-
if (line.trim() === '') {
95+
if (line.trim() == '') {
11396
line = '~nnn'
114-
} else if (line.trim() === '+') {
97+
} else if (line.trim() == '+') {
11598
line = '+~nnn'
116-
} else if (line.trim() === '-') {
99+
} else if (line.trim() == '-') {
117100
line = '-~nnn'
118101
}
119102

@@ -154,9 +137,13 @@ export const apply_diff = (params: {
154137
const push_block = () => {
155138
if (search_chunks.length > 0 || replace_chunks.length > 0) {
156139
if (current_block_has_changes) {
157-
search_replace_blocks.push(
158-
new SearchBlock(search_chunks, replace_chunks, -1)
159-
)
140+
search_replace_blocks.push({
141+
search_lines: search_chunks,
142+
replace_lines: replace_chunks,
143+
search_block_start_index: -1,
144+
actual_original_line_count: 0,
145+
search_to_original_map: new Map()
146+
})
160147
}
161148
}
162149
search_chunks = []
@@ -224,7 +211,7 @@ export const apply_diff = (params: {
224211

225212
if (search_chunks.length > 0 || replace_chunks.length > 0) {
226213
if (
227-
search_chunks[search_chunks.length - 1] === '~nnn' &&
214+
search_chunks[search_chunks.length - 1] == '~nnn' &&
228215
!inside_replace_block
229216
) {
230217
search_chunks.pop()
@@ -237,7 +224,7 @@ export const apply_diff = (params: {
237224
for (let i = 0; i < search_replace_blocks.length; i++) {
238225
const block = search_replace_blocks[i]
239226

240-
if (block.search_lines.length === 0 && previous_found_index === 0) {
227+
if (block.search_lines.length == 0 && previous_found_index == 0) {
241228
block.search_block_start_index = -1
242229
continue
243230
}
@@ -260,23 +247,25 @@ export const apply_diff = (params: {
260247
const s_val = block.search_lines[search_ptr]
261248
const o_val = original_code_lines_normalized[original_ptr].value
262249

263-
if (s_val === o_val) {
250+
if (s_val == o_val) {
264251
current_search_to_original.set(
265252
search_ptr,
266253
original_code_lines_normalized[original_ptr].key
267254
)
268255
matched_indices.push(original_code_lines_normalized[original_ptr].key)
269256
search_ptr++
270257
original_ptr++
271-
} else if (o_val === '~nnn') {
258+
} else if (o_val == '~nnn') {
272259
matched_indices.push(original_code_lines_normalized[original_ptr].key)
273260
original_ptr++
261+
} else if (s_val == '~nnn') {
262+
search_ptr++
274263
} else {
275264
break
276265
}
277266
}
278267

279-
if (search_ptr === block.search_lines.length) {
268+
if (search_ptr == block.search_lines.length) {
280269
block.search_block_start_index = matched_indices[0]
281270
block.actual_original_line_count = matched_indices.length
282271
block.search_to_original_map = current_search_to_original
@@ -292,33 +281,36 @@ export const apply_diff = (params: {
292281
}
293282

294283
const valid_blocks = search_replace_blocks.filter(
295-
(block) => block.get_start_index() !== -2
284+
(block) => block.search_block_start_index != -2
285+
)
286+
valid_blocks.sort(
287+
(a, b) => b.search_block_start_index - a.search_block_start_index
296288
)
297-
valid_blocks.sort((a, b) => b.get_start_index() - a.get_start_index())
298289

299290
const result_lines = [...original_code_lines]
300291

301292
for (const block of valid_blocks) {
302293
const start_index =
303-
block.get_start_index() === -1 ? 0 : block.get_start_index()
294+
block.search_block_start_index == -1 ? 0 : block.search_block_start_index
304295
const replacement_content: string[] = []
305296
let last_original_idx = start_index - 1
306297

307298
for (const line of block.replace_lines) {
308-
if (line.search_index !== null) {
299+
if (line.search_index != null) {
309300
const original_idx = block.search_to_original_map.get(line.search_index)
310-
if (original_idx !== undefined) {
301+
if (original_idx != undefined) {
311302
for (let skip = last_original_idx + 1; skip < original_idx; skip++) {
312-
if (original_code_lines_normalized[skip].value === '~nnn') {
303+
if (original_code_lines_normalized[skip].value == '~nnn') {
313304
replacement_content.push(original_code_lines[skip])
314305
}
315306
}
316307
let content = original_code_lines[original_idx]
317308
if (
318309
!content.endsWith('\n') &&
319310
original_idx < original_code_lines.length - 1
320-
)
311+
) {
321312
content += '\n'
313+
}
322314
replacement_content.push(content)
323315
last_original_idx = original_idx
324316
}
@@ -327,19 +319,17 @@ export const apply_diff = (params: {
327319
}
328320
}
329321

330-
const end_original_idx = start_index + block.get_search_count() - 1
322+
const search_count =
323+
block.actual_original_line_count || block.search_lines.length
324+
const end_original_idx = start_index + search_count - 1
331325
for (let skip = last_original_idx + 1; skip <= end_original_idx; skip++) {
332-
if (original_code_lines_normalized[skip].value === '~nnn') {
326+
if (original_code_lines_normalized[skip].value == '~nnn') {
333327
replacement_content.push(original_code_lines[skip])
334328
}
335329
}
336330

337331
if (start_index >= 0 && start_index <= result_lines.length) {
338-
result_lines.splice(
339-
start_index,
340-
block.get_search_count(),
341-
...replacement_content
342-
)
332+
result_lines.splice(start_index, search_count, ...replacement_content)
343333
}
344334
}
345335

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--- a/apps/editor/src/commands/check-all-with-keywords.ts
2+
+++ b/apps/editor/src/commands/check-all-with-keywords.ts
3+
@@ -1,9 +1,9 @@
4+
import * as vscode from 'vscode'
5+
import * as path from 'path'
6+
+import * as fs from 'fs'
7+
import { WorkspaceProvider } from '../context/providers/workspace/workspace-provider'
8+
import { Logger } from '@shared/utils/logger'
9+
import { LAST_CONTEXT_MERGE_REPLACE_OPTION_STATE_KEY } from '../constants/state-keys'
10+
import { dictionary } from '@shared/constants/dictionary'
11+
-import { search_files_by_keywords } from '../utils/search-files-by-keywords'
12+
13+
14+
export const check_all_with_keywords_command = (
15+
@@ -194,3 +194,62 @@
16+
}
17+
)
18+
}
19+
+
20+
+const search_files_by_keywords = async (
21+
+ files: string[],
22+
+ keyword_groups: string[][],
23+
+ progress?: vscode.Progress<{ message?: string; increment?: number }>,
24+
+ token?: vscode.CancellationToken
25+
+): Promise<string[]> => {
26+
+ const matched_files: string[] = []
27+
+ const total = files.length
28+
+ let processed = 0
29+
+ const increment = total > 0 ? (1 / total) * 100 : 0
30+
+
31+
+ const matchers = keyword_groups.map((group) =>
32+
+ group.map((k) => {
33+
+ if (k.length >= 2 && k.startsWith('"') && k.endsWith('"')) {
34+
+ const term = k.slice(1, -1)
35+
+ if (!term) return () => false
36+
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
37+
+ const regex = new RegExp(`\\b${escaped}\\b`)
38+
+ return (text: string) => regex.test(text)
39+
+ }
40+
+ return (text: string) => text.includes(k)
41+
+ })
42+
+ )
43+
+
44+
+ for (const file_path of files) {
45+
+ if (token?.isCancellationRequested) break
46+
+
47+
+ try {
48+
+ const file_name_lower = path.basename(file_path).toLowerCase()
49+
+ if (
50+
+ matchers.some((group) => group.every((match) => match(file_name_lower)))
51+
+ ) {
52+
+ matched_files.push(file_path)
53+
+ processed++
54+
+ progress?.report({
55+
+ increment,
56+
+ message: `${processed}/${total}`
57+
+ })
58+
+ continue
59+
+ }
60+
+
61+
+ // Skip large files (> 1MB) to avoid performance issues
62+
+ const stats = await fs.promises.stat(file_path)
63+
+ if (stats.size > 1024 * 1024) {
64+
+ processed++
65+
+ progress?.report({
66+
+ increment,
67+
+ message: `${processed}/${total}`
68+
+ })
69+
+ continue
70+
+ }
71+
+
72+
+ const content = await fs.promises.readFile(file_path, 'utf-8')
73+
+ const content_lower = content.toLowerCase()
74+
+
75+
+ if (
76+
+ matchers.some((group) => group.every((match) => match(content_lower)))
77+
+ ) {
78+
+ matched_files.push(file_path)
79+
+ }
80+
+ } catch (error) {
81+
+ // Ignore read errors (binary files, permissions, etc.)
82+
+ }
83+
+
84+
+ processed++
85+
+ progress?.report({
86+
+ increment,
87+
+ message: `${processed}/${total}`
88+
+ })
89+
+ }
90+
+
91+
+ return matched_files
92+
+}

0 commit comments

Comments
 (0)