vscode-delfiles-by-search 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +162 -0
- package/LICENSE +20 -0
- package/PUBLISHING.md +110 -0
- package/README.md +183 -0
- package/USAGE.md +47 -0
- package/bin/delfiles-cli.js +3 -0
- package/package.json +80 -0
- package/src/cli/index.js +258 -0
- package/src/core/file-manager.js +158 -0
- package/src/core/parser.js +95 -0
- package/src/core/searcher.js +172 -0
- package/src/extension/extension.js +268 -0
- package/vscode-delfiles-by-search-0.0.1.vsix +0 -0
- package/vscode-delfiles-by-search-0.0.2.tgz +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
|
|
2
|
+
const vscode = require('vscode');
|
|
3
|
+
const { parseFilePaths } = require('../core/parser');
|
|
4
|
+
const { deleteFiles } = require('../core/file-manager');
|
|
5
|
+
const { searchFiles } = require('../core/searcher');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {vscode.ExtensionContext} context
|
|
10
|
+
*/
|
|
11
|
+
function activate(context) {
|
|
12
|
+
console.log('扩展 "vscode-delfiles-by-search" 已激活');
|
|
13
|
+
|
|
14
|
+
let disposable = vscode.commands.registerCommand('delfiles.parseAndDelete', async () => {
|
|
15
|
+
// 1. 获取输入
|
|
16
|
+
// 逻辑更新:使用 QuickPick 让用户明确选择输入源
|
|
17
|
+
const editor = vscode.window.activeTextEditor;
|
|
18
|
+
let selectedText = '';
|
|
19
|
+
if (editor && !editor.selection.isEmpty) {
|
|
20
|
+
selectedText = editor.document.getText(editor.selection);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let clipboardText = '';
|
|
24
|
+
try {
|
|
25
|
+
const cb = await vscode.env.clipboard.readText();
|
|
26
|
+
if (cb && cb.trim() !== '') {
|
|
27
|
+
clipboardText = cb;
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.warn('无法读取剪贴板内容:', err);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const items = [];
|
|
34
|
+
|
|
35
|
+
// 选项 1: 选中文本
|
|
36
|
+
if (selectedText) {
|
|
37
|
+
items.push({
|
|
38
|
+
label: '$(selection) 使用选中的文本',
|
|
39
|
+
description: selectedText.length > 50 ? selectedText.substring(0, 50) + '...' : selectedText,
|
|
40
|
+
action: 'selection'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 选项 2: 剪贴板内容
|
|
45
|
+
if (clipboardText) {
|
|
46
|
+
items.push({
|
|
47
|
+
label: '$(clippy) 使用剪贴板内容',
|
|
48
|
+
description: clipboardText.length > 50 ? clipboardText.substring(0, 50) + '...' : clipboardText,
|
|
49
|
+
action: 'clipboard'
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 选项 3: 手动输入
|
|
54
|
+
items.push({
|
|
55
|
+
label: '$(keyboard) 手动输入搜索结果',
|
|
56
|
+
description: '在输入框中粘贴内容',
|
|
57
|
+
action: 'manual'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 选项 4: 独立正则搜索 (新增)
|
|
61
|
+
items.push({
|
|
62
|
+
label: '$(regex) 输入关键字(支持正则)',
|
|
63
|
+
description: '独立搜索项目文件内容',
|
|
64
|
+
action: 'regex_search'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 选项 5: 取消
|
|
68
|
+
items.push({
|
|
69
|
+
label: '$(close) 取消',
|
|
70
|
+
description: '退出插件执行',
|
|
71
|
+
action: 'cancel'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const selectedSource = await vscode.window.showQuickPick(items, {
|
|
75
|
+
placeHolder: '请选择搜索结果的来源',
|
|
76
|
+
ignoreFocusOut: true
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!selectedSource || selectedSource.action === 'cancel') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 4. 确认基准路径
|
|
84
|
+
// 默认使用当前工作区根目录
|
|
85
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
86
|
+
let basePath = '';
|
|
87
|
+
|
|
88
|
+
// 尝试从配置中获取基准路径
|
|
89
|
+
const config = vscode.workspace.getConfiguration('delfiles');
|
|
90
|
+
let configBasePath = config.get('basePath');
|
|
91
|
+
const ignorePatterns = config.get('ignorePatterns') || [];
|
|
92
|
+
|
|
93
|
+
if (configBasePath && configBasePath !== '${workspaceFolder}') {
|
|
94
|
+
basePath = configBasePath;
|
|
95
|
+
} else if (workspaceFolders && workspaceFolders.length > 0) {
|
|
96
|
+
basePath = workspaceFolders[0].uri.fsPath;
|
|
97
|
+
} else {
|
|
98
|
+
// 如果没有打开文件夹,询问用户
|
|
99
|
+
const uris = await vscode.window.showOpenDialog({
|
|
100
|
+
canSelectFiles: false,
|
|
101
|
+
canSelectFolders: true,
|
|
102
|
+
canSelectMany: false,
|
|
103
|
+
title: '选择基准目录'
|
|
104
|
+
});
|
|
105
|
+
if (uris && uris.length > 0) {
|
|
106
|
+
basePath = uris[0].fsPath;
|
|
107
|
+
} else {
|
|
108
|
+
return; // 用户取消
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let filePaths = [];
|
|
113
|
+
|
|
114
|
+
if (selectedSource.action === 'regex_search') {
|
|
115
|
+
// 独立正则搜索逻辑
|
|
116
|
+
const regexStr = await vscode.window.showInputBox({
|
|
117
|
+
prompt: '请输入搜索关键字(支持正则表达式)',
|
|
118
|
+
placeHolder: '例如:TODO|FIXME',
|
|
119
|
+
ignoreFocusOut: true,
|
|
120
|
+
validateInput: (value) => {
|
|
121
|
+
if (!value) return '关键字不能为空';
|
|
122
|
+
try {
|
|
123
|
+
new RegExp(value);
|
|
124
|
+
return null;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return '正则表达式语法错误: ' + e.message;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!regexStr) return;
|
|
132
|
+
|
|
133
|
+
source = `正则搜索 "${regexStr}"`;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
await vscode.window.withProgress({
|
|
137
|
+
location: vscode.ProgressLocation.Notification,
|
|
138
|
+
title: "正在搜索文件...",
|
|
139
|
+
cancellable: false
|
|
140
|
+
}, async () => {
|
|
141
|
+
filePaths = await searchFiles(basePath, new RegExp(regexStr), ignorePatterns);
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
vscode.window.showErrorMessage(`搜索失败: ${err.message}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (filePaths.length === 0) {
|
|
149
|
+
vscode.window.showInformationMessage(`未找到匹配 "${regexStr}" 的文件。`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} else {
|
|
154
|
+
// 原有的解析逻辑
|
|
155
|
+
let text = '';
|
|
156
|
+
let sourceLabel = '';
|
|
157
|
+
|
|
158
|
+
if (selectedSource.action === 'selection') {
|
|
159
|
+
text = selectedText;
|
|
160
|
+
sourceLabel = '选中文本';
|
|
161
|
+
} else if (selectedSource.action === 'clipboard') {
|
|
162
|
+
text = clipboardText;
|
|
163
|
+
sourceLabel = '剪贴板内容';
|
|
164
|
+
} else if (selectedSource.action === 'manual') {
|
|
165
|
+
text = await vscode.window.showInputBox({
|
|
166
|
+
prompt: '请在此粘贴搜索结果:',
|
|
167
|
+
placeHolder: '例如:src/utils/helper.ts: ...',
|
|
168
|
+
ignoreFocusOut: true
|
|
169
|
+
});
|
|
170
|
+
sourceLabel = '手动输入';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!text || text.trim() === '') {
|
|
174
|
+
if (selectedSource.action === 'manual') {
|
|
175
|
+
vscode.window.showErrorMessage('未提供有效的文本内容。');
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
filePaths = parseFilePaths(text);
|
|
181
|
+
source = sourceLabel; // 更新 source 用于后续提示
|
|
182
|
+
|
|
183
|
+
if (filePaths.length === 0) {
|
|
184
|
+
vscode.window.showInformationMessage(`未在${source}中解析到任何文件路径。请确认格式是否正确(例如包含 "**/*.js:" )。`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 3. 交互式确认文件列表 (新增)
|
|
190
|
+
// 注意:如果是正则搜索,source 变量已经设置好了
|
|
191
|
+
const fileItems = filePaths.map(filePath => ({
|
|
192
|
+
label: filePath,
|
|
193
|
+
picked: true,
|
|
194
|
+
description: '' // 可选:显示绝对路径或其他信息
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
const selectedItems = await vscode.window.showQuickPick(fileItems, {
|
|
198
|
+
canPickMany: true,
|
|
199
|
+
placeHolder: `已从${source}解析到 ${filePaths.length} 个文件。请选择要删除的文件 (Esc 取消):`,
|
|
200
|
+
ignoreFocusOut: true
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!selectedItems || selectedItems.length === 0) {
|
|
204
|
+
// 用户取消或未选择任何文件
|
|
205
|
+
if (selectedItems && selectedItems.length === 0) {
|
|
206
|
+
vscode.window.showInformationMessage('未选择任何文件,操作已取消。');
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 更新待删除文件列表
|
|
212
|
+
filePaths = selectedItems.map(item => item.label);
|
|
213
|
+
|
|
214
|
+
// 4. 确认删除
|
|
215
|
+
const confirm = await vscode.window.showWarningMessage(
|
|
216
|
+
`解析到 ${filePaths.length} 个文件。即将从 ${basePath} 删除这些文件。确定吗?`,
|
|
217
|
+
{ modal: true },
|
|
218
|
+
'确定删除',
|
|
219
|
+
'仅模拟运行 (Dry Run)'
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!confirm) return;
|
|
223
|
+
|
|
224
|
+
const isDryRun = confirm === '仅模拟运行 (Dry Run)';
|
|
225
|
+
const moveToTrash = config.get('moveToTrash', true);
|
|
226
|
+
|
|
227
|
+
// 5. 执行
|
|
228
|
+
const outputChannel = vscode.window.createOutputChannel('Delete Files');
|
|
229
|
+
outputChannel.show();
|
|
230
|
+
|
|
231
|
+
const logger = (msg) => outputChannel.appendLine(msg);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await vscode.window.withProgress({
|
|
235
|
+
location: vscode.ProgressLocation.Notification,
|
|
236
|
+
title: "正在处理文件...",
|
|
237
|
+
cancellable: false
|
|
238
|
+
}, async (progress) => {
|
|
239
|
+
const result = await deleteFiles(filePaths, {
|
|
240
|
+
basePath,
|
|
241
|
+
dryRun: isDryRun,
|
|
242
|
+
moveToTrash,
|
|
243
|
+
logger,
|
|
244
|
+
ignorePatterns
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (result.failed.length > 0) {
|
|
248
|
+
vscode.window.showErrorMessage(`操作完成,但有 ${result.failed.length} 个文件处理失败,请查看输出面板。`);
|
|
249
|
+
} else {
|
|
250
|
+
const action = isDryRun ? '模拟运行' : '删除操作';
|
|
251
|
+
vscode.window.showInformationMessage(`${action}完成。成功: ${result.success.length}, 跳过: ${result.skipped.length}`);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
vscode.window.showErrorMessage(`发生错误: ${error.message}`);
|
|
256
|
+
logger(error.stack);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
context.subscriptions.push(disposable);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function deactivate() {}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
activate,
|
|
267
|
+
deactivate
|
|
268
|
+
};
|
|
Binary file
|
|
Binary file
|