Skip to content

Commit 30f6f89

Browse files
committed
Refactor file operations to support pure renames and consolidate shared helpers for applying response changes
- Fix binary file corruption during pure file moves by adding an is_pure_rename flag that bypasses UTF-8 string reading/writing and safely transfers the file using raw filesystem copy operations. Also take care about re-applying from response history. [Accuracy boost] - it's been reported that from response history, pure renames don't work. They only work for the very first-time application (the UI reports correctly but no changes on filesystem take place). propose a solution that fully relies on FileInPreview [Accuracy boost] ├── apps │ └── editor │ ├── package.json │ └── src │ ├── commands │ │ └── apply-response-command │ │ ├── apply-response-command.ts │ │ ├── index.ts │ │ ├── response-processor │ │ │ ├── handlers │ │ │ │ ├── diff-handler.ts │ │ │ │ ├── fast-replace-handler.ts │ │ │ │ ├── patch-repair-handler.ts │ │ │ │ ├── restore-preview-handler.ts │ │ │ │ ├── search-replace-handler.ts │ │ │ │ └── truncated-handler.ts │ │ │ ├── index.ts │ │ │ └── response-processor.ts │ │ ├── types │ │ │ └── original-file-state.ts │ │ └── utils │ │ ├── file-checks.ts │ │ ├── file-operations │ │ │ ├── apply-file-content.ts │ │ │ ├── apply-file-relocations.ts │ │ │ ├── cleanup-rename-source.ts │ │ │ ├── close-file-tabs.ts │ │ │ ├── create-file-if-needed.ts │ │ │ ├── ensure-directory.ts │ │ │ ├── get-rename-source-info.ts │ │ │ ├── handle-deleted-file-item.ts │ │ │ ├── index.ts │ │ │ ├── prepare-rename-operation.ts │ │ │ ├── preserve-newline.ts │ │ │ ├── read-rename-source-file.ts │ │ │ ├── relocate-file.ts │ │ │ ├── remove-directory-if-empty.ts │ │ │ ├── undo-files.ts │ │ │ └── uri-exists.ts │ │ ├── preview │ │ │ ├── build-history-files.ts │ │ │ ├── file-preparer.ts │ │ │ ├── preview.ts │ │ │ ├── types.ts │ │ │ └── workspace-listener.ts │ │ ├── preview-handler.ts │ │ ├── response-parser │ │ │ ├── index.ts │ │ │ ├── parsers │ │ │ │ ├── diff-parser │ │ │ │ │ ├── diff-parser.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── standard-parser │ │ │ │ ├── helpers │ │ │ │ │ ├── create-or-update-file-item.ts │ │ │ │ │ └── extract-and-set-workspace-path.ts │ │ │ │ ├── index.ts │ │ │ │ └── standard-parser.ts │ │ │ └── response-parser.ts │ │ ├── state-manager.ts │ │ ├── tab-group-manager.ts │ │ └── workspace.ts │ ├── context │ │ └── context-initialization.ts │ ├── extension.ts │ ├── utils │ │ └── path-sanitizer.ts │ └── views │ └── prompt │ ├── backend │ │ ├── message-handlers │ │ │ ├── handle-apply-response-from-history.ts │ │ │ └── index.ts │ │ └── prompt-view-provider.ts │ ├── frontend │ │ ├── Main │ │ │ ├── Main.tsx │ │ │ └── MainView │ │ │ └── MainView.tsx │ │ ├── Prompt.tsx │ │ └── hooks │ │ └── use-response-history.ts │ └── types │ └── messages.ts └── packages ├── shared │ └── src │ └── types │ ├── file-in-preview.ts │ └── response-history-item.ts └── ui └── src └── components └── editor └── prompt-view ├── ResponsePreview │ ├── ResponsePreview.tsx │ └── components │ └── FileItem │ └── FileItem.tsx └── Responses ├── Responses.module.scss ├── Responses.tsx └── index.ts
1 parent 876c3b9 commit 30f6f89

16 files changed

Lines changed: 358 additions & 256 deletions

File tree

‎apps/editor/src/commands/apply-response-command/response-processor/handlers/diff-handler.ts‎

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Logger } from '@shared/utils/logger'
66
import { OriginalFileState } from '../../types/original-file-state'
77
import { create_safe_path } from '@/utils/path-sanitizer'
88
import { apply_diff } from '../../../../utils/changes-integration/diff-processor'
9-
import { remove_directory_if_empty } from '../../utils/file-operations'
9+
import { remove_directory_if_empty, ensure_directory_exists } from '../../utils/file-operations'
1010
import { DiffItem } from '../../utils/response-parser'
1111
import { get_error_message } from '@/utils/get-error-message'
1212

@@ -153,7 +153,8 @@ export const store_original_file_states = async (
153153
patch_content: string,
154154
workspace_path: string,
155155
workspace_name?: string,
156-
fallback_patch?: DiffItem
156+
fallback_patch?: DiffItem,
157+
is_renaming?: boolean
157158
): Promise<OriginalFileState[]> => {
158159
const file_paths = extract_file_paths_from_patch(
159160
patch_content,
@@ -173,20 +174,29 @@ export const store_original_file_states = async (
173174
}
174175

175176
if (fs.existsSync(safe_path)) {
176-
try {
177-
const document = await vscode.workspace.openTextDocument(safe_path)
178-
const content = document.getText()
177+
if (is_renaming) {
179178
original_states.push({
180179
file_path,
181-
content,
182-
workspace_name
183-
})
184-
} catch (error) {
185-
Logger.error({
186-
function_name: 'store_original_file_states',
187-
message: 'Failed to read file content',
188-
data: { file_path, error }
180+
content: '',
181+
workspace_name,
182+
is_pure_rename: true
189183
})
184+
} else {
185+
try {
186+
const document = await vscode.workspace.openTextDocument(safe_path)
187+
const content = document.getText()
188+
original_states.push({
189+
file_path,
190+
content,
191+
workspace_name
192+
})
193+
} catch (error) {
194+
Logger.error({
195+
function_name: 'store_original_file_states',
196+
message: 'Failed to read file content',
197+
data: { file_path, error }
198+
})
199+
}
190200
}
191201
} else {
192202
original_states.push({
@@ -364,10 +374,7 @@ const handle_new_file_patch = async (
364374
}
365375
const new_content = content_lines.join('\n')
366376

367-
const dir = path.dirname(safe_path)
368-
if (!fs.existsSync(dir)) {
369-
await fs.promises.mkdir(dir, { recursive: true })
370-
}
377+
await ensure_directory_exists(safe_path)
371378

372379
await fs.promises.writeFile(safe_path, new_content, 'utf8')
373380

@@ -552,7 +559,8 @@ export const apply_git_patch = async (
552559
patch_content,
553560
workspace_path,
554561
workspace_name,
555-
patch
562+
patch,
563+
patch_info.is_renaming
556564
)
557565

558566
closed_files = await close_files_in_all_editor_groups(

‎apps/editor/src/commands/apply-response-command/response-processor/handlers/fast-replace-handler.ts‎

Lines changed: 44 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as fs from 'fs'
2-
import * as path from 'path'
32
import * as vscode from 'vscode'
43
import { Logger } from '@shared/utils/logger'
54
import { create_safe_path, sanitize_file_name } from '@/utils/path-sanitizer'
@@ -12,9 +11,12 @@ import {
1211
} from '../../utils/workspace'
1312
import { get_error_message } from '@/utils/get-error-message'
1413
import {
15-
cleanup_rename_source,
1614
handle_deleted_file_item,
17-
get_rename_source_info
15+
relocate_file,
16+
replace_document_content,
17+
preserve_trailing_newline,
18+
ensure_directory_exists,
19+
prepare_rename_operation
1820
} from '../../utils/file-operations'
1921

2022
export const handle_fast_replace = async (params: {
@@ -123,49 +125,41 @@ export const handle_fast_replace = async (params: {
123125

124126
const {
125127
rename_source_path,
126-
rename_source_content,
127-
rename_source_workspace_root
128-
} = await get_rename_source_info({
128+
rename_source_workspace_root,
129+
is_pure_rename,
130+
original_content
131+
} = await prepare_rename_operation({
129132
file,
130133
workspace_map,
131134
default_workspace
132135
})
133136

134-
if (
135-
rename_source_path &&
136-
rename_source_content !== undefined &&
137-
rename_source_workspace_root
138-
) {
137+
if (rename_source_path && rename_source_workspace_root) {
139138
try {
140-
let new_content = file.content
141-
if (new_content === '') {
142-
new_content = rename_source_content
143-
}
144-
145-
await cleanup_rename_source({
146-
source_path: rename_source_path,
147-
workspace_root: rename_source_workspace_root
139+
await relocate_file({
140+
old_path: file.renamed_from!,
141+
new_path: file.file_path,
142+
old_workspace_root: rename_source_workspace_root,
143+
new_workspace_root: workspace_root
148144
})
149145

150-
const directory = path.dirname(safe_path)
151-
if (!fs.existsSync(directory)) {
152-
await fs.promises.mkdir(directory, { recursive: true })
146+
if (!is_pure_rename) {
147+
await vscode.workspace.fs.writeFile(
148+
vscode.Uri.file(safe_path),
149+
Buffer.from(file.content, 'utf8')
150+
)
153151
}
154-
await vscode.workspace.fs.writeFile(
155-
vscode.Uri.file(safe_path),
156-
Buffer.from(new_content, 'utf8')
157-
)
158152

159153
original_states.push({
160154
file_path: file.file_path,
161-
content: rename_source_content,
155+
content: original_content,
162156
workspace_name: file.workspace_name,
163157
file_path_to_restore: file.renamed_from,
164158
restore_workspace_name: file.renamed_from_workspace,
165159
ai_content: file.content,
166-
proposed_content: new_content
160+
proposed_content: is_pure_rename ? '' : file.content,
161+
is_pure_rename
167162
})
168-
169163
continue
170164
} catch (error) {
171165
Logger.error({
@@ -189,35 +183,19 @@ export const handle_fast_replace = async (params: {
189183
if (file_exists) {
190184
const file_uri = vscode.Uri.file(safe_path)
191185
const document = await vscode.workspace.openTextDocument(file_uri)
192-
const original_content = document.getText()
193-
let final_content = file.content
194-
if (
195-
original_content.endsWith('\n') &&
196-
!final_content.endsWith('\n')
197-
) {
198-
final_content += '\n'
199-
}
186+
const original_content_doc = document.getText()
187+
const final_content = preserve_trailing_newline(original_content_doc, file.content)
200188

201189
original_states.push({
202190
file_path: file.file_path,
203-
content: original_content,
191+
content: original_content_doc,
204192
workspace_name: file.workspace_name,
205193
ai_content: file.content,
206194
proposed_content: final_content
207195
})
208196

209-
const edit = new vscode.WorkspaceEdit()
210-
edit.replace(
211-
document.uri,
212-
new vscode.Range(
213-
document.positionAt(0),
214-
document.positionAt(document.getText().length)
215-
),
216-
final_content
217-
)
218-
await vscode.workspace.applyEdit(edit)
197+
await replace_document_content(document, final_content)
219198

220-
await document.save()
221199
Logger.info({
222200
function_name: 'handle_fast_replace',
223201
message: 'Existing file replaced and saved',
@@ -232,31 +210,24 @@ export const handle_fast_replace = async (params: {
232210
ai_content: file.content,
233211
proposed_content: file.content
234212
})
235-
const directory = path.dirname(safe_path)
236-
if (!fs.existsSync(directory)) {
237-
try {
238-
fs.mkdirSync(directory, { recursive: true })
239-
Logger.info({
240-
function_name: 'handle_fast_replace',
241-
message: 'Directory created',
242-
data: directory
243-
})
244-
} catch (error) {
245-
Logger.error({
246-
function_name: 'handle_fast_replace',
247-
message: 'Failed to create directory',
248-
data: { directory, error, file_path: file.file_path }
249-
})
250-
vscode.window.showErrorMessage(
251-
t(
252-
'command.apply-response-command.error.failed-to-create-directory',
253-
{
254-
path: file.file_path
255-
}
256-
)
213+
214+
try {
215+
await ensure_directory_exists(safe_path)
216+
} catch (error) {
217+
Logger.error({
218+
function_name: 'handle_fast_replace',
219+
message: 'Failed to create directory',
220+
data: { error, file_path: file.file_path }
221+
})
222+
vscode.window.showErrorMessage(
223+
t(
224+
'command.apply-response-command.error.failed-to-create-directory',
225+
{
226+
path: file.file_path
227+
}
257228
)
258-
continue
259-
}
229+
)
230+
continue
260231
}
261232

262233
try {

0 commit comments

Comments
 (0)