@involvex/fresh-editor 0.1.76 → 0.1.78
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/bin/CHANGELOG.md +1017 -0
- package/bin/LICENSE +117 -0
- package/bin/README.md +248 -0
- package/bin/fresh.exe +0 -0
- package/bin/plugins/README.md +71 -0
- package/bin/plugins/audit_mode.i18n.json +821 -0
- package/bin/plugins/audit_mode.ts +1810 -0
- package/bin/plugins/buffer_modified.i18n.json +67 -0
- package/bin/plugins/buffer_modified.ts +281 -0
- package/bin/plugins/calculator.i18n.json +93 -0
- package/bin/plugins/calculator.ts +770 -0
- package/bin/plugins/clangd-lsp.ts +168 -0
- package/bin/plugins/clangd_support.i18n.json +223 -0
- package/bin/plugins/clangd_support.md +20 -0
- package/bin/plugins/clangd_support.ts +325 -0
- package/bin/plugins/color_highlighter.i18n.json +145 -0
- package/bin/plugins/color_highlighter.ts +304 -0
- package/bin/plugins/config-schema.json +768 -0
- package/bin/plugins/csharp-lsp.ts +147 -0
- package/bin/plugins/csharp_support.i18n.json +80 -0
- package/bin/plugins/csharp_support.ts +170 -0
- package/bin/plugins/css-lsp.ts +143 -0
- package/bin/plugins/diagnostics_panel.i18n.json +236 -0
- package/bin/plugins/diagnostics_panel.ts +642 -0
- package/bin/plugins/examples/README.md +85 -0
- package/bin/plugins/examples/async_demo.ts +165 -0
- package/bin/plugins/examples/bookmarks.ts +329 -0
- package/bin/plugins/examples/buffer_query_demo.ts +110 -0
- package/bin/plugins/examples/git_grep.ts +262 -0
- package/bin/plugins/examples/hello_world.ts +93 -0
- package/bin/plugins/examples/virtual_buffer_demo.ts +116 -0
- package/bin/plugins/find_references.i18n.json +275 -0
- package/bin/plugins/find_references.ts +359 -0
- package/bin/plugins/git_blame.i18n.json +496 -0
- package/bin/plugins/git_blame.ts +707 -0
- package/bin/plugins/git_find_file.i18n.json +314 -0
- package/bin/plugins/git_find_file.ts +300 -0
- package/bin/plugins/git_grep.i18n.json +171 -0
- package/bin/plugins/git_grep.ts +191 -0
- package/bin/plugins/git_gutter.i18n.json +93 -0
- package/bin/plugins/git_gutter.ts +477 -0
- package/bin/plugins/git_log.i18n.json +481 -0
- package/bin/plugins/git_log.ts +1285 -0
- package/bin/plugins/go-lsp.ts +143 -0
- package/bin/plugins/html-lsp.ts +145 -0
- package/bin/plugins/json-lsp.ts +145 -0
- package/bin/plugins/lib/fresh.d.ts +1321 -0
- package/bin/plugins/lib/index.ts +24 -0
- package/bin/plugins/lib/navigation-controller.ts +214 -0
- package/bin/plugins/lib/panel-manager.ts +220 -0
- package/bin/plugins/lib/types.ts +72 -0
- package/bin/plugins/lib/virtual-buffer-factory.ts +130 -0
- package/bin/plugins/live_grep.i18n.json +171 -0
- package/bin/plugins/live_grep.ts +422 -0
- package/bin/plugins/markdown_compose.i18n.json +223 -0
- package/bin/plugins/markdown_compose.ts +630 -0
- package/bin/plugins/merge_conflict.i18n.json +821 -0
- package/bin/plugins/merge_conflict.ts +1810 -0
- package/bin/plugins/path_complete.i18n.json +80 -0
- package/bin/plugins/path_complete.ts +165 -0
- package/bin/plugins/python-lsp.ts +162 -0
- package/bin/plugins/rust-lsp.ts +166 -0
- package/bin/plugins/search_replace.i18n.json +405 -0
- package/bin/plugins/search_replace.ts +484 -0
- package/bin/plugins/test_i18n.i18n.json +67 -0
- package/bin/plugins/test_i18n.ts +18 -0
- package/bin/plugins/theme_editor.i18n.json +3746 -0
- package/bin/plugins/theme_editor.ts +2063 -0
- package/bin/plugins/todo_highlighter.i18n.json +184 -0
- package/bin/plugins/todo_highlighter.ts +206 -0
- package/bin/plugins/typescript-lsp.ts +167 -0
- package/bin/plugins/vi_mode.i18n.json +1549 -0
- package/bin/plugins/vi_mode.ts +2747 -0
- package/bin/plugins/welcome.i18n.json +236 -0
- package/bin/plugins/welcome.ts +76 -0
- package/bin/themes/dark.json +102 -0
- package/bin/themes/dracula.json +62 -0
- package/bin/themes/high-contrast.json +102 -0
- package/bin/themes/light.json +102 -0
- package/bin/themes/nord.json +62 -0
- package/bin/themes/nostalgia.json +102 -0
- package/bin/themes/solarized-dark.json +62 -0
- package/binary-install.js +1 -1
- package/dist/bin/fresh.js +9 -0
- package/dist/binary-install.js +149 -0
- package/dist/binary.js +30 -0
- package/dist/fresh-6yhknp07.exe +0 -0
- package/dist/install.js +158 -0
- package/dist/run-fresh.js +43 -0
- package/package.json +7 -2
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/// <reference path="../types/fresh.d.ts" />
|
|
2
|
+
const editor = getEditor();
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Multi-File Search & Replace Plugin
|
|
7
|
+
*
|
|
8
|
+
* Provides project-wide search and replace functionality using git grep.
|
|
9
|
+
* Shows results in a virtual buffer split with preview and confirmation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Result item structure
|
|
13
|
+
interface SearchResult {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
column: number;
|
|
17
|
+
content: string;
|
|
18
|
+
selected: boolean; // Whether this result will be replaced
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Plugin state
|
|
22
|
+
let panelOpen = false;
|
|
23
|
+
let resultsBufferId: number | null = null;
|
|
24
|
+
let sourceSplitId: number | null = null;
|
|
25
|
+
let resultsSplitId: number | null = null;
|
|
26
|
+
let searchResults: SearchResult[] = [];
|
|
27
|
+
let searchPattern: string = "";
|
|
28
|
+
let replaceText: string = "";
|
|
29
|
+
let searchRegex: boolean = false;
|
|
30
|
+
|
|
31
|
+
// Maximum results to display
|
|
32
|
+
const MAX_RESULTS = 200;
|
|
33
|
+
|
|
34
|
+
// Define the search-replace mode with keybindings
|
|
35
|
+
editor.defineMode(
|
|
36
|
+
"search-replace-list",
|
|
37
|
+
null,
|
|
38
|
+
[
|
|
39
|
+
["Return", "search_replace_preview"],
|
|
40
|
+
["space", "search_replace_toggle_item"],
|
|
41
|
+
["a", "search_replace_select_all"],
|
|
42
|
+
["n", "search_replace_select_none"],
|
|
43
|
+
["r", "search_replace_execute"],
|
|
44
|
+
["q", "search_replace_close"],
|
|
45
|
+
["Escape", "search_replace_close"],
|
|
46
|
+
],
|
|
47
|
+
true // read-only
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Get relative path for display
|
|
51
|
+
function getRelativePath(filePath: string): string {
|
|
52
|
+
const cwd = editor.getCwd();
|
|
53
|
+
if (filePath.startsWith(cwd)) {
|
|
54
|
+
return filePath.slice(cwd.length + 1);
|
|
55
|
+
}
|
|
56
|
+
return filePath;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse git grep output
|
|
60
|
+
function parseGitGrepLine(line: string): SearchResult | null {
|
|
61
|
+
const match = line.match(/^([^:]+):(\d+):(\d+):(.*)$/);
|
|
62
|
+
if (match) {
|
|
63
|
+
return {
|
|
64
|
+
file: match[1],
|
|
65
|
+
line: parseInt(match[2], 10),
|
|
66
|
+
column: parseInt(match[3], 10),
|
|
67
|
+
content: match[4],
|
|
68
|
+
selected: true, // Selected by default
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Format a result for display
|
|
75
|
+
function formatResult(item: SearchResult, index: number): string {
|
|
76
|
+
const checkbox = item.selected ? "[x]" : "[ ]";
|
|
77
|
+
const displayPath = getRelativePath(item.file);
|
|
78
|
+
const location = `${displayPath}:${item.line}`;
|
|
79
|
+
|
|
80
|
+
// Truncate for display
|
|
81
|
+
const maxLocationLen = 40;
|
|
82
|
+
const truncatedLocation = location.length > maxLocationLen
|
|
83
|
+
? "..." + location.slice(-(maxLocationLen - 3))
|
|
84
|
+
: location.padEnd(maxLocationLen);
|
|
85
|
+
|
|
86
|
+
const trimmedContent = item.content.trim();
|
|
87
|
+
const maxContentLen = 50;
|
|
88
|
+
const displayContent = trimmedContent.length > maxContentLen
|
|
89
|
+
? trimmedContent.slice(0, maxContentLen - 3) + "..."
|
|
90
|
+
: trimmedContent;
|
|
91
|
+
|
|
92
|
+
return `${checkbox} ${truncatedLocation} ${displayContent}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Build panel entries
|
|
96
|
+
function buildPanelEntries(): TextPropertyEntry[] {
|
|
97
|
+
const entries: TextPropertyEntry[] = [];
|
|
98
|
+
|
|
99
|
+
// Header
|
|
100
|
+
const selectedCount = searchResults.filter(r => r.selected).length;
|
|
101
|
+
entries.push({
|
|
102
|
+
text: `═══ ${editor.t("panel.header")} ═══\n`,
|
|
103
|
+
properties: { type: "header" },
|
|
104
|
+
});
|
|
105
|
+
entries.push({
|
|
106
|
+
text: `${editor.t("panel.search_label")} "${searchPattern}"${searchRegex ? " " + editor.t("panel.regex") : ""}\n`,
|
|
107
|
+
properties: { type: "info" },
|
|
108
|
+
});
|
|
109
|
+
entries.push({
|
|
110
|
+
text: `${editor.t("panel.replace_label")} "${replaceText}"\n`,
|
|
111
|
+
properties: { type: "info" },
|
|
112
|
+
});
|
|
113
|
+
entries.push({
|
|
114
|
+
text: `\n`,
|
|
115
|
+
properties: { type: "spacer" },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (searchResults.length === 0) {
|
|
119
|
+
entries.push({
|
|
120
|
+
text: " " + editor.t("panel.no_matches") + "\n",
|
|
121
|
+
properties: { type: "empty" },
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
// Results header
|
|
125
|
+
const limitNote = searchResults.length >= MAX_RESULTS ? " " + editor.t("panel.limited", { max: String(MAX_RESULTS) }) : "";
|
|
126
|
+
entries.push({
|
|
127
|
+
text: `${editor.t("panel.results", { count: String(searchResults.length) })}${limitNote} ${editor.t("panel.selected", { selected: String(selectedCount) })}\n`,
|
|
128
|
+
properties: { type: "count" },
|
|
129
|
+
});
|
|
130
|
+
entries.push({
|
|
131
|
+
text: `\n`,
|
|
132
|
+
properties: { type: "spacer" },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Add each result
|
|
136
|
+
for (let i = 0; i < searchResults.length; i++) {
|
|
137
|
+
const result = searchResults[i];
|
|
138
|
+
entries.push({
|
|
139
|
+
text: formatResult(result, i),
|
|
140
|
+
properties: {
|
|
141
|
+
type: "result",
|
|
142
|
+
index: i,
|
|
143
|
+
location: {
|
|
144
|
+
file: result.file,
|
|
145
|
+
line: result.line,
|
|
146
|
+
column: result.column,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Footer
|
|
154
|
+
entries.push({
|
|
155
|
+
text: `───────────────────────────────────────────────────────────────────────────────\n`,
|
|
156
|
+
properties: { type: "separator" },
|
|
157
|
+
});
|
|
158
|
+
entries.push({
|
|
159
|
+
text: editor.t("panel.help") + "\n",
|
|
160
|
+
properties: { type: "help" },
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return entries;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Update panel content
|
|
167
|
+
function updatePanelContent(): void {
|
|
168
|
+
if (resultsBufferId !== null) {
|
|
169
|
+
const entries = buildPanelEntries();
|
|
170
|
+
editor.setVirtualBufferContent(resultsBufferId, entries);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Perform the search
|
|
175
|
+
async function performSearch(pattern: string, replace: string, isRegex: boolean): Promise<void> {
|
|
176
|
+
searchPattern = pattern;
|
|
177
|
+
replaceText = replace;
|
|
178
|
+
searchRegex = isRegex;
|
|
179
|
+
|
|
180
|
+
// Build git grep args
|
|
181
|
+
const args = ["grep", "-n", "--column", "-I"];
|
|
182
|
+
if (isRegex) {
|
|
183
|
+
args.push("-E"); // Extended regex
|
|
184
|
+
} else {
|
|
185
|
+
args.push("-F"); // Fixed string
|
|
186
|
+
}
|
|
187
|
+
args.push("--", pattern);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const cwd = editor.getCwd();
|
|
191
|
+
const result = await editor.spawnProcess("git", args, cwd);
|
|
192
|
+
|
|
193
|
+
searchResults = [];
|
|
194
|
+
|
|
195
|
+
if (result.exit_code === 0) {
|
|
196
|
+
for (const line of result.stdout.split("\n")) {
|
|
197
|
+
if (!line.trim()) continue;
|
|
198
|
+
const match = parseGitGrepLine(line);
|
|
199
|
+
if (match) {
|
|
200
|
+
searchResults.push(match);
|
|
201
|
+
if (searchResults.length >= MAX_RESULTS) break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (searchResults.length === 0) {
|
|
207
|
+
editor.setStatus(editor.t("status.no_matches", { pattern }));
|
|
208
|
+
} else {
|
|
209
|
+
editor.setStatus(editor.t("status.found_matches", { count: String(searchResults.length) }));
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
editor.setStatus(editor.t("status.search_error", { error: String(e) }));
|
|
213
|
+
searchResults = [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Show the search results panel
|
|
218
|
+
async function showResultsPanel(): Promise<void> {
|
|
219
|
+
if (panelOpen && resultsBufferId !== null) {
|
|
220
|
+
updatePanelContent();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
sourceSplitId = editor.getActiveSplitId();
|
|
225
|
+
const entries = buildPanelEntries();
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
resultsBufferId = await editor.createVirtualBufferInSplit({
|
|
229
|
+
name: "*Search/Replace*",
|
|
230
|
+
mode: "search-replace-list",
|
|
231
|
+
read_only: true,
|
|
232
|
+
entries: entries,
|
|
233
|
+
ratio: 0.6, // 60/40 split
|
|
234
|
+
panel_id: "search-replace-panel",
|
|
235
|
+
show_line_numbers: false,
|
|
236
|
+
show_cursors: true,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
panelOpen = true;
|
|
240
|
+
resultsSplitId = editor.getActiveSplitId();
|
|
241
|
+
editor.debug(`Search/Replace panel opened with buffer ID ${resultsBufferId}`);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
244
|
+
editor.setStatus(editor.t("status.failed_open_panel"));
|
|
245
|
+
editor.debug(`ERROR: createVirtualBufferInSplit failed: ${errorMessage}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Execute replacements
|
|
250
|
+
async function executeReplacements(): Promise<void> {
|
|
251
|
+
const selectedResults = searchResults.filter(r => r.selected);
|
|
252
|
+
|
|
253
|
+
if (selectedResults.length === 0) {
|
|
254
|
+
editor.setStatus(editor.t("status.no_selected"));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Group by file
|
|
259
|
+
const fileGroups: Map<string, SearchResult[]> = new Map();
|
|
260
|
+
for (const result of selectedResults) {
|
|
261
|
+
if (!fileGroups.has(result.file)) {
|
|
262
|
+
fileGroups.set(result.file, []);
|
|
263
|
+
}
|
|
264
|
+
fileGroups.get(result.file)!.push(result);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let filesModified = 0;
|
|
268
|
+
let replacementsCount = 0;
|
|
269
|
+
const errors: string[] = [];
|
|
270
|
+
|
|
271
|
+
for (const [filePath, results] of fileGroups) {
|
|
272
|
+
try {
|
|
273
|
+
// Read file
|
|
274
|
+
const content = await editor.readFile(filePath);
|
|
275
|
+
const lines = content.split("\n");
|
|
276
|
+
|
|
277
|
+
// Sort results by line (descending) to avoid offset issues
|
|
278
|
+
const sortedResults = [...results].sort((a, b) => {
|
|
279
|
+
if (a.line !== b.line) return b.line - a.line;
|
|
280
|
+
return b.column - a.column;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Apply replacements
|
|
284
|
+
for (const result of sortedResults) {
|
|
285
|
+
const lineIndex = result.line - 1;
|
|
286
|
+
if (lineIndex >= 0 && lineIndex < lines.length) {
|
|
287
|
+
let line = lines[lineIndex];
|
|
288
|
+
|
|
289
|
+
if (searchRegex) {
|
|
290
|
+
// Regex replacement
|
|
291
|
+
const regex = new RegExp(searchPattern, "g");
|
|
292
|
+
lines[lineIndex] = line.replace(regex, replaceText);
|
|
293
|
+
} else {
|
|
294
|
+
// Simple string replacement (all occurrences in line)
|
|
295
|
+
lines[lineIndex] = line.split(searchPattern).join(replaceText);
|
|
296
|
+
}
|
|
297
|
+
replacementsCount++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Write back
|
|
302
|
+
const newContent = lines.join("\n");
|
|
303
|
+
await editor.writeFile(filePath, newContent);
|
|
304
|
+
filesModified++;
|
|
305
|
+
|
|
306
|
+
} catch (e) {
|
|
307
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
308
|
+
errors.push(`${filePath}: ${errorMessage}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Report results
|
|
313
|
+
if (errors.length > 0) {
|
|
314
|
+
editor.setStatus(editor.t("status.replaced_with_errors", { files: String(filesModified), errors: String(errors.length) }));
|
|
315
|
+
editor.debug(`Replacement errors: ${errors.join(", ")}`);
|
|
316
|
+
} else {
|
|
317
|
+
editor.setStatus(editor.t("status.replaced", { count: String(replacementsCount), files: String(filesModified) }));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Close panel after replacement
|
|
321
|
+
globalThis.search_replace_close();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Start search/replace workflow
|
|
325
|
+
globalThis.start_search_replace = function(): void {
|
|
326
|
+
searchResults = [];
|
|
327
|
+
searchPattern = "";
|
|
328
|
+
replaceText = "";
|
|
329
|
+
|
|
330
|
+
editor.startPrompt(editor.t("prompt.search"), "search-replace-search");
|
|
331
|
+
editor.setStatus(editor.t("status.enter_pattern"));
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Handle search prompt confirmation
|
|
335
|
+
globalThis.onSearchReplaceSearchConfirmed = function(args: {
|
|
336
|
+
prompt_type: string;
|
|
337
|
+
selected_index: number | null;
|
|
338
|
+
input: string;
|
|
339
|
+
}): boolean {
|
|
340
|
+
if (args.prompt_type !== "search-replace-search") {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const pattern = args.input.trim();
|
|
345
|
+
if (!pattern) {
|
|
346
|
+
editor.setStatus(editor.t("status.cancelled_empty"));
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
searchPattern = pattern;
|
|
351
|
+
|
|
352
|
+
// Ask for replacement text
|
|
353
|
+
editor.startPrompt(editor.t("prompt.replace"), "search-replace-replace");
|
|
354
|
+
return true;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Handle replace prompt confirmation
|
|
358
|
+
globalThis.onSearchReplaceReplaceConfirmed = async function(args: {
|
|
359
|
+
prompt_type: string;
|
|
360
|
+
selected_index: number | null;
|
|
361
|
+
input: string;
|
|
362
|
+
}): Promise<boolean> {
|
|
363
|
+
if (args.prompt_type !== "search-replace-replace") {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
replaceText = args.input; // Can be empty for deletion
|
|
368
|
+
|
|
369
|
+
// Perform search and show results
|
|
370
|
+
await performSearch(searchPattern, replaceText, false);
|
|
371
|
+
await showResultsPanel();
|
|
372
|
+
|
|
373
|
+
return true;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Handle prompt cancellation
|
|
377
|
+
globalThis.onSearchReplacePromptCancelled = function(args: {
|
|
378
|
+
prompt_type: string;
|
|
379
|
+
}): boolean {
|
|
380
|
+
if (args.prompt_type !== "search-replace-search" &&
|
|
381
|
+
args.prompt_type !== "search-replace-replace") {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
editor.setStatus(editor.t("status.cancelled"));
|
|
386
|
+
return true;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Toggle selection of current item
|
|
390
|
+
globalThis.search_replace_toggle_item = function(): void {
|
|
391
|
+
if (resultsBufferId === null || searchResults.length === 0) return;
|
|
392
|
+
|
|
393
|
+
const props = editor.getTextPropertiesAtCursor(resultsBufferId);
|
|
394
|
+
if (props.length > 0 && typeof props[0].index === "number") {
|
|
395
|
+
const index = props[0].index as number;
|
|
396
|
+
if (index >= 0 && index < searchResults.length) {
|
|
397
|
+
searchResults[index].selected = !searchResults[index].selected;
|
|
398
|
+
updatePanelContent();
|
|
399
|
+
const selected = searchResults.filter(r => r.selected).length;
|
|
400
|
+
editor.setStatus(editor.t("status.selected_count", { selected: String(selected), total: String(searchResults.length) }));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Select all items
|
|
406
|
+
globalThis.search_replace_select_all = function(): void {
|
|
407
|
+
for (const result of searchResults) {
|
|
408
|
+
result.selected = true;
|
|
409
|
+
}
|
|
410
|
+
updatePanelContent();
|
|
411
|
+
editor.setStatus(editor.t("status.selected_count", { selected: String(searchResults.length), total: String(searchResults.length) }));
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// Select no items
|
|
415
|
+
globalThis.search_replace_select_none = function(): void {
|
|
416
|
+
for (const result of searchResults) {
|
|
417
|
+
result.selected = false;
|
|
418
|
+
}
|
|
419
|
+
updatePanelContent();
|
|
420
|
+
editor.setStatus(editor.t("status.selected_count", { selected: "0", total: String(searchResults.length) }));
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// Execute replacement
|
|
424
|
+
globalThis.search_replace_execute = function(): void {
|
|
425
|
+
const selected = searchResults.filter(r => r.selected).length;
|
|
426
|
+
if (selected === 0) {
|
|
427
|
+
editor.setStatus(editor.t("status.no_items_selected"));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
editor.setStatus(editor.t("status.replacing", { count: String(selected) }));
|
|
432
|
+
executeReplacements();
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Preview current item (jump to location)
|
|
436
|
+
globalThis.search_replace_preview = function(): void {
|
|
437
|
+
if (sourceSplitId === null || resultsBufferId === null) return;
|
|
438
|
+
|
|
439
|
+
const props = editor.getTextPropertiesAtCursor(resultsBufferId);
|
|
440
|
+
if (props.length > 0) {
|
|
441
|
+
const location = props[0].location as { file: string; line: number; column: number } | undefined;
|
|
442
|
+
if (location) {
|
|
443
|
+
editor.openFileInSplit(sourceSplitId, location.file, location.line, location.column);
|
|
444
|
+
editor.setStatus(editor.t("status.preview", { file: getRelativePath(location.file), line: String(location.line) }));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// Close the panel
|
|
450
|
+
globalThis.search_replace_close = function(): void {
|
|
451
|
+
if (!panelOpen) return;
|
|
452
|
+
|
|
453
|
+
if (resultsBufferId !== null) {
|
|
454
|
+
editor.closeBuffer(resultsBufferId);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (resultsSplitId !== null && resultsSplitId !== sourceSplitId) {
|
|
458
|
+
editor.closeSplit(resultsSplitId);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
panelOpen = false;
|
|
462
|
+
resultsBufferId = null;
|
|
463
|
+
sourceSplitId = null;
|
|
464
|
+
resultsSplitId = null;
|
|
465
|
+
searchResults = [];
|
|
466
|
+
editor.setStatus(editor.t("status.closed"));
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Register event handlers
|
|
470
|
+
editor.on("prompt_confirmed", "onSearchReplaceSearchConfirmed");
|
|
471
|
+
editor.on("prompt_confirmed", "onSearchReplaceReplaceConfirmed");
|
|
472
|
+
editor.on("prompt_cancelled", "onSearchReplacePromptCancelled");
|
|
473
|
+
|
|
474
|
+
// Register command
|
|
475
|
+
editor.registerCommand(
|
|
476
|
+
"%cmd.search_replace",
|
|
477
|
+
"%cmd.search_replace_desc",
|
|
478
|
+
"start_search_replace",
|
|
479
|
+
"normal"
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// Plugin initialization
|
|
483
|
+
editor.debug("Search & Replace plugin loaded");
|
|
484
|
+
editor.setStatus(editor.t("status.ready"));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cs": {
|
|
3
|
+
"cmd.test": "Testovací lokalizovaný příkaz",
|
|
4
|
+
"cmd.test_desc": "Příkaz pro testování lokalizace pluginů",
|
|
5
|
+
"msg.hello": "Ahoj, %{name}! Tvůj jazyk je %{locale}."
|
|
6
|
+
},
|
|
7
|
+
"de": {
|
|
8
|
+
"cmd.test": "Lokalisierter Testbefehl",
|
|
9
|
+
"cmd.test_desc": "Ein Befehl zum Testen der Plugin-Lokalisierung",
|
|
10
|
+
"msg.hello": "Hallo, %{name}! Deine Sprache ist %{locale}."
|
|
11
|
+
},
|
|
12
|
+
"en": {
|
|
13
|
+
"cmd.test": "Test Localized Command",
|
|
14
|
+
"cmd.test_desc": "A command to test plugin localization",
|
|
15
|
+
"msg.hello": "Hello, %{name}! Your locale is %{locale}."
|
|
16
|
+
},
|
|
17
|
+
"es": {
|
|
18
|
+
"cmd.test": "Comando de prueba localizado",
|
|
19
|
+
"cmd.test_desc": "Un comando para probar la localización de plugins",
|
|
20
|
+
"msg.hello": "¡Hola, %{name}! Tu idioma es %{locale}."
|
|
21
|
+
},
|
|
22
|
+
"fr": {
|
|
23
|
+
"cmd.test": "Commande de test localisée",
|
|
24
|
+
"cmd.test_desc": "Une commande pour tester la localisation des plugins",
|
|
25
|
+
"msg.hello": "Bonjour, %{name} ! Votre langue est %{locale}."
|
|
26
|
+
},
|
|
27
|
+
"it": {
|
|
28
|
+
"cmd.test": "Comando di test localizzato",
|
|
29
|
+
"cmd.test_desc": "Un comando per testare la localizzazione dei plugin",
|
|
30
|
+
"msg.hello": "Ciao, %{name}! La tua lingua è %{locale}."
|
|
31
|
+
},
|
|
32
|
+
"ja": {
|
|
33
|
+
"cmd.test": "ローカライズテストコマンド",
|
|
34
|
+
"cmd.test_desc": "プラグインのローカライズをテストするコマンド",
|
|
35
|
+
"msg.hello": "こんにちは、%{name}さん!あなたの言語は%{locale}です。"
|
|
36
|
+
},
|
|
37
|
+
"ko": {
|
|
38
|
+
"cmd.test": "현지화 테스트 명령",
|
|
39
|
+
"cmd.test_desc": "플러그인 현지화를 테스트하는 명령",
|
|
40
|
+
"msg.hello": "안녕하세요, %{name}님! 당신의 언어는 %{locale}입니다."
|
|
41
|
+
},
|
|
42
|
+
"pt-BR": {
|
|
43
|
+
"cmd.test": "Comando de teste localizado",
|
|
44
|
+
"cmd.test_desc": "Um comando para testar a localização de plugins",
|
|
45
|
+
"msg.hello": "Olá, %{name}! Seu idioma é %{locale}."
|
|
46
|
+
},
|
|
47
|
+
"ru": {
|
|
48
|
+
"cmd.test": "Локализованная тестовая команда",
|
|
49
|
+
"cmd.test_desc": "Команда для тестирования локализации плагинов",
|
|
50
|
+
"msg.hello": "Привет, %{name}! Ваш язык — %{locale}."
|
|
51
|
+
},
|
|
52
|
+
"th": {
|
|
53
|
+
"cmd.test": "คำสั่งทดสอบการแปล",
|
|
54
|
+
"cmd.test_desc": "คำสั่งสำหรับทดสอบการแปลปลั๊กอิน",
|
|
55
|
+
"msg.hello": "สวัสดี %{name}! ภาษาของคุณคือ %{locale}"
|
|
56
|
+
},
|
|
57
|
+
"uk": {
|
|
58
|
+
"cmd.test": "Локалізована тестова команда",
|
|
59
|
+
"cmd.test_desc": "Команда для тестування локалізації плагінів",
|
|
60
|
+
"msg.hello": "Привіт, %{name}! Ваша мова — %{locale}."
|
|
61
|
+
},
|
|
62
|
+
"zh-CN": {
|
|
63
|
+
"cmd.test": "本地化测试命令",
|
|
64
|
+
"cmd.test_desc": "用于测试插件本地化的命令",
|
|
65
|
+
"msg.hello": "你好,%{name}!你的语言是%{locale}。"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference path="./lib/fresh.d.ts" />
|
|
2
|
+
const editor = getEditor();
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
globalThis.test_i18n_action = function() {
|
|
6
|
+
const locale = editor.getCurrentLocale();
|
|
7
|
+
const msg = editor.t("msg.hello", { name: "User", locale: locale });
|
|
8
|
+
editor.setStatus(msg);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
editor.registerCommand(
|
|
12
|
+
"%cmd.test",
|
|
13
|
+
"%cmd.test_desc",
|
|
14
|
+
"test_i18n_action",
|
|
15
|
+
"normal"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
editor.setStatus("Test i18n plugin loaded");
|