Skip to content

Commit 8ac5f2b

Browse files
committed
Enhance clipboard parsing to convert file-like code blocks into new file diffs
1 parent ce769f8 commit 8ac5f2b

9 files changed

Lines changed: 192 additions & 76 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ It uses a simple [content script](https://github.com/robertpiosik/CodeWebChat/bl
7676
- Qwen
7777
- Together
7878
- Yuanbao
79-
- Z.AI
79+
- Z
8080

81-
> <small>**Legal disclaimer:** The injected, yellow [Apply response with CWC] button is not a means of automatic output extraction, it's an alias for the original _copy to clipboard_ button—changes integration process uses clipboard-stored chat response text.</small>
81+
> <small>**Legal disclaimer:** The injected, yellow _Apply response with CWC_ button is not a means of automatic output extraction, it's an alias for the original _copy to clipboard_ button—changes integration process uses clipboard-stored chat response text.</small>
8282
8383
## <span style="background-color: #fbb100; color: black; padding: 0.2em 0.6em; border-radius: 999px">API Tools</span>
8484

packages/browser/src/content-scripts/send-prompt-content-script/chatbots/grok.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const grok: Chatbot = {
9999
get_code_blocks: (t) => t.querySelectorAll('code'),
100100
perform_copy: (f) => {
101101
const copy_button = f.querySelector(
102-
'button:nth-child(2)'
102+
'button:nth-child(4)'
103103
) as HTMLElement
104104
if (!copy_button) {
105105
report_initialization_error({

packages/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "gemini-coder",
33
"displayName": "Code Web Chat",
44
"description": "CWC initializes your favorite chatbot with code and instructions, hands-free!",
5-
"version": "1.408.0",
5+
"version": "1.409.0",
66
"scripts": {
77
"build": "npx vsce package --no-dependencies",
88
"vscode:prepublish": "rimraf out && npm run compile",

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,6 @@ describe('clipboard-parser', () => {
213213
)
214214
})
215215

216-
// it('should handle quoted filenames', () => {
217-
// const text = load_clipboard_text('quoted-filenames.txt')
218-
// const result = parse_multiple_files({
219-
// response: text,
220-
// is_single_root_folder_workspace: true
221-
// })
222-
223-
// expect(result).toHaveLength(1)
224-
// expect(result[0].file_path).toBe('src/utils.py')
225-
// expect(result[0].content).toBe(`def add(a, b):
226-
// return a + b`)
227-
// })
228-
229216
it('should parse uncommented filename format', () => {
230217
const text = load_test_case_file(
231218
'uncommented-filename',
@@ -724,6 +711,27 @@ describe('clipboard-parser', () => {
724711
)
725712
})
726713

714+
it('should parse multiple diff files format in variant k', () => {
715+
const text = load_test_case_file(
716+
'diff-multiple-files-variant-k',
717+
'diff-multiple-files-variant-k.txt'
718+
)
719+
const result = parse_response(text, true)
720+
721+
expect(result.type).toBe('patches')
722+
expect(result.patches).toHaveLength(2)
723+
724+
expect(result.patches![0].file_path).toBe('src/lorem.ts')
725+
expect(result.patches![1].file_path).toBe('src/ipsum.ts')
726+
727+
expect(result.patches![0].content).toBe(
728+
load_test_case_file('diff-multiple-files-variant-k', 'file-1.txt')
729+
)
730+
expect(result.patches![1].content).toBe(
731+
load_test_case_file('diff-multiple-files-variant-k', 'file-2.txt')
732+
)
733+
})
734+
727735
it('should parse multiple diff files format with inner triple backticks', () => {
728736
const text = load_test_case_file(
729737
'diff-inner-triple-backticks',

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

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ export interface ClipboardContent {
2323
code_completion?: ClipboardCodeCompletion
2424
}
2525

26-
const extract_workspace_and_path = (
27-
raw_file_path: string,
28-
is_single_root_folder_workspace = false
29-
): { workspace_name?: string; relative_path: string } => {
30-
const file_path = raw_file_path.replace(/\\/g, '/')
26+
const extract_workspace_and_path = (params: {
27+
raw_file_path: string
28+
is_single_root_folder_workspace?: boolean
29+
}): { workspace_name?: string; relative_path: string } => {
30+
const file_path = params.raw_file_path.replace(/\\/g, '/')
3131
// If workspace has only one root folder, don't try to extract workspace name
32-
if (is_single_root_folder_workspace) {
32+
if (params.is_single_root_folder_workspace) {
3333
return { relative_path: file_path }
3434
}
3535

@@ -113,10 +113,11 @@ export const parse_code_completion = (params: {
113113
const completion_info = extract_path_and_position(first_line_of_block)
114114
if (completion_info) {
115115
const { workspace_name, relative_path } =
116-
extract_workspace_and_path(
117-
completion_info.path,
118-
params.is_single_root_folder_workspace
119-
)
116+
extract_workspace_and_path({
117+
raw_file_path: completion_info.path,
118+
is_single_root_folder_workspace:
119+
params.is_single_root_folder_workspace
120+
})
120121
return {
121122
file_path: relative_path,
122123
content: cleanup_api_response({
@@ -203,10 +204,11 @@ export const parse_multiple_files = (params: {
203204
if (extracted_filename) {
204205
state = 'CONTENT'
205206
top_level_xml_file_mode = true
206-
const { workspace_name, relative_path } = extract_workspace_and_path(
207-
extracted_filename,
208-
params.is_single_root_folder_workspace
209-
)
207+
const { workspace_name, relative_path } = extract_workspace_and_path({
208+
raw_file_path: extracted_filename,
209+
is_single_root_folder_workspace:
210+
params.is_single_root_folder_workspace
211+
})
210212
current_file_name = relative_path
211213
if (workspace_name) {
212214
current_workspace_name = workspace_name
@@ -340,10 +342,11 @@ export const parse_multiple_files = (params: {
340342
const extracted_filename = extract_file_path_from_xml(line)
341343
if (extracted_filename) {
342344
const { workspace_name, relative_path } =
343-
extract_workspace_and_path(
344-
extracted_filename,
345-
params.is_single_root_folder_workspace
346-
)
345+
extract_workspace_and_path({
346+
raw_file_path: extracted_filename,
347+
is_single_root_folder_workspace:
348+
params.is_single_root_folder_workspace
349+
})
347350
current_file_name = relative_path
348351
if (workspace_name) {
349352
current_workspace_name = workspace_name
@@ -394,10 +397,11 @@ export const parse_multiple_files = (params: {
394397

395398
if (extracted_filename) {
396399
const { workspace_name, relative_path } =
397-
extract_workspace_and_path(
398-
extracted_filename,
399-
params.is_single_root_folder_workspace
400-
)
400+
extract_workspace_and_path({
401+
raw_file_path: extracted_filename,
402+
is_single_root_folder_workspace:
403+
params.is_single_root_folder_workspace
404+
})
401405
current_file_name = relative_path
402406
if (workspace_name) {
403407
current_workspace_name = workspace_name
@@ -498,10 +502,10 @@ export const parse_file_content_only = (params: {
498502
const extracted_filename = extract_path_from_line_of_code(first_line)
499503
if (!extracted_filename) return null
500504

501-
const { workspace_name, relative_path } = extract_workspace_and_path(
502-
extracted_filename,
503-
params.is_single_root_folder_workspace
504-
)
505+
const { workspace_name, relative_path } = extract_workspace_and_path({
506+
raw_file_path: extracted_filename,
507+
is_single_root_folder_workspace: params.is_single_root_folder_workspace
508+
})
505509

506510
const content = lines.slice(1).join('\n')
507511
const cleaned_content = content

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

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -105,56 +105,134 @@ const process_collected_patch_lines = (
105105
return patch
106106
}
107107

108-
const extract_code_block_patches = (normalized_text: string): Diff[] => {
109-
const patches: Diff[] = []
110-
const lines = normalized_text.split('\n')
111-
112-
// Parse from end to beginning to find code blocks
113-
const code_blocks: { start: number; end: number }[] = []
114-
115-
// First pass: find all closing ``` and work backwards to find opening ```diff
116-
for (let i = lines.length - 1; i >= 0; i--) {
117-
const trimmed_line = lines[i].trim()
108+
const convert_code_block_to_new_file_diff = (lines: string[]): Diff | null => {
109+
if (lines.length == 0) {
110+
return null
111+
}
118112

119-
if (trimmed_line == '```') {
120-
// Found closing backticks, now look backwards for opening
121-
for (let j = i - 1; j >= 0; j--) {
122-
const opening_line = lines[j].trim()
123-
if (opening_line == '```diff') {
124-
code_blocks.unshift({ start: j, end: i })
125-
i = j // Skip to this position to avoid overlapping blocks
126-
break
113+
// Regex to find file path. It can be commented or not.
114+
// It handles paths with slashes, backslashes, dots, alphanumerics, underscores, and hyphens.
115+
const path_regex = /(?:(?:\/\/|#|--|<!--)\s*)?([\w./\\-]+)/
116+
117+
let file_path: string | undefined
118+
let path_line_index = -1
119+
120+
// Look for a file path in the first few lines of the code block
121+
for (let i = 0; i < Math.min(lines.length, 5); i++) {
122+
const line = lines[i].trim()
123+
const match = line.match(path_regex)
124+
125+
if (match && match[1]) {
126+
const potential_path = match[1]
127+
128+
if (
129+
(potential_path.includes('.') || potential_path.includes('/')) &&
130+
!potential_path.includes(' ')
131+
) {
132+
const rest_of_line = line
133+
.substring(line.indexOf(potential_path) + potential_path.length)
134+
.trim()
135+
// e.g. `25:5` for line and column, or `-->` for html comments
136+
const is_just_path_and_location = /^(?:\d+:\d+)?\s*(-->)?\s*$/.test(
137+
rest_of_line
138+
)
139+
140+
if (is_just_path_and_location) {
141+
file_path = potential_path.replace(/\\/g, '/') // normalize backslashes
142+
path_line_index = i
143+
break // Found it, stop searching.
127144
}
128145
}
129146
}
130147
}
131148

132-
// Handle unclosed block at the end
133-
let last_processed_line = -1
134-
if (code_blocks.length > 0) {
135-
last_processed_line = code_blocks[code_blocks.length - 1].end
149+
if (!file_path) {
150+
return null
136151
}
137152

138-
let last_opener_index = -1
153+
const content_lines = lines.filter((_, index) => index !== path_line_index)
154+
155+
const patch_lines = content_lines.map((line) => `+${line}`)
156+
const patch_content = [
157+
`--- /dev/null`,
158+
`+++ b/${file_path}`,
159+
`@@ -0,0 +1,${content_lines.length} @@`,
160+
...patch_lines
161+
].join('\n')
162+
163+
return {
164+
file_path,
165+
content: patch_content + '\n'
166+
}
167+
}
168+
169+
const extract_all_code_block_patches = (normalized_text: string): Diff[] => {
170+
const patches: Diff[] = []
171+
const lines = normalized_text.split('\n')
172+
173+
const code_block_regex = /^```(\w*)/
174+
175+
const code_blocks: { start: number; end: number; type: string }[] = []
176+
const stack: { start: number; type: string }[] = []
177+
139178
for (let i = 0; i < lines.length; i++) {
140-
const trimmed_line = lines[i].trim()
141-
if (trimmed_line == '```diff') {
142-
last_opener_index = i
179+
const line = lines[i]
180+
const trimmed_line = line.trim()
181+
const match = trimmed_line.match(code_block_regex)
182+
183+
if (!match) continue
184+
185+
const lang = match[1] || ''
186+
187+
if (stack.length > 0 && stack[stack.length - 1].type == 'diff') {
188+
if (line.match(/^[+\- ]/)) {
189+
continue
190+
}
191+
}
192+
193+
if (lang == '' && stack.length > 0 && trimmed_line == '```') {
194+
const open_block = stack.pop()!
195+
code_blocks.push({
196+
start: open_block.start,
197+
end: i,
198+
type: open_block.type
199+
})
200+
} else {
201+
stack.push({ start: i, type: lang })
143202
}
144203
}
145204

146-
if (last_opener_index > last_processed_line) {
205+
const closed_blocks = [...code_blocks].sort((a, b) => a.start - b.start)
206+
207+
for (const open_block of stack) {
208+
let end_line = lines.length
209+
for (const closed_block of closed_blocks) {
210+
if (closed_block.start > open_block.start) {
211+
end_line = closed_block.start
212+
break
213+
}
214+
}
147215
code_blocks.push({
148-
start: last_opener_index,
149-
end: lines.length
216+
start: open_block.start,
217+
end: end_line,
218+
type: open_block.type
150219
})
151220
}
152221

222+
code_blocks.sort((a, b) => a.start - b.start)
223+
153224
// Process each found code block
154225
for (const block of code_blocks) {
155226
const block_lines = lines.slice(block.start + 1, block.end) // Exclude the ``` lines
156-
const processed_patches = parse_multiple_raw_patches(block_lines)
157-
patches.push(...processed_patches)
227+
if (block.type == 'diff') {
228+
const processed_patches = parse_multiple_raw_patches(block_lines)
229+
patches.push(...processed_patches)
230+
} else {
231+
const patch = convert_code_block_to_new_file_diff(block_lines)
232+
if (patch) {
233+
patches.push(patch)
234+
}
235+
}
158236
}
159237

160238
return patches
@@ -202,10 +280,13 @@ export const extract_diffs = (clipboard_text: string): Diff[] => {
202280
const normalized_text = clipboard_text.replace(/\r\n/g, '\n')
203281
const lines = normalized_text.split('\n')
204282

205-
const uses_code_blocks = lines.some((line) => line.trim() == '```diff')
283+
const code_block_regex = /^```(\w*)/
284+
const uses_code_blocks = lines.some((line) =>
285+
code_block_regex.test(line.trim())
286+
)
206287

207288
if (uses_code_blocks) {
208-
return extract_code_block_patches(normalized_text)
289+
return extract_all_code_block_patches(normalized_text)
209290
} else {
210291
return parse_multiple_raw_patches(lines)
211292
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
```html
2+
// src/lorem.ts
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.ts
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)