Skip to content

Commit bc55d43

Browse files
committed
Add support for parsing and handling relevant files list to prune context
1 parent 78ba3cc commit bc55d43

14 files changed

Lines changed: 428 additions & 262 deletions

File tree

‎packages/vscode/src/commands/apply-chat-response-command/apply-chat-response-command.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ export const apply_chat_response_command = (params: {
151151
args,
152152
chat_response,
153153
params.context,
154-
params.panel_provider
154+
params.panel_provider,
155+
params.workspace_provider
155156
)
156157

157158
if (preview_data) {

‎packages/vscode/src/commands/apply-chat-response-command/response-processor.ts‎

Lines changed: 147 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import * as vscode from 'vscode'
22
import * as fs from 'fs'
3+
import * as path from 'path'
34
import { OriginalFileState } from './types/original-file-state'
45
import { handle_restore_preview } from './handlers/restore-preview-handler'
5-
import { parse_response, FileItem, DiffItem } from './utils/clipboard-parser'
6+
import {
7+
parse_response,
8+
FileItem,
9+
DiffItem,
10+
RelevantFilesItem
11+
} from './utils/clipboard-parser'
612
import { create_safe_path } from '@/utils/path-sanitizer'
713
import { dictionary } from '@shared/constants/dictionary'
814
import { Logger } from '@shared/utils/logger'
@@ -22,7 +28,8 @@ import {
2228
handle_truncated_edit,
2329
is_truncation_line
2430
} from './handlers/truncated-handler'
25-
import { Diff } from 'diff'
31+
import { WorkspaceProvider } from '@/context/providers/workspace/workspace-provider'
32+
import { natural_sort } from '@/utils/natural-sort'
2633

2734
export type PreviewData = {
2835
original_states: OriginalFileState[]
@@ -45,7 +52,8 @@ export const process_chat_response = async (
4552
args: CommandArgs | undefined,
4653
chat_response: string,
4754
context: vscode.ExtensionContext,
48-
panel_provider: PanelProvider
55+
panel_provider: PanelProvider,
56+
workspace_provider: WorkspaceProvider
4957
): Promise<PreviewData | null> => {
5058
if (args?.files_with_content) {
5159
const result = await handle_restore_preview(args.files_with_content)
@@ -92,83 +100,72 @@ export const process_chat_response = async (
92100
is_single_root_folder_workspace
93101
})
94102

95-
if (clipboard_items.some((item) => item.type == 'completion')) {
96-
const completion = clipboard_items.find(
97-
(item) => item.type == 'completion'
98-
)!
99-
const workspace_map = new Map<string, string>()
100-
vscode.workspace.workspaceFolders!.forEach((folder) => {
101-
workspace_map.set(folder.name, folder.uri.fsPath)
102-
})
103-
const default_workspace = vscode.workspace.workspaceFolders![0].uri.fsPath
104-
let workspace_root = default_workspace
105-
if (
106-
completion.workspace_name &&
107-
workspace_map.has(completion.workspace_name)
108-
) {
109-
workspace_root = workspace_map.get(completion.workspace_name)!
110-
}
111-
const safe_path = create_safe_path(workspace_root, completion.file_path)
112-
if (!safe_path || !fs.existsSync(safe_path)) {
113-
vscode.window.showErrorMessage(
114-
dictionary.error_message.FILE_NOT_FOUND(completion.file_path)
115-
)
116-
Logger.warn({
117-
function_name: 'process_chat_response',
118-
message: 'File not found for code completion.',
119-
data: { file_path: completion.file_path, safe_path }
120-
})
121-
return null
122-
}
103+
if (clipboard_items.some((item) => item.type == 'relevant-files')) {
104+
const relevant_files_item = clipboard_items.find(
105+
(item) => item.type == 'relevant-files'
106+
) as RelevantFilesItem
123107

124-
const document = await vscode.workspace.openTextDocument(safe_path)
125-
const original_content = document.getText()
126-
const line_index = completion.line - 1
127-
const char_index = completion.character - 1
128-
129-
if (
130-
line_index < 0 ||
131-
char_index < 0 ||
132-
line_index >= document.lineCount ||
133-
char_index > document.lineAt(line_index).text.length
134-
) {
135-
vscode.window.showErrorMessage(
136-
dictionary.error_message.INVALID_POSITION_FOR_CODE_COMPLETION(
137-
completion.file_path
138-
)
139-
)
140-
return null
141-
}
142-
143-
const position_offset = document.offsetAt(
144-
new vscode.Position(line_index, char_index)
108+
const current_checked_files = workspace_provider.get_checked_files()
109+
const relevant_paths_normalized = new Set(
110+
relevant_files_item.file_paths.map((p) => p.replace(/\\/g, '/'))
145111
)
146-
const new_content =
147-
original_content.slice(0, position_offset) +
148-
completion.content +
149-
original_content.slice(position_offset)
150-
151-
if (!args) args = {}
152-
if (!args.original_editor_state) {
153-
args.original_editor_state = {
154-
file_path: safe_path,
155-
position: {
156-
line: line_index,
157-
character: char_index
112+
113+
const quick_pick_items: {
114+
label: string
115+
picked: boolean
116+
description?: string
117+
}[] = relevant_files_item.file_paths.map((path) => ({
118+
label: path,
119+
picked: true
120+
}))
121+
122+
for (const file_path of current_checked_files) {
123+
const workspace_root =
124+
workspace_provider.get_workspace_root_for_file(file_path)
125+
if (workspace_root) {
126+
const relative_path = path
127+
.relative(workspace_root, file_path)
128+
.replace(/\\/g, '/')
129+
if (!relevant_paths_normalized.has(relative_path)) {
130+
if (!quick_pick_items.some((item) => item.label == relative_path)) {
131+
quick_pick_items.push({
132+
label: relative_path,
133+
picked: false
134+
})
135+
}
158136
}
159137
}
160138
}
161139

162-
clipboard_items = [
163-
{
164-
type: 'file',
165-
file_path: completion.file_path,
166-
content: new_content,
167-
workspace_name: completion.workspace_name
140+
quick_pick_items.sort((a, b) => natural_sort(a.label, b.label))
141+
142+
const selected_items = await vscode.window.showQuickPick(quick_pick_items, {
143+
canPickMany: true,
144+
placeHolder: 'Confirm context pruning'
145+
})
146+
147+
if (selected_items && selected_items.length > 0) {
148+
const workspace_roots = workspace_provider.get_workspace_roots()
149+
const files_to_check: string[] = []
150+
151+
for (const selection of selected_items) {
152+
for (const root of workspace_roots) {
153+
const potential_path = path.join(root, selection.label)
154+
if (fs.existsSync(potential_path)) {
155+
files_to_check.push(potential_path)
156+
break
157+
}
158+
}
159+
}
160+
161+
if (files_to_check.length > 0) {
162+
await workspace_provider.set_checked_files(files_to_check)
163+
vscode.window.showInformationMessage(`Context updated successfully.`)
168164
}
169-
]
170-
}
171-
if (clipboard_items.some((item) => item.type == 'diff')) {
165+
}
166+
167+
return null
168+
} else if (clipboard_items.some((item) => item.type == 'diff')) {
172169
const patches = clipboard_items.filter(
173170
(item): item is DiffItem => item.type == 'diff'
174171
)
@@ -291,6 +288,83 @@ export const process_chat_response = async (
291288

292289
return null
293290
} else {
291+
if (clipboard_items.some((item) => item.type == 'completion')) {
292+
const completion = clipboard_items.find(
293+
(item) => item.type == 'completion'
294+
)!
295+
const workspace_map = new Map<string, string>()
296+
vscode.workspace.workspaceFolders!.forEach((folder) => {
297+
workspace_map.set(folder.name, folder.uri.fsPath)
298+
})
299+
const default_workspace = vscode.workspace.workspaceFolders![0].uri.fsPath
300+
let workspace_root = default_workspace
301+
if (
302+
completion.workspace_name &&
303+
workspace_map.has(completion.workspace_name)
304+
) {
305+
workspace_root = workspace_map.get(completion.workspace_name)!
306+
}
307+
const safe_path = create_safe_path(workspace_root, completion.file_path)
308+
if (!safe_path || !fs.existsSync(safe_path)) {
309+
vscode.window.showErrorMessage(
310+
dictionary.error_message.FILE_NOT_FOUND(completion.file_path)
311+
)
312+
Logger.warn({
313+
function_name: 'process_chat_response',
314+
message: 'File not found for code completion.',
315+
data: { file_path: completion.file_path, safe_path }
316+
})
317+
return null
318+
}
319+
320+
const document = await vscode.workspace.openTextDocument(safe_path)
321+
const original_content = document.getText()
322+
const line_index = completion.line - 1
323+
const char_index = completion.character - 1
324+
325+
if (
326+
line_index < 0 ||
327+
char_index < 0 ||
328+
line_index >= document.lineCount ||
329+
char_index > document.lineAt(line_index).text.length
330+
) {
331+
vscode.window.showErrorMessage(
332+
dictionary.error_message.INVALID_POSITION_FOR_CODE_COMPLETION(
333+
completion.file_path
334+
)
335+
)
336+
return null
337+
}
338+
339+
const position_offset = document.offsetAt(
340+
new vscode.Position(line_index, char_index)
341+
)
342+
const new_content =
343+
original_content.slice(0, position_offset) +
344+
completion.content +
345+
original_content.slice(position_offset)
346+
347+
if (!args) args = {}
348+
if (!args.original_editor_state) {
349+
args.original_editor_state = {
350+
file_path: safe_path,
351+
position: {
352+
line: line_index,
353+
character: char_index
354+
}
355+
}
356+
}
357+
358+
clipboard_items = [
359+
{
360+
type: 'file',
361+
file_path: completion.file_path,
362+
content: new_content,
363+
workspace_name: completion.workspace_name
364+
}
365+
]
366+
}
367+
294368
const files = clipboard_items.filter(
295369
(item): item is FileItem => item.type == 'file'
296370
)

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,21 @@ describe('clipboard-parser', () => {
10161016
content: 'Lorem ipsum.'
10171017
})
10181018
})
1019+
1020+
it('parses relevant files list', () => {
1021+
const test_case = 'relevant-files'
1022+
const text = load_test_case_file('relevant-files', `${test_case}.txt`)
1023+
const result = parse_response({
1024+
response: text,
1025+
is_single_root_folder_workspace: true
1026+
})
1027+
1028+
expect(result).toHaveLength(1)
1029+
expect(result[0]).toMatchObject({
1030+
type: 'relevant-files',
1031+
file_paths: ['src/hello.ts', 'src/welcome.ts']
1032+
})
1033+
})
10191034
})
10201035

10211036
describe('parse_response fallback to single file without blocks', () => {

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
import {
22
extract_diffs,
33
parse_code_completion,
4-
parse_multiple_files
4+
parse_multiple_files,
5+
parse_relevant_files
56
} from './parsers'
67

7-
export interface FileItem {
8+
export type FileItem = {
89
type: 'file'
910
file_path: string
1011
content: string
1112
workspace_name?: string
1213
renamed_from?: string
1314
}
1415

15-
export interface DiffItem {
16+
export type DiffItem = {
1617
type: 'diff'
1718
file_path: string
1819
content: string
1920
workspace_name?: string
2021
new_file_path?: string
2122
}
2223

23-
export interface CompletionItem {
24+
export type CompletionItem = {
2425
type: 'completion'
2526
file_path: string
2627
content: string
@@ -29,12 +30,17 @@ export interface CompletionItem {
2930
workspace_name?: string
3031
}
3132

32-
export interface TextItem {
33+
export type RelevantFilesItem = {
34+
type: 'relevant-files'
35+
file_paths: string[]
36+
}
37+
38+
export type TextItem = {
3339
type: 'text'
3440
content: string
3541
}
3642

37-
export interface InlineFileItem {
43+
export type InlineFileItem = {
3844
type: 'inline-file'
3945
content: string
4046
language?: string
@@ -46,6 +52,7 @@ export type ClipboardItem =
4652
| CompletionItem
4753
| TextItem
4854
| InlineFileItem
55+
| RelevantFilesItem
4956

5057
export const extract_workspace_and_path = (params: {
5158
raw_file_path: string
@@ -73,14 +80,21 @@ export const parse_response = (params: {
7380
}): ClipboardItem[] => {
7481
const is_single_root_folder_workspace =
7582
params.is_single_root_folder_workspace ?? true
83+
7684
const code_completion_items = parse_code_completion({
7785
response: params.response,
7886
is_single_root_folder_workspace
7987
})
88+
8089
if (code_completion_items && code_completion_items.length > 0) {
8190
return code_completion_items
8291
}
8392

93+
const relevant_files = parse_relevant_files({ response: params.response })
94+
if (relevant_files) {
95+
return [relevant_files]
96+
}
97+
8498
const processed_response = params.response.replace(/``````/g, '```\n```')
8599

86100
const hunk_header_regex = /^(@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@)/m
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './code-completion-parser'
22
export * from './file-content-only-parser'
3-
export * from './multiple-files-parser'
3+
export * from './multiple-files-parser/multiple-files-parser'
44
export * from './diff-parser/diff-parser'
5+
export * from './relevant-files-parser'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './xml-utils'
2+
export * from './path-utils'
3+
export * from './text-processing'
4+
export * from './item-management'

0 commit comments

Comments
 (0)