Skip to content

Commit 003953a

Browse files
committed
Split file operations utility into modular files
- regarding `apps/editor/src/commands/apply-response-command/utils/file-operations.ts`: place each individual function in a separate file in `apps/editor/src/commands/apply-response-command/utils/file-operations...` └── apps └── editor └── src ├── commands │ └── apply-response-command │ ├── handlers │ │ ├── diff-handler.ts │ │ ├── fast-replace-handler.ts │ │ ├── restore-preview-handler.ts │ │ ├── search-replace-handler.ts │ │ └── truncated-handler.ts │ ├── response-processor.ts │ └── utils │ ├── file-operations │ │ ├── apply-file-relocations.ts │ │ ├── cleanup-rename-source.ts │ │ ├── close-file-tabs.ts │ │ ├── create-file-if-needed.ts │ │ ├── get-rename-source-info.ts │ │ ├── handle-deleted-file-item.ts │ │ ├── index.ts │ │ ├── read-rename-source-file.ts │ │ ├── relocate-file.ts │ │ ├── remove-directory-if-empty.ts │ │ ├── undo-files.ts │ │ └── uri-exists.ts │ ├── preview │ │ └── workspace-listener.ts │ └── preview-handler.ts └── views └── prompt └── backend └── message-handlers └── handle-undo.ts
1 parent 555dec1 commit 003953a

13 files changed

Lines changed: 772 additions & 719 deletions

‎apps/editor/src/commands/apply-response-command/utils/file-operations.ts‎

Lines changed: 0 additions & 719 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import * as vscode from 'vscode'
2+
import { OriginalFileState } from '@/commands/apply-response-command/types/original-file-state'
3+
import { create_safe_path } from '@/utils/path-sanitizer'
4+
import { uri_exists } from './uri-exists'
5+
import { relocate_file } from './relocate-file'
6+
7+
export const apply_file_relocations = async (
8+
original_states: OriginalFileState[]
9+
): Promise<void> => {
10+
if (
11+
!vscode.workspace.workspaceFolders ||
12+
vscode.workspace.workspaceFolders.length == 0
13+
) {
14+
return
15+
}
16+
17+
const workspace_map = new Map<string, string>()
18+
vscode.workspace.workspaceFolders.forEach((folder) => {
19+
workspace_map.set(folder.name, folder.uri.fsPath)
20+
})
21+
22+
const default_workspace = vscode.workspace.workspaceFolders[0].uri.fsPath
23+
24+
for (const state of original_states) {
25+
if (
26+
state.new_file_path &&
27+
(state.new_file_path !== state.file_path ||
28+
state.new_workspace_name !== state.workspace_name)
29+
) {
30+
let workspace_root = default_workspace
31+
if (state.workspace_name && workspace_map.has(state.workspace_name)) {
32+
workspace_root = workspace_map.get(state.workspace_name)!
33+
}
34+
35+
let new_workspace_root = workspace_root
36+
if (
37+
state.new_workspace_name &&
38+
workspace_map.has(state.new_workspace_name)
39+
) {
40+
new_workspace_root = workspace_map.get(state.new_workspace_name)!
41+
}
42+
43+
const old_safe_path = create_safe_path(workspace_root, state.file_path)
44+
const new_safe_path = create_safe_path(
45+
new_workspace_root,
46+
state.new_file_path
47+
)
48+
49+
if (!old_safe_path || !new_safe_path) continue
50+
51+
const old_exists = await uri_exists(vscode.Uri.file(old_safe_path))
52+
const new_exists = await uri_exists(vscode.Uri.file(new_safe_path))
53+
54+
let success = false
55+
if (old_exists) {
56+
success = await relocate_file({
57+
old_path: state.file_path,
58+
new_path: state.new_file_path,
59+
old_workspace_root: workspace_root,
60+
new_workspace_root: new_workspace_root
61+
})
62+
} else if (new_exists) {
63+
success = true
64+
}
65+
66+
if (success) {
67+
state.file_path_to_restore = state.file_path
68+
state.restore_workspace_name = state.workspace_name
69+
state.file_path = state.new_file_path
70+
if (state.new_workspace_name) {
71+
state.workspace_name = state.new_workspace_name
72+
}
73+
state.new_file_path = undefined
74+
state.new_workspace_name = undefined
75+
}
76+
}
77+
}
78+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as vscode from 'vscode'
2+
import * as path from 'path'
3+
import { close_file_tabs } from './close-file-tabs'
4+
import { remove_directory_if_empty } from './remove-directory-if-empty'
5+
6+
export const cleanup_rename_source = async (params: {
7+
source_path: string
8+
workspace_root: string
9+
}) => {
10+
await close_file_tabs(params.source_path)
11+
await vscode.workspace.fs.delete(vscode.Uri.file(params.source_path))
12+
await remove_directory_if_empty({
13+
dir_path: path.dirname(params.source_path),
14+
workspace_root: params.workspace_root
15+
})
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as vscode from 'vscode'
2+
3+
export const close_file_tabs = async (file_path: string) => {
4+
const tabs_to_close: vscode.Tab[] = []
5+
for (const tab_group of vscode.window.tabGroups.all) {
6+
tabs_to_close.push(
7+
...tab_group.tabs.filter((tab) => {
8+
const tab_uri = (tab.input as any)?.uri as vscode.Uri | undefined
9+
return tab_uri && tab_uri.fsPath === file_path
10+
})
11+
)
12+
}
13+
if (tabs_to_close.length > 0) {
14+
await vscode.window.tabGroups.close(tabs_to_close)
15+
}
16+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as vscode from 'vscode'
2+
import * as path from 'path'
3+
import { Logger } from '@shared/utils/logger'
4+
import { t } from '@/i18n'
5+
import { create_safe_path } from '@/utils/path-sanitizer'
6+
import { uri_exists } from './uri-exists'
7+
8+
export const create_file_if_needed = async (params: {
9+
file_path: string
10+
content: string
11+
workspace_name?: string
12+
}): Promise<boolean> => {
13+
Logger.info({
14+
function_name: 'create_file_if_needed',
15+
message: 'start',
16+
data: { filePath: params.file_path, workspace_name: params.workspace_name }
17+
})
18+
if (
19+
!vscode.workspace.workspaceFolders ||
20+
vscode.workspace.workspaceFolders.length == 0
21+
) {
22+
vscode.window.showErrorMessage(
23+
t('command.apply-response.error.no-workspace-folder')
24+
)
25+
Logger.warn({
26+
function_name: 'create_file_if_needed',
27+
message: 'No workspace folder open.'
28+
})
29+
return false
30+
}
31+
32+
let workspace_folder_path: string | undefined
33+
34+
if (params.workspace_name) {
35+
const target_workspace = vscode.workspace.workspaceFolders.find(
36+
(folder) => folder.name == params.workspace_name
37+
)
38+
if (target_workspace) {
39+
workspace_folder_path = target_workspace.uri.fsPath
40+
} else {
41+
Logger.warn({
42+
function_name: 'create_file_if_needed',
43+
message: `Workspace named "${params.workspace_name}" not found. Falling back to the first workspace.`,
44+
data: params.file_path
45+
})
46+
workspace_folder_path = vscode.workspace.workspaceFolders[0].uri.fsPath
47+
}
48+
} else {
49+
workspace_folder_path = vscode.workspace.workspaceFolders[0].uri.fsPath
50+
}
51+
52+
const safe_path = create_safe_path(workspace_folder_path, params.file_path)
53+
54+
if (!safe_path) {
55+
vscode.window.showErrorMessage(
56+
t('command.apply-response.error.invalid-file-path-traversal', {
57+
path: params.file_path
58+
})
59+
)
60+
Logger.error({
61+
function_name: 'create_file_if_needed',
62+
message: 'Invalid file path',
63+
data: params.file_path
64+
})
65+
return false
66+
}
67+
const file_uri = vscode.Uri.file(safe_path)
68+
const directory_uri = vscode.Uri.file(path.dirname(safe_path))
69+
70+
if (!(await uri_exists(directory_uri))) {
71+
try {
72+
await vscode.workspace.fs.createDirectory(directory_uri)
73+
Logger.info({
74+
function_name: 'create_file_if_needed',
75+
message: 'Directory created',
76+
data: directory_uri.fsPath
77+
})
78+
} catch (error) {
79+
Logger.error({
80+
function_name: 'create_file_if_needed',
81+
message: 'Failed to create directory',
82+
data: { directory: directory_uri.fsPath, error }
83+
})
84+
vscode.window.showErrorMessage(
85+
t('command.apply-response.error.failed-to-create-directory', {
86+
path: directory_uri.fsPath
87+
})
88+
)
89+
return false
90+
}
91+
}
92+
93+
try {
94+
await vscode.workspace.fs.writeFile(
95+
file_uri,
96+
Buffer.from(params.content, 'utf8')
97+
)
98+
Logger.info({
99+
function_name: 'create_file_if_needed',
100+
message: 'File created',
101+
data: safe_path
102+
})
103+
return true
104+
} catch (error) {
105+
Logger.error({
106+
function_name: 'create_file_if_needed',
107+
message: 'Failed to write file',
108+
data: { safe_path, error }
109+
})
110+
vscode.window.showErrorMessage(
111+
t('command.apply-response.error.failed-to-write-file', {
112+
path: safe_path
113+
})
114+
)
115+
}
116+
return false
117+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { FileItem } from '../response-parser'
2+
import { read_rename_source_file } from './read-rename-source-file'
3+
4+
export const get_rename_source_info = async (params: {
5+
file: FileItem
6+
workspace_map: Map<string, string>
7+
default_workspace: string
8+
}) => {
9+
let rename_source_path: string | undefined
10+
let rename_source_content: string | undefined
11+
let rename_source_workspace_root: string | undefined
12+
13+
if (params.file.renamed_from) {
14+
let old_workspace_root = params.default_workspace
15+
if (
16+
params.file.renamed_from_workspace &&
17+
params.workspace_map.has(params.file.renamed_from_workspace)
18+
) {
19+
old_workspace_root = params.workspace_map.get(
20+
params.file.renamed_from_workspace
21+
)!
22+
}
23+
24+
const source_info = await read_rename_source_file({
25+
renamed_from: params.file.renamed_from,
26+
workspace_root: old_workspace_root
27+
})
28+
if (source_info) {
29+
rename_source_path = source_info.path
30+
rename_source_content = source_info.content
31+
rename_source_workspace_root = old_workspace_root
32+
}
33+
}
34+
35+
return {
36+
rename_source_path,
37+
rename_source_content,
38+
rename_source_workspace_root
39+
}
40+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 { OriginalFileState } from '@/commands/apply-response-command/types/original-file-state'
6+
import { FileItem } from '../response-parser'
7+
import { get_rename_source_info } from './get-rename-source-info'
8+
import { cleanup_rename_source } from './cleanup-rename-source'
9+
import { close_file_tabs } from './close-file-tabs'
10+
import { remove_directory_if_empty } from './remove-directory-if-empty'
11+
12+
export const handle_deleted_file_item = async (params: {
13+
file: FileItem
14+
safe_path: string
15+
workspace_root: string
16+
workspace_map: Map<string, string>
17+
default_workspace: string
18+
function_name: string
19+
}): Promise<{ success: boolean; original_state?: OriginalFileState }> => {
20+
await get_rename_source_info({
21+
file: params.file,
22+
workspace_map: params.workspace_map,
23+
default_workspace: params.default_workspace
24+
}).then(async (info) => {
25+
if (info.rename_source_path && info.rename_source_workspace_root) {
26+
await cleanup_rename_source({
27+
source_path: info.rename_source_path,
28+
workspace_root: info.rename_source_workspace_root
29+
})
30+
}
31+
})
32+
33+
let original_content = ''
34+
if (fs.existsSync(params.safe_path)) {
35+
try {
36+
const document = await vscode.workspace.openTextDocument(params.safe_path)
37+
original_content = document.getText()
38+
39+
await close_file_tabs(params.safe_path)
40+
41+
await vscode.workspace.fs.delete(vscode.Uri.file(params.safe_path))
42+
await remove_directory_if_empty({
43+
dir_path: path.dirname(params.safe_path),
44+
workspace_root: params.workspace_root
45+
})
46+
47+
Logger.info({
48+
function_name: params.function_name,
49+
message: 'File deleted',
50+
data: params.safe_path
51+
})
52+
} catch (error: any) {
53+
Logger.error({
54+
function_name: params.function_name,
55+
message: 'Failed to delete file',
56+
data: { error: error.message || error, file_path: params.safe_path }
57+
})
58+
return { success: false }
59+
}
60+
}
61+
62+
const original_state: OriginalFileState = {
63+
file_path: params.file.file_path,
64+
content: original_content,
65+
workspace_name: params.file.workspace_name,
66+
file_state: 'deleted',
67+
ai_content: params.file.content,
68+
proposed_content: ''
69+
}
70+
71+
return { success: true, original_state }
72+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export * from './apply-file-relocations'
2+
export * from './cleanup-rename-source'
3+
export * from './close-file-tabs'
4+
export * from './create-file-if-needed'
5+
export * from './get-rename-source-info'
6+
export * from './handle-deleted-file-item'
7+
export * from './read-rename-source-file'
8+
export * from './remove-directory-if-empty'
9+
export * from './undo-files'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as vscode from 'vscode'
2+
import * as fs from 'fs'
3+
import { Logger } from '@shared/utils/logger'
4+
import { create_safe_path, sanitize_file_name } from '@/utils/path-sanitizer'
5+
6+
export const read_rename_source_file = async (params: {
7+
renamed_from: string
8+
workspace_root: string
9+
}): Promise<{ path: string; content: string } | undefined> => {
10+
const sanitized_rename_path = sanitize_file_name(params.renamed_from)
11+
const safe_rename_path = create_safe_path(
12+
params.workspace_root,
13+
sanitized_rename_path
14+
)
15+
16+
if (safe_rename_path && fs.existsSync(safe_rename_path)) {
17+
try {
18+
const document = await vscode.workspace.openTextDocument(safe_rename_path)
19+
return {
20+
path: safe_rename_path,
21+
content: document.getText()
22+
}
23+
} catch (e) {
24+
Logger.warn({
25+
function_name: 'read_rename_source_file',
26+
message: 'Failed to read rename source file',
27+
data: safe_rename_path
28+
})
29+
}
30+
}
31+
return undefined
32+
}

0 commit comments

Comments
 (0)