@fresh-editor/fresh-editor 0.1.74 → 0.1.76
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/CHANGELOG.md +54 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/plugins/audit_mode.ts +9 -4
- package/plugins/buffer_modified.ts +1 -1
- package/plugins/calculator.ts +1 -1
- package/plugins/check-types.sh +41 -0
- package/plugins/clangd_support.ts +1 -1
- package/plugins/color_highlighter.ts +4 -1
- package/plugins/config-schema.json +44 -2
- package/plugins/diagnostics_panel.i18n.json +52 -52
- package/plugins/diagnostics_panel.ts +168 -540
- package/plugins/find_references.ts +82 -324
- package/plugins/git_blame.i18n.json +260 -247
- package/plugins/git_blame.ts +4 -9
- package/plugins/git_find_file.ts +42 -270
- package/plugins/git_grep.ts +50 -167
- package/plugins/git_gutter.ts +1 -1
- package/plugins/git_log.ts +4 -11
- package/plugins/lib/finder.ts +1499 -0
- package/plugins/lib/fresh.d.ts +104 -19
- package/plugins/lib/index.ts +14 -0
- package/plugins/lib/navigation-controller.ts +1 -1
- package/plugins/lib/panel-manager.ts +7 -13
- package/plugins/lib/results-panel.ts +914 -0
- package/plugins/lib/search-utils.ts +343 -0
- package/plugins/lib/virtual-buffer-factory.ts +3 -2
- package/plugins/live_grep.ts +56 -379
- package/plugins/markdown_compose.ts +1 -17
- package/plugins/merge_conflict.ts +16 -14
- package/plugins/path_complete.ts +1 -1
- package/plugins/search_replace.i18n.json +13 -13
- package/plugins/search_replace.ts +11 -9
- package/plugins/theme_editor.ts +57 -30
- package/plugins/todo_highlighter.ts +1 -0
- package/plugins/vi_mode.ts +9 -5
- package/plugins/welcome.ts +1 -1
- package/themes/dark.json +102 -0
- package/themes/high-contrast.json +102 -0
- package/themes/light.json +102 -0
- package/themes/nostalgia.json +102 -0
package/plugins/live_grep.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/// <reference path="./lib/fresh.d.ts" />
|
|
2
|
-
const editor = getEditor();
|
|
3
|
-
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Live Grep Plugin
|
|
7
5
|
*
|
|
8
6
|
* Project-wide search with ripgrep and live preview.
|
|
7
|
+
* Uses the Finder abstraction for unified search UX.
|
|
8
|
+
*
|
|
9
9
|
* - Type to search across all files
|
|
10
10
|
* - Navigate results with Up/Down to see preview
|
|
11
11
|
* - Press Enter to open file at location
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { Finder, parseGrepOutput } from "./lib/finder.ts";
|
|
15
|
+
|
|
16
|
+
const editor = getEditor();
|
|
17
|
+
|
|
18
|
+
// Result type from ripgrep
|
|
14
19
|
interface GrepMatch {
|
|
15
20
|
file: string;
|
|
16
21
|
line: number;
|
|
@@ -18,398 +23,70 @@ interface GrepMatch {
|
|
|
18
23
|
content: string;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Parse ripgrep output into suggestions
|
|
50
|
-
function parseRipgrepOutput(stdout: string): {
|
|
51
|
-
results: GrepMatch[];
|
|
52
|
-
suggestions: PromptSuggestion[];
|
|
53
|
-
} {
|
|
54
|
-
const results: GrepMatch[] = [];
|
|
55
|
-
const suggestions: PromptSuggestion[] = [];
|
|
56
|
-
|
|
57
|
-
for (const line of stdout.split("\n")) {
|
|
58
|
-
if (!line.trim()) continue;
|
|
59
|
-
const match = parseRipgrepLine(line);
|
|
60
|
-
if (match) {
|
|
61
|
-
results.push(match);
|
|
62
|
-
|
|
63
|
-
// Truncate long content for display
|
|
64
|
-
const displayContent =
|
|
65
|
-
match.content.length > 60
|
|
66
|
-
? match.content.substring(0, 57) + "..."
|
|
67
|
-
: match.content;
|
|
68
|
-
|
|
69
|
-
suggestions.push({
|
|
70
|
-
text: `${match.file}:${match.line}`,
|
|
71
|
-
description: displayContent.trim(),
|
|
72
|
-
value: `${results.length - 1}`, // Store index as value
|
|
73
|
-
disabled: false,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Limit to 100 results for performance
|
|
77
|
-
if (results.length >= 100) {
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { results, suggestions };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Create or update preview buffer with file content
|
|
87
|
-
async function updatePreview(match: GrepMatch): Promise<void> {
|
|
88
|
-
try {
|
|
89
|
-
// Read the file content
|
|
90
|
-
const content = await editor.readFile(match.file);
|
|
91
|
-
const lines = content.split("\n");
|
|
92
|
-
|
|
93
|
-
// Calculate context window (5 lines before and after)
|
|
94
|
-
const contextBefore = 5;
|
|
95
|
-
const contextAfter = 5;
|
|
96
|
-
const startLine = Math.max(0, match.line - 1 - contextBefore);
|
|
97
|
-
const endLine = Math.min(lines.length, match.line + contextAfter);
|
|
98
|
-
|
|
99
|
-
// Build preview entries with highlighting
|
|
100
|
-
const entries: TextPropertyEntry[] = [];
|
|
101
|
-
|
|
102
|
-
// Header
|
|
103
|
-
entries.push({
|
|
104
|
-
text: ` ${match.file}:${match.line}:${match.column}\n`,
|
|
105
|
-
properties: { type: "header" },
|
|
106
|
-
});
|
|
107
|
-
entries.push({
|
|
108
|
-
text: "─".repeat(60) + "\n",
|
|
109
|
-
properties: { type: "separator" },
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Content lines with line numbers
|
|
113
|
-
for (let i = startLine; i < endLine; i++) {
|
|
114
|
-
const lineNum = i + 1;
|
|
115
|
-
const lineContent = lines[i] || "";
|
|
116
|
-
const isMatchLine = lineNum === match.line;
|
|
117
|
-
const prefix = isMatchLine ? "> " : " ";
|
|
118
|
-
const lineNumStr = String(lineNum).padStart(4, " ");
|
|
119
|
-
|
|
120
|
-
entries.push({
|
|
121
|
-
text: `${prefix}${lineNumStr} │ ${lineContent}\n`,
|
|
122
|
-
properties: {
|
|
123
|
-
type: isMatchLine ? "match" : "context",
|
|
124
|
-
line: lineNum,
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Create or update the preview buffer
|
|
130
|
-
if (previewBufferId === null) {
|
|
131
|
-
// Define mode for preview buffer
|
|
132
|
-
editor.defineMode("live-grep-preview", "special", [["q", "close_buffer"]], true);
|
|
133
|
-
|
|
134
|
-
// Create preview in a split on the right
|
|
135
|
-
const result = await editor.createVirtualBufferInSplit({
|
|
136
|
-
name: "*Preview*",
|
|
137
|
-
mode: "live-grep-preview",
|
|
138
|
-
read_only: true,
|
|
139
|
-
entries,
|
|
140
|
-
ratio: 0.5,
|
|
141
|
-
direction: "vertical",
|
|
142
|
-
panel_id: "live-grep-preview",
|
|
143
|
-
show_line_numbers: false,
|
|
144
|
-
editing_disabled: true,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Extract buffer and split IDs from result
|
|
148
|
-
previewBufferId = result.buffer_id;
|
|
149
|
-
previewSplitId = result.split_id ?? null;
|
|
150
|
-
|
|
151
|
-
// Return focus to original split so prompt stays active
|
|
152
|
-
if (originalSplitId !== null) {
|
|
153
|
-
editor.focusSplit(originalSplitId);
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
// Update existing buffer content
|
|
157
|
-
editor.setVirtualBufferContent(previewBufferId, entries);
|
|
158
|
-
}
|
|
159
|
-
} catch (e) {
|
|
160
|
-
editor.debug(`Failed to update preview: ${e}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Close preview buffer and its split
|
|
165
|
-
function closePreview(): void {
|
|
166
|
-
// Close the buffer first
|
|
167
|
-
if (previewBufferId !== null) {
|
|
168
|
-
editor.closeBuffer(previewBufferId);
|
|
169
|
-
previewBufferId = null;
|
|
170
|
-
}
|
|
171
|
-
// Then close the split
|
|
172
|
-
if (previewSplitId !== null) {
|
|
173
|
-
editor.closeSplit(previewSplitId);
|
|
174
|
-
previewSplitId = null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Run ripgrep search with debouncing
|
|
179
|
-
async function runSearch(query: string): Promise<void> {
|
|
180
|
-
// Increment version to invalidate any pending debounced search
|
|
181
|
-
const thisVersion = ++searchVersion;
|
|
182
|
-
editor.debug(`[live_grep] runSearch called: query="${query}", version=${thisVersion}`);
|
|
183
|
-
|
|
184
|
-
// Kill any existing search immediately (don't wait) to stop wasting CPU
|
|
185
|
-
// Store the kill promise globally so ALL pending searches wait for it
|
|
186
|
-
if (currentSearch) {
|
|
187
|
-
editor.debug(`[live_grep] killing existing search immediately`);
|
|
188
|
-
pendingKill = currentSearch.kill();
|
|
189
|
-
currentSearch = null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!query || query.trim().length < 2) {
|
|
193
|
-
// Wait for any pending kill to complete before returning
|
|
194
|
-
if (pendingKill) {
|
|
195
|
-
await pendingKill;
|
|
196
|
-
pendingKill = null;
|
|
197
|
-
}
|
|
198
|
-
editor.debug(`[live_grep] query too short, clearing`);
|
|
199
|
-
editor.setPromptSuggestions([]);
|
|
200
|
-
grepResults = [];
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Debounce: wait a bit to see if user is still typing
|
|
205
|
-
editor.debug(`[live_grep] debouncing for ${DEBOUNCE_MS}ms...`);
|
|
206
|
-
await editor.delay(DEBOUNCE_MS);
|
|
207
|
-
|
|
208
|
-
// Always await any pending kill before continuing - ensures old process is dead
|
|
209
|
-
if (pendingKill) {
|
|
210
|
-
editor.debug(`[live_grep] waiting for previous search to terminate`);
|
|
211
|
-
await pendingKill;
|
|
212
|
-
pendingKill = null;
|
|
213
|
-
editor.debug(`[live_grep] previous search terminated`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// If version changed during delay, a newer search was triggered - abort this one
|
|
217
|
-
if (searchVersion !== thisVersion) {
|
|
218
|
-
editor.debug(`[live_grep] version mismatch after debounce (${thisVersion} vs ${searchVersion}), aborting`);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Avoid duplicate searches
|
|
223
|
-
if (query === lastQuery) {
|
|
224
|
-
editor.debug(`[live_grep] duplicate query, skipping`);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
lastQuery = query;
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
const cwd = editor.getCwd();
|
|
231
|
-
editor.debug(`[live_grep] spawning rg for query="${query}" in cwd="${cwd}"`);
|
|
232
|
-
const searchStartTime = Date.now();
|
|
233
|
-
const search = editor.spawnProcess("rg", [
|
|
26
|
+
// Create the finder instance
|
|
27
|
+
const finder = new Finder<GrepMatch>(editor, {
|
|
28
|
+
id: "live-grep",
|
|
29
|
+
format: (match) => ({
|
|
30
|
+
label: `${match.file}:${match.line}`,
|
|
31
|
+
description:
|
|
32
|
+
match.content.length > 60
|
|
33
|
+
? match.content.substring(0, 57).trim() + "..."
|
|
34
|
+
: match.content.trim(),
|
|
35
|
+
location: {
|
|
36
|
+
file: match.file,
|
|
37
|
+
line: match.line,
|
|
38
|
+
column: match.column,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
preview: true,
|
|
42
|
+
maxResults: 100,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Search function that parses ripgrep output
|
|
46
|
+
async function searchWithRipgrep(query: string): Promise<GrepMatch[]> {
|
|
47
|
+
const cwd = editor.getCwd();
|
|
48
|
+
const result = await editor.spawnProcess(
|
|
49
|
+
"rg",
|
|
50
|
+
[
|
|
234
51
|
"--line-number",
|
|
235
52
|
"--column",
|
|
236
53
|
"--no-heading",
|
|
237
54
|
"--color=never",
|
|
238
55
|
"--smart-case",
|
|
239
56
|
"--max-count=100",
|
|
240
|
-
"-g",
|
|
241
|
-
"
|
|
242
|
-
"-g",
|
|
243
|
-
"
|
|
57
|
+
"-g",
|
|
58
|
+
"!.git",
|
|
59
|
+
"-g",
|
|
60
|
+
"!node_modules",
|
|
61
|
+
"-g",
|
|
62
|
+
"!target",
|
|
63
|
+
"-g",
|
|
64
|
+
"!*.lock",
|
|
244
65
|
"--",
|
|
245
66
|
query,
|
|
246
|
-
],
|
|
67
|
+
],
|
|
68
|
+
cwd
|
|
69
|
+
);
|
|
247
70
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const result = await search;
|
|
251
|
-
const searchDuration = Date.now() - searchStartTime;
|
|
252
|
-
editor.debug(`[live_grep] rg completed in ${searchDuration}ms, exit_code=${result.exit_code}, stdout_len=${result.stdout.length}`);
|
|
253
|
-
|
|
254
|
-
// Check if this search was cancelled (a new search started)
|
|
255
|
-
if (currentSearch !== search) {
|
|
256
|
-
editor.debug(`[live_grep] search was superseded, discarding results`);
|
|
257
|
-
return; // Discard stale results
|
|
258
|
-
}
|
|
259
|
-
currentSearch = null;
|
|
260
|
-
|
|
261
|
-
if (result.exit_code === 0) {
|
|
262
|
-
const parseStart = Date.now();
|
|
263
|
-
const { results, suggestions } = parseRipgrepOutput(result.stdout);
|
|
264
|
-
editor.debug(`[live_grep] parsed ${results.length} results in ${Date.now() - parseStart}ms`);
|
|
265
|
-
grepResults = results;
|
|
266
|
-
editor.setPromptSuggestions(suggestions);
|
|
267
|
-
|
|
268
|
-
if (results.length > 0) {
|
|
269
|
-
editor.setStatus(editor.t("status.found_matches", { count: String(results.length) }));
|
|
270
|
-
// Show preview of first result
|
|
271
|
-
await updatePreview(results[0]);
|
|
272
|
-
} else {
|
|
273
|
-
editor.setStatus(editor.t("status.no_matches"));
|
|
274
|
-
}
|
|
275
|
-
} else if (result.exit_code === 1) {
|
|
276
|
-
// No matches
|
|
277
|
-
editor.debug(`[live_grep] no matches (exit_code=1)`);
|
|
278
|
-
grepResults = [];
|
|
279
|
-
editor.setPromptSuggestions([]);
|
|
280
|
-
editor.setStatus(editor.t("status.no_matches"));
|
|
281
|
-
} else if (result.exit_code === -1) {
|
|
282
|
-
// Process was killed, ignore
|
|
283
|
-
editor.debug(`[live_grep] process was killed`);
|
|
284
|
-
} else {
|
|
285
|
-
editor.debug(`[live_grep] search error: ${result.stderr}`);
|
|
286
|
-
editor.setStatus(editor.t("status.search_error", { error: result.stderr }));
|
|
287
|
-
}
|
|
288
|
-
} catch (e) {
|
|
289
|
-
// Ignore errors from killed processes
|
|
290
|
-
const errorMsg = String(e);
|
|
291
|
-
editor.debug(`[live_grep] caught error: ${errorMsg}`);
|
|
292
|
-
if (!errorMsg.includes("killed") && !errorMsg.includes("not found")) {
|
|
293
|
-
editor.setStatus(editor.t("status.search_error", { error: String(e) }));
|
|
294
|
-
}
|
|
71
|
+
if (result.exit_code === 0) {
|
|
72
|
+
return parseGrepOutput(result.stdout, 100) as GrepMatch[];
|
|
295
73
|
}
|
|
74
|
+
return [];
|
|
296
75
|
}
|
|
297
76
|
|
|
298
77
|
// Start live grep
|
|
299
78
|
globalThis.start_live_grep = function (): void {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
editor.startPrompt(editor.t("prompt.live_grep"), "live-grep");
|
|
310
|
-
editor.setStatus(editor.t("status.type_to_search"));
|
|
79
|
+
finder.prompt({
|
|
80
|
+
title: editor.t("prompt.live_grep"),
|
|
81
|
+
source: {
|
|
82
|
+
mode: "search",
|
|
83
|
+
search: searchWithRipgrep,
|
|
84
|
+
debounceMs: 150,
|
|
85
|
+
minQueryLength: 2,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
311
88
|
};
|
|
312
89
|
|
|
313
|
-
// Handle prompt input changes
|
|
314
|
-
globalThis.onLiveGrepPromptChanged = function (args: {
|
|
315
|
-
prompt_type: string;
|
|
316
|
-
input: string;
|
|
317
|
-
}): boolean {
|
|
318
|
-
if (args.prompt_type !== "live-grep") {
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
editor.debug(`[live_grep] onPromptChanged: input="${args.input}"`);
|
|
323
|
-
|
|
324
|
-
// runSearch handles debouncing internally
|
|
325
|
-
runSearch(args.input);
|
|
326
|
-
|
|
327
|
-
return true;
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// Handle selection changes - update preview
|
|
331
|
-
globalThis.onLiveGrepSelectionChanged = function (args: {
|
|
332
|
-
prompt_type: string;
|
|
333
|
-
selected_index: number;
|
|
334
|
-
}): boolean {
|
|
335
|
-
if (args.prompt_type !== "live-grep") {
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const match = grepResults[args.selected_index];
|
|
340
|
-
if (match) {
|
|
341
|
-
updatePreview(match);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return true;
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// Handle prompt confirmation - open file
|
|
348
|
-
globalThis.onLiveGrepPromptConfirmed = function (args: {
|
|
349
|
-
prompt_type: string;
|
|
350
|
-
selected_index: number | null;
|
|
351
|
-
input: string;
|
|
352
|
-
}): boolean {
|
|
353
|
-
if (args.prompt_type !== "live-grep") {
|
|
354
|
-
return true;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Kill any running search
|
|
358
|
-
if (currentSearch) {
|
|
359
|
-
currentSearch.kill();
|
|
360
|
-
currentSearch = null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Close preview first
|
|
364
|
-
closePreview();
|
|
365
|
-
|
|
366
|
-
// Open selected file
|
|
367
|
-
if (args.selected_index !== null && grepResults[args.selected_index]) {
|
|
368
|
-
const selected = grepResults[args.selected_index];
|
|
369
|
-
editor.openFile(selected.file, selected.line, selected.column);
|
|
370
|
-
editor.setStatus(editor.t("status.opened_file", { file: selected.file, line: String(selected.line) }));
|
|
371
|
-
} else {
|
|
372
|
-
editor.setStatus(editor.t("status.no_file_selected"));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Clear state
|
|
376
|
-
grepResults = [];
|
|
377
|
-
originalSplitId = null;
|
|
378
|
-
previewSplitId = null;
|
|
379
|
-
|
|
380
|
-
return true;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
// Handle prompt cancellation
|
|
384
|
-
globalThis.onLiveGrepPromptCancelled = function (args: {
|
|
385
|
-
prompt_type: string;
|
|
386
|
-
}): boolean {
|
|
387
|
-
if (args.prompt_type !== "live-grep") {
|
|
388
|
-
return true;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Kill any running search
|
|
392
|
-
if (currentSearch) {
|
|
393
|
-
currentSearch.kill();
|
|
394
|
-
currentSearch = null;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Close preview and cleanup
|
|
398
|
-
closePreview();
|
|
399
|
-
grepResults = [];
|
|
400
|
-
originalSplitId = null;
|
|
401
|
-
previewSplitId = null;
|
|
402
|
-
editor.setStatus(editor.t("status.cancelled"));
|
|
403
|
-
|
|
404
|
-
return true;
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// Register event handlers
|
|
408
|
-
editor.on("prompt_changed", "onLiveGrepPromptChanged");
|
|
409
|
-
editor.on("prompt_selection_changed", "onLiveGrepSelectionChanged");
|
|
410
|
-
editor.on("prompt_confirmed", "onLiveGrepPromptConfirmed");
|
|
411
|
-
editor.on("prompt_cancelled", "onLiveGrepPromptCancelled");
|
|
412
|
-
|
|
413
90
|
// Register command
|
|
414
91
|
editor.registerCommand(
|
|
415
92
|
"%cmd.live_grep",
|
|
@@ -418,5 +95,5 @@ editor.registerCommand(
|
|
|
418
95
|
"normal"
|
|
419
96
|
);
|
|
420
97
|
|
|
421
|
-
editor.debug("Live Grep plugin loaded");
|
|
98
|
+
editor.debug("Live Grep plugin loaded (using Finder abstraction)");
|
|
422
99
|
editor.setStatus(editor.t("status.ready"));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference path="./lib/fresh.d.ts" />
|
|
1
2
|
// Markdown Compose Mode Plugin
|
|
2
3
|
// Provides compose mode for Markdown documents with:
|
|
3
4
|
// - Soft wrapping at a configurable width
|
|
@@ -24,23 +25,6 @@ const config: MarkdownConfig = {
|
|
|
24
25
|
// Track buffers in compose mode (explicit toggle)
|
|
25
26
|
const composeBuffers = new Set<number>();
|
|
26
27
|
|
|
27
|
-
// Types match the Rust ViewTokenWire structure
|
|
28
|
-
interface ViewTokenWire {
|
|
29
|
-
source_offset: number | null;
|
|
30
|
-
kind: ViewTokenWireKind;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type ViewTokenWireKind =
|
|
34
|
-
| { Text: string }
|
|
35
|
-
| "Newline"
|
|
36
|
-
| "Space"
|
|
37
|
-
| "Break";
|
|
38
|
-
|
|
39
|
-
interface LayoutHints {
|
|
40
|
-
compose_width?: number | null;
|
|
41
|
-
column_guides?: number[] | null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
// =============================================================================
|
|
45
29
|
// Block-based parser for hanging indent support
|
|
46
30
|
// =============================================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/// <reference path="
|
|
1
|
+
/// <reference path="./lib/fresh.d.ts" />
|
|
2
2
|
const editor = getEditor();
|
|
3
3
|
|
|
4
4
|
|
|
@@ -14,6 +14,9 @@ const editor = getEditor();
|
|
|
14
14
|
* - Visual highlighting with intra-line diffing
|
|
15
15
|
*
|
|
16
16
|
* Architecture: Plugin-based implementation following the spec in docs/MERGE.md
|
|
17
|
+
*
|
|
18
|
+
* TODO: Consider rewriting to use the built-in side-by-side diff in the core
|
|
19
|
+
* editor, or use composite buffers for a simpler implementation.
|
|
17
20
|
*/
|
|
18
21
|
|
|
19
22
|
// =============================================================================
|
|
@@ -915,19 +918,19 @@ function buildResultEntries(): TextPropertyEntry[] {
|
|
|
915
918
|
function applyHighlighting(): void {
|
|
916
919
|
// Highlight OURS panel
|
|
917
920
|
if (mergeState.oursPanelId !== null) {
|
|
918
|
-
editor.
|
|
921
|
+
editor.clearAllOverlays(mergeState.oursPanelId);
|
|
919
922
|
highlightPanel(mergeState.oursPanelId, "ours");
|
|
920
923
|
}
|
|
921
924
|
|
|
922
925
|
// Highlight THEIRS panel
|
|
923
926
|
if (mergeState.theirsPanelId !== null) {
|
|
924
|
-
editor.
|
|
927
|
+
editor.clearAllOverlays(mergeState.theirsPanelId);
|
|
925
928
|
highlightPanel(mergeState.theirsPanelId, "theirs");
|
|
926
929
|
}
|
|
927
930
|
|
|
928
931
|
// Highlight RESULT panel
|
|
929
932
|
if (mergeState.resultPanelId !== null) {
|
|
930
|
-
editor.
|
|
933
|
+
editor.clearAllOverlays(mergeState.resultPanelId);
|
|
931
934
|
highlightResultPanel(mergeState.resultPanelId);
|
|
932
935
|
}
|
|
933
936
|
}
|
|
@@ -1337,7 +1340,6 @@ async function createMergePanels(): Promise<void> {
|
|
|
1337
1340
|
mode: "merge-conflict",
|
|
1338
1341
|
read_only: true,
|
|
1339
1342
|
entries: buildFullFileEntries("ours"),
|
|
1340
|
-
panel_id: "merge-ours",
|
|
1341
1343
|
show_line_numbers: true,
|
|
1342
1344
|
show_cursors: true,
|
|
1343
1345
|
editing_disabled: true,
|
|
@@ -1349,7 +1351,7 @@ async function createMergePanels(): Promise<void> {
|
|
|
1349
1351
|
}
|
|
1350
1352
|
|
|
1351
1353
|
// Create THEIRS panel to the right (vertical split)
|
|
1352
|
-
const
|
|
1354
|
+
const theirsResult = await editor.createVirtualBufferInSplit({
|
|
1353
1355
|
name: `*THEIRS*${sourceExt}`,
|
|
1354
1356
|
mode: "merge-conflict",
|
|
1355
1357
|
read_only: true,
|
|
@@ -1362,9 +1364,9 @@ async function createMergePanels(): Promise<void> {
|
|
|
1362
1364
|
editing_disabled: true,
|
|
1363
1365
|
});
|
|
1364
1366
|
|
|
1365
|
-
if (
|
|
1366
|
-
mergeState.theirsPanelId =
|
|
1367
|
-
mergeState.theirsSplitId = editor.getActiveSplitId();
|
|
1367
|
+
if (theirsResult !== null) {
|
|
1368
|
+
mergeState.theirsPanelId = theirsResult.buffer_id;
|
|
1369
|
+
mergeState.theirsSplitId = theirsResult.split_id ?? editor.getActiveSplitId();
|
|
1368
1370
|
}
|
|
1369
1371
|
|
|
1370
1372
|
// Focus back on OURS and create RESULT in the middle
|
|
@@ -1372,7 +1374,7 @@ async function createMergePanels(): Promise<void> {
|
|
|
1372
1374
|
editor.focusSplit(mergeState.oursSplitId);
|
|
1373
1375
|
}
|
|
1374
1376
|
|
|
1375
|
-
const
|
|
1377
|
+
const resultResult = await editor.createVirtualBufferInSplit({
|
|
1376
1378
|
name: `*RESULT*${sourceExt}`,
|
|
1377
1379
|
mode: "merge-result",
|
|
1378
1380
|
read_only: false,
|
|
@@ -1385,9 +1387,9 @@ async function createMergePanels(): Promise<void> {
|
|
|
1385
1387
|
editing_disabled: false,
|
|
1386
1388
|
});
|
|
1387
1389
|
|
|
1388
|
-
if (
|
|
1389
|
-
mergeState.resultPanelId =
|
|
1390
|
-
mergeState.resultSplitId = editor.getActiveSplitId();
|
|
1390
|
+
if (resultResult !== null) {
|
|
1391
|
+
mergeState.resultPanelId = resultResult.buffer_id;
|
|
1392
|
+
mergeState.resultSplitId = resultResult.split_id ?? editor.getActiveSplitId();
|
|
1391
1393
|
}
|
|
1392
1394
|
|
|
1393
1395
|
// Distribute splits evenly so all three panels get equal width
|
|
@@ -1606,7 +1608,7 @@ globalThis.merge_save_and_exit = async function(): Promise<void> {
|
|
|
1606
1608
|
|
|
1607
1609
|
// Delete all content
|
|
1608
1610
|
if (bufferLength > 0) {
|
|
1609
|
-
editor.deleteRange(mergeState.sourceBufferId,
|
|
1611
|
+
editor.deleteRange(mergeState.sourceBufferId, 0, bufferLength);
|
|
1610
1612
|
}
|
|
1611
1613
|
|
|
1612
1614
|
// Insert resolved content
|
package/plugins/path_complete.ts
CHANGED