Skip to content

Commit e5de9ad

Browse files
committed
Implement file rename tracking and handling within the code review process, and introduce a dedicated handler to restore files from review data
1 parent 4787e6a commit e5de9ad

7 files changed

Lines changed: 480 additions & 21 deletions

File tree

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
promote_temporary_checkpoint
3636
} from '../checkpoints-command/actions'
3737
import { FileInReview } from '@shared/types/file-in-review'
38+
import { handle_restore_review } from './handlers/restore-review-handler'
3839

3940
let ongoing_review_cleanup_promise: Promise<void> | null = null
4041

@@ -436,6 +437,22 @@ export const apply_chat_response_command = (
436437
}
437438

438439
const review_data = await (async (): Promise<ReviewData | null> => {
440+
if (args?.files_with_content) {
441+
const result = await handle_restore_review(args.files_with_content)
442+
if (result.success && result.original_states) {
443+
update_undo_button_state(
444+
result.original_states,
445+
chat_response,
446+
args?.original_editor_state
447+
)
448+
return {
449+
original_states: result.original_states,
450+
chat_response
451+
}
452+
}
453+
return null
454+
}
455+
439456
const is_single_root_folder_workspace =
440457
vscode.workspace.workspaceFolders?.length == 1
441458

@@ -444,13 +461,6 @@ export const apply_chat_response_command = (
444461
is_single_root_folder_workspace
445462
})
446463

447-
if (args?.files_with_content) {
448-
clipboard_content = {
449-
type: 'files',
450-
files: args.files_with_content as ClipboardFile[]
451-
}
452-
}
453-
454464
if (
455465
clipboard_content.type == 'code-completion' &&
456466
clipboard_content.code_completion
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import * as vscode from 'vscode'
2+
import * as path from 'path'
3+
import * as fs from 'fs'
4+
import { Logger } from '@shared/utils/logger'
5+
import { dictionary } from '@shared/constants/dictionary'
6+
import { create_safe_path } from '@/utils/path-sanitizer'
7+
import { OriginalFileState } from '../types/original-file-state'
8+
import { FileInReview } from '@shared/types/file-in-review'
9+
import { remove_directory_if_empty } from '../utils/file-operations'
10+
11+
export const handle_restore_review = async (
12+
files: FileInReview[]
13+
): Promise<{ success: boolean; original_states?: OriginalFileState[] }> => {
14+
Logger.info({
15+
function_name: 'handle_restore_review',
16+
message: 'start',
17+
data: { file_count: files.length }
18+
})
19+
20+
if (
21+
!vscode.workspace.workspaceFolders ||
22+
vscode.workspace.workspaceFolders.length == 0
23+
) {
24+
vscode.window.showErrorMessage(
25+
dictionary.error_message.NO_WORKSPACE_FOLDER_OPEN
26+
)
27+
Logger.warn({
28+
function_name: 'handle_restore_review',
29+
message: 'No workspace folder open.'
30+
})
31+
return { success: false }
32+
}
33+
34+
const workspace_map = new Map<string, string>()
35+
vscode.workspace.workspaceFolders.forEach((folder) => {
36+
workspace_map.set(folder.name, folder.uri.fsPath)
37+
})
38+
const default_workspace = vscode.workspace.workspaceFolders[0].uri.fsPath
39+
40+
const original_states: OriginalFileState[] = []
41+
42+
for (const file of files) {
43+
let workspace_root = default_workspace
44+
if (file.workspace_name && workspace_map.has(file.workspace_name)) {
45+
workspace_root = workspace_map.get(file.workspace_name)!
46+
} else if (file.workspace_name) {
47+
Logger.warn({
48+
function_name: 'handle_restore_review',
49+
message: `Workspace '${file.workspace_name}' not found for file '${file.file_path}'. Using default.`
50+
})
51+
}
52+
53+
const safe_path = create_safe_path(workspace_root, file.file_path)
54+
55+
if (!safe_path) {
56+
Logger.warn({
57+
function_name: 'handle_restore_review',
58+
message: 'Unsafe file path detected and skipped',
59+
data: file.file_path
60+
})
61+
continue
62+
}
63+
64+
const file_exists = fs.existsSync(safe_path)
65+
let original_content = ''
66+
if (file_exists) {
67+
try {
68+
original_content = fs.readFileSync(safe_path, 'utf8')
69+
} catch (e) {
70+
Logger.warn({
71+
function_name: 'handle_restore_review',
72+
message: 'Could not read original content of file',
73+
data: { file_path: safe_path, error: e }
74+
})
75+
}
76+
}
77+
78+
original_states.push({
79+
file_path: file.file_path,
80+
content: original_content,
81+
is_new: !file_exists,
82+
workspace_name: file.workspace_name,
83+
is_deleted: file.is_deleted
84+
})
85+
86+
if (file.is_deleted) {
87+
if (file_exists) {
88+
try {
89+
const tabs_to_close: vscode.Tab[] = []
90+
for (const tab_group of vscode.window.tabGroups.all) {
91+
tabs_to_close.push(
92+
...tab_group.tabs.filter((tab) => {
93+
const tab_uri = (tab.input as any)?.uri as
94+
| vscode.Uri
95+
| undefined
96+
return tab_uri && tab_uri.fsPath === safe_path
97+
})
98+
)
99+
}
100+
101+
if (tabs_to_close.length > 0) {
102+
await vscode.window.tabGroups.close(tabs_to_close)
103+
}
104+
105+
fs.unlinkSync(safe_path)
106+
Logger.info({
107+
function_name: 'handle_restore_review',
108+
message: 'File deleted',
109+
data: safe_path
110+
})
111+
await remove_directory_if_empty({
112+
dir_path: path.dirname(safe_path),
113+
workspace_root
114+
})
115+
} catch (error) {
116+
Logger.error({
117+
function_name: 'handle_restore_review',
118+
message: 'Error deleting file',
119+
data: { safe_path, error }
120+
})
121+
vscode.window.showErrorMessage(
122+
`Failed to delete file: ${file.file_path}`
123+
)
124+
}
125+
}
126+
} else if (file.content !== undefined) {
127+
// Create or update
128+
if (file_exists) {
129+
try {
130+
const file_uri = vscode.Uri.file(safe_path)
131+
const document = await vscode.workspace.openTextDocument(file_uri)
132+
const editor = await vscode.window.showTextDocument(document)
133+
let final_content = file.content
134+
if (
135+
original_content.endsWith('\n') &&
136+
!final_content.endsWith('\n')
137+
) {
138+
final_content += '\n'
139+
}
140+
await editor.edit((edit) => {
141+
edit.replace(
142+
new vscode.Range(
143+
document.positionAt(0),
144+
document.positionAt(document.getText().length)
145+
),
146+
final_content
147+
)
148+
})
149+
150+
await document.save()
151+
Logger.info({
152+
function_name: 'handle_restore_review',
153+
message: 'Existing file replaced and saved',
154+
data: safe_path
155+
})
156+
} catch (error) {
157+
Logger.error({
158+
function_name: 'handle_restore_review',
159+
message: 'Error updating file',
160+
data: { safe_path, error }
161+
})
162+
vscode.window.showErrorMessage(
163+
`Failed to update file: ${file.file_path}`
164+
)
165+
}
166+
} else {
167+
// New file
168+
const directory = path.dirname(safe_path)
169+
if (!fs.existsSync(directory)) {
170+
try {
171+
fs.mkdirSync(directory, { recursive: true })
172+
Logger.info({
173+
function_name: 'handle_restore_review',
174+
message: 'Directory created',
175+
data: directory
176+
})
177+
} catch (error) {
178+
Logger.error({
179+
function_name: 'handle_restore_review',
180+
message: 'Failed to create directory',
181+
data: { directory, error, file_path: file.file_path }
182+
})
183+
vscode.window.showErrorMessage(
184+
dictionary.error_message.FAILED_TO_CREATE_DIRECTORY(
185+
file.file_path
186+
)
187+
)
188+
continue
189+
}
190+
}
191+
192+
try {
193+
fs.writeFileSync(safe_path, file.content)
194+
Logger.info({
195+
function_name: 'handle_restore_review',
196+
message: 'New file created',
197+
data: safe_path
198+
})
199+
const document = await vscode.workspace.openTextDocument(safe_path)
200+
await vscode.window.showTextDocument(document, { preview: false })
201+
} catch (error) {
202+
Logger.error({
203+
function_name: 'handle_restore_review',
204+
message: 'Failed to write new file',
205+
data: { safe_path, error, file_path: file.file_path }
206+
})
207+
vscode.window.showErrorMessage(
208+
dictionary.error_message.FAILED_TO_WRITE_FILE(file.file_path)
209+
)
210+
continue
211+
}
212+
}
213+
}
214+
}
215+
216+
return { success: true, original_states }
217+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
export type OriginalFileState = {
22
file_path: string
33
content: string
4-
is_new: boolean
4+
is_new?: boolean
55
workspace_name?: string
66
cursor_offset?: number
77
new_file_path?: string
88
file_path_to_restore?: string
99
is_fallback?: boolean
1010
diff_fallback_method?: 'recount' | 'search_and_replace'
1111
is_replaced?: boolean
12+
is_deleted?: boolean
1213
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export const undo_files = async (params: {
326326
continue // Skip this file
327327
}
328328

329-
if (state.is_new) {
329+
if (state.is_new && !state.file_path_to_restore) {
330330
if (fs.existsSync(safe_path)) {
331331
// Close any open editors for this file before deleting
332332
const uri = vscode.Uri.file(safe_path)

0 commit comments

Comments
 (0)