@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +6 -0
  3. package/package.json +1 -1
  4. package/plugins/audit_mode.ts +9 -4
  5. package/plugins/buffer_modified.ts +1 -1
  6. package/plugins/calculator.ts +1 -1
  7. package/plugins/check-types.sh +41 -0
  8. package/plugins/clangd_support.ts +1 -1
  9. package/plugins/color_highlighter.ts +4 -1
  10. package/plugins/config-schema.json +44 -2
  11. package/plugins/diagnostics_panel.i18n.json +52 -52
  12. package/plugins/diagnostics_panel.ts +168 -540
  13. package/plugins/find_references.ts +82 -324
  14. package/plugins/git_blame.i18n.json +260 -247
  15. package/plugins/git_blame.ts +4 -9
  16. package/plugins/git_find_file.ts +42 -270
  17. package/plugins/git_grep.ts +50 -167
  18. package/plugins/git_gutter.ts +1 -1
  19. package/plugins/git_log.ts +4 -11
  20. package/plugins/lib/finder.ts +1499 -0
  21. package/plugins/lib/fresh.d.ts +104 -19
  22. package/plugins/lib/index.ts +14 -0
  23. package/plugins/lib/navigation-controller.ts +1 -1
  24. package/plugins/lib/panel-manager.ts +7 -13
  25. package/plugins/lib/results-panel.ts +914 -0
  26. package/plugins/lib/search-utils.ts +343 -0
  27. package/plugins/lib/virtual-buffer-factory.ts +3 -2
  28. package/plugins/live_grep.ts +56 -379
  29. package/plugins/markdown_compose.ts +1 -17
  30. package/plugins/merge_conflict.ts +16 -14
  31. package/plugins/path_complete.ts +1 -1
  32. package/plugins/search_replace.i18n.json +13 -13
  33. package/plugins/search_replace.ts +11 -9
  34. package/plugins/theme_editor.ts +57 -30
  35. package/plugins/todo_highlighter.ts +1 -0
  36. package/plugins/vi_mode.ts +9 -5
  37. package/plugins/welcome.ts +1 -1
  38. package/themes/dark.json +102 -0
  39. package/themes/high-contrast.json +102 -0
  40. package/themes/light.json +102 -0
  41. package/themes/nostalgia.json +102 -0
@@ -1,359 +1,117 @@
1
- /// <reference path="../types/fresh.d.ts" />
2
- const editor = getEditor();
3
-
1
+ /// <reference path="./lib/fresh.d.ts" />
4
2
 
5
3
  /**
6
- * Find References Plugin (TypeScript)
4
+ * Find References Plugin
7
5
  *
8
- * Displays LSP find references results in a virtual buffer split view.
9
- * Listens for lsp_references hook from the editor and shows results.
10
- * Uses cursor movement for navigation (Up/Down/j/k work naturally).
6
+ * Displays LSP find references results using the Finder abstraction
7
+ * with filter mode for unified prompt-based UX.
11
8
  */
12
9
 
13
- // Panel state
14
- let panelOpen = false;
15
- let referencesBufferId: number | null = null;
16
- let sourceSplitId: number | null = null;
17
- let referencesSplitId: number | null = null; // Track the split we created
18
- let currentReferences: ReferenceItem[] = [];
19
- let currentSymbol: string = "";
20
- let lineCache: Map<string, string[]> = new Map(); // Cache file contents
10
+ import { Finder, getRelativePath } from "./lib/finder.ts";
21
11
 
22
- // Maximum number of results to display
23
- const MAX_RESULTS = 100;
12
+ const editor = getEditor();
24
13
 
25
- // Reference item structure
26
- interface ReferenceItem {
14
+ // Reference location from LSP
15
+ interface ReferenceLocation {
27
16
  file: string;
28
17
  line: number;
29
18
  column: number;
30
- lineText?: string; // Cached line text
31
- }
32
-
33
- // Define the references mode with minimal keybindings
34
- // Navigation uses normal cursor movement (arrows, j/k work naturally)
35
- editor.defineMode(
36
- "references-list",
37
- null, // no parent mode
38
- [
39
- ["Return", "references_goto"],
40
- ["q", "references_close"],
41
- ["Escape", "references_close"],
42
- ],
43
- true // read-only
44
- );
45
-
46
- // Get relative path for display
47
- function getRelativePath(filePath: string): string {
48
- const cwd = editor.getCwd();
49
- if (filePath.startsWith(cwd)) {
50
- return filePath.slice(cwd.length + 1); // Remove cwd and leading /
51
- }
52
- return filePath;
53
- }
54
-
55
- // Format a reference for display with line preview
56
- function formatReference(item: ReferenceItem): string {
57
- const displayPath = getRelativePath(item.file);
58
- const location = `${displayPath}:${item.line}:${item.column}`;
59
-
60
- // Truncate location if too long, leaving room for line text
61
- const maxLocationLen = 50;
62
- const truncatedLocation = location.length > maxLocationLen
63
- ? "..." + location.slice(-(maxLocationLen - 3))
64
- : location.padEnd(maxLocationLen);
65
-
66
- // Get line text preview (truncated)
67
- const lineText = item.lineText || "";
68
- const trimmedLine = lineText.trim();
69
- const maxLineLen = 60;
70
- const displayLine = trimmedLine.length > maxLineLen
71
- ? trimmedLine.slice(0, maxLineLen - 3) + "..."
72
- : trimmedLine;
73
-
74
- return ` ${truncatedLocation} ${displayLine}\n`;
19
+ content?: string;
75
20
  }
76
21
 
77
- // Build entries for the virtual buffer
78
- function buildPanelEntries(): TextPropertyEntry[] {
79
- const entries: TextPropertyEntry[] = [];
80
-
81
- // Header with symbol name
82
- const totalCount = currentReferences.length;
83
- const limitNote = totalCount >= MAX_RESULTS ? editor.t("panel.limited", { max: String(MAX_RESULTS) }) : "";
84
- const symbolDisplay = currentSymbol ? `'${currentSymbol}'` : "symbol";
85
- entries.push({
86
- text: `═══ ${editor.t("panel.header", { symbol: symbolDisplay, count: String(totalCount), limit: limitNote })} ═══\n`,
87
- properties: { type: "header" },
88
- });
89
-
90
- if (currentReferences.length === 0) {
91
- entries.push({
92
- text: " " + editor.t("panel.no_references") + "\n",
93
- properties: { type: "empty" },
94
- });
95
- } else {
96
- // Add each reference
97
- for (let i = 0; i < currentReferences.length; i++) {
98
- const ref = currentReferences[i];
99
- entries.push({
100
- text: formatReference(ref),
101
- properties: {
102
- type: "reference",
103
- index: i,
104
- location: {
105
- file: ref.file,
106
- line: ref.line,
107
- column: ref.column,
108
- },
109
- },
110
- });
111
- }
112
- }
113
-
114
- // Footer
115
- entries.push({
116
- text: `───────────────────────────────────────────────────────────────────────────────\n`,
117
- properties: { type: "separator" },
118
- });
119
- entries.push({
120
- text: editor.t("panel.help") + "\n",
121
- properties: { type: "help" },
122
- });
22
+ // Create the finder instance - same UX as grep plugins
23
+ const finder = new Finder<ReferenceLocation>(editor, {
24
+ id: "references",
25
+ format: (ref) => {
26
+ const displayPath = getRelativePath(editor, ref.file);
27
+ const content = ref.content?.trim() ?? "";
28
+ const description =
29
+ content.length > 60 ? content.substring(0, 57) + "..." : content;
30
+
31
+ return {
32
+ label: `${displayPath}:${ref.line}`,
33
+ description,
34
+ location: {
35
+ file: ref.file,
36
+ line: ref.line,
37
+ column: ref.column,
38
+ },
39
+ };
40
+ },
41
+ preview: true,
42
+ maxResults: 100,
43
+ });
44
+
45
+ // Pending references for the current prompt
46
+ let pendingRefs: ReferenceLocation[] = [];
123
47
 
124
- return entries;
125
- }
126
-
127
- // Load line text for references
128
- async function loadLineTexts(references: ReferenceItem[]): Promise<void> {
129
- // Group references by file
130
- const fileRefs: Map<string, ReferenceItem[]> = new Map();
131
- for (const ref of references) {
132
- if (!fileRefs.has(ref.file)) {
133
- fileRefs.set(ref.file, []);
134
- }
135
- fileRefs.get(ref.file)!.push(ref);
136
- }
137
-
138
- // Load each file and extract lines
139
- for (const [filePath, refs] of fileRefs) {
140
- try {
141
- // Check cache first
142
- let lines = lineCache.get(filePath);
143
- if (!lines) {
144
- const content = await editor.readFile(filePath);
48
+ /**
49
+ * Load line content for references
50
+ */
51
+ async function loadLineContent(
52
+ refs: ReferenceLocation[]
53
+ ): Promise<ReferenceLocation[]> {
54
+ const result: ReferenceLocation[] = [];
55
+ const fileCache = new Map<string, string[]>();
56
+
57
+ for (const ref of refs) {
58
+ let lines: string[];
59
+ const cached = fileCache.get(ref.file);
60
+ if (cached !== undefined) {
61
+ lines = cached;
62
+ } else {
63
+ try {
64
+ const content = await editor.readFile(ref.file);
145
65
  lines = content.split("\n");
146
- lineCache.set(filePath, lines);
147
- }
148
-
149
- // Set line text for each reference
150
- for (const ref of refs) {
151
- const lineIndex = ref.line - 1; // Convert 1-based to 0-based
152
- if (lineIndex >= 0 && lineIndex < lines.length) {
153
- ref.lineText = lines[lineIndex];
154
- } else {
155
- ref.lineText = "";
156
- }
157
- }
158
- } catch (error) {
159
- // If file can't be read, leave lineText empty
160
- for (const ref of refs) {
161
- ref.lineText = "";
66
+ } catch {
67
+ lines = [];
162
68
  }
69
+ fileCache.set(ref.file, lines);
163
70
  }
164
- }
165
- }
166
-
167
- // Show references panel
168
- async function showReferencesPanel(symbol: string, references: ReferenceItem[]): Promise<void> {
169
- // Only save the source split ID if panel is not already open
170
- // (avoid overwriting it with the references split ID on subsequent calls)
171
- if (!panelOpen) {
172
- sourceSplitId = editor.getActiveSplitId();
173
- }
174
-
175
- // Limit results
176
- const limitedRefs = references.slice(0, MAX_RESULTS);
177
-
178
- // Set references and symbol
179
- currentSymbol = symbol;
180
- currentReferences = limitedRefs;
181
-
182
- // Load line texts for preview
183
- await loadLineTexts(currentReferences);
184
-
185
- // Build panel entries
186
- const entries = buildPanelEntries();
187
71
 
188
- // Create or update virtual buffer in horizontal split
189
- // The panel_id mechanism will reuse the existing buffer/split if it exists
190
- try {
191
- referencesBufferId = await editor.createVirtualBufferInSplit({
192
- name: "*References*",
193
- mode: "references-list",
194
- read_only: true,
195
- entries: entries,
196
- ratio: 0.7, // Original pane takes 70%, references takes 30%
197
- panel_id: "references-panel",
198
- show_line_numbers: false,
199
- show_cursors: true, // Enable cursor for navigation
200
- });
72
+ const lineIndex = ref.line - 1;
73
+ const lineContent =
74
+ lineIndex >= 0 && lineIndex < lines.length ? lines[lineIndex] : "";
201
75
 
202
- panelOpen = true;
203
- // Track the references split (it becomes active after creation)
204
- referencesSplitId = editor.getActiveSplitId();
205
-
206
- const limitMsg = references.length > MAX_RESULTS
207
- ? editor.t("status.showing_first", { max: String(MAX_RESULTS) })
208
- : "";
209
- editor.setStatus(
210
- editor.t("status.found_references", { count: String(references.length), limit: limitMsg })
211
- );
212
- editor.debug(`References panel opened with buffer ID ${referencesBufferId}, split ID ${referencesSplitId}`);
213
- } catch (error) {
214
- const errorMessage = error instanceof Error ? error.message : String(error);
215
- editor.setStatus(editor.t("status.failed_open_panel"));
216
- editor.debug(`ERROR: createVirtualBufferInSplit failed: ${errorMessage}`);
76
+ result.push({ ...ref, content: lineContent });
217
77
  }
78
+
79
+ return result;
218
80
  }
219
81
 
220
82
  // Handle lsp_references hook
221
- globalThis.on_lsp_references = function (data: { symbol: string; locations: ReferenceItem[] }): void {
222
- editor.debug(`Received ${data.locations.length} references for '${data.symbol}'`);
83
+ globalThis.on_lsp_references = async function (data: {
84
+ symbol: string;
85
+ locations: ReferenceLocation[];
86
+ }): Promise<void> {
87
+ editor.debug(
88
+ `Received ${data.locations.length} references for '${data.symbol}'`
89
+ );
223
90
 
224
91
  if (data.locations.length === 0) {
225
- editor.setStatus(editor.t("status.no_references", { symbol: data.symbol }));
92
+ editor.setStatus(`No references found for '${data.symbol}'`);
226
93
  return;
227
94
  }
228
95
 
229
- // Clear line cache for fresh results
230
- lineCache.clear();
96
+ // Load line content for descriptions
97
+ pendingRefs = await loadLineContent(data.locations);
231
98
 
232
- // Show the references panel
233
- showReferencesPanel(data.symbol, data.locations);
99
+ // Use prompt mode with filter source - same UX as grep plugins
100
+ finder.prompt({
101
+ title: `References to '${data.symbol}' (${data.locations.length})`,
102
+ source: {
103
+ mode: "filter",
104
+ load: async () => pendingRefs,
105
+ },
106
+ });
234
107
  };
235
108
 
236
109
  // Register the hook handler
237
110
  editor.on("lsp_references", "on_lsp_references");
238
111
 
239
- // Handle cursor movement to show current reference info
240
- globalThis.on_references_cursor_moved = function (data: {
241
- buffer_id: number;
242
- cursor_id: number;
243
- old_position: number;
244
- new_position: number;
245
- }): void {
246
- // Only handle cursor movement in our references buffer
247
- if (referencesBufferId === null || data.buffer_id !== referencesBufferId) {
248
- return;
249
- }
250
-
251
- // Get cursor line to determine which reference is selected
252
- // getCursorLine() returns the line for the active buffer
253
- const cursorLine = editor.getCursorLine();
254
-
255
- // Line 0 is header, lines 1 to N are references
256
- const refIndex = cursorLine - 1;
257
-
258
- if (refIndex >= 0 && refIndex < currentReferences.length) {
259
- editor.setStatus(editor.t("status.reference_index", { current: String(refIndex + 1), total: String(currentReferences.length) }));
260
- }
261
- };
262
-
263
- // Register cursor movement handler
264
- editor.on("cursor_moved", "on_references_cursor_moved");
265
-
266
- // Hide references panel
267
- globalThis.hide_references_panel = function (): void {
268
- if (!panelOpen) {
269
- return;
270
- }
271
-
272
- if (referencesBufferId !== null) {
273
- editor.closeBuffer(referencesBufferId);
274
- }
275
-
276
- // Close the split we created (if it exists and is different from source)
277
- if (referencesSplitId !== null && referencesSplitId !== sourceSplitId) {
278
- editor.closeSplit(referencesSplitId);
279
- }
280
-
281
- panelOpen = false;
282
- referencesBufferId = null;
283
- sourceSplitId = null;
284
- referencesSplitId = null;
285
- currentReferences = [];
286
- currentSymbol = "";
287
- lineCache.clear();
288
- editor.setStatus(editor.t("status.closed"));
112
+ // Close function for command palette
113
+ globalThis.close_references = function (): void {
114
+ finder.close();
289
115
  };
290
116
 
291
- // Navigation: go to selected reference (based on cursor position)
292
- globalThis.references_goto = function (): void {
293
- if (currentReferences.length === 0) {
294
- editor.setStatus(editor.t("status.no_references_to_jump"));
295
- return;
296
- }
297
-
298
- if (sourceSplitId === null) {
299
- editor.setStatus(editor.t("status.source_split_unavailable"));
300
- return;
301
- }
302
-
303
- if (referencesBufferId === null) {
304
- return;
305
- }
306
-
307
- // Get text properties at cursor position
308
- const props = editor.getTextPropertiesAtCursor(referencesBufferId);
309
- editor.debug(`references_goto: props.length=${props.length}, referencesBufferId=${referencesBufferId}, sourceSplitId=${sourceSplitId}`);
310
-
311
- if (props.length > 0) {
312
- editor.debug(`references_goto: props[0]=${JSON.stringify(props[0])}`);
313
- const location = props[0].location as
314
- | { file: string; line: number; column: number }
315
- | undefined;
316
- if (location) {
317
- editor.debug(`references_goto: opening ${location.file}:${location.line}:${location.column} in split ${sourceSplitId}`);
318
- // Open file in the source split, not the references split
319
- editor.openFileInSplit(
320
- sourceSplitId,
321
- location.file,
322
- location.line,
323
- location.column || 0
324
- );
325
- const displayPath = getRelativePath(location.file);
326
- editor.setStatus(editor.t("status.jumped_to", { file: displayPath, line: String(location.line) }));
327
- } else {
328
- editor.debug(`references_goto: no location in props[0]`);
329
- editor.setStatus(editor.t("status.move_cursor"));
330
- }
331
- } else {
332
- editor.debug(`references_goto: no props found at cursor`);
333
- editor.setStatus(editor.t("status.move_cursor"));
334
- }
335
- };
336
-
337
- // Close the references panel
338
- globalThis.references_close = function (): void {
339
- globalThis.hide_references_panel();
340
- };
341
-
342
- // Register commands
343
- editor.registerCommand(
344
- "%cmd.show_references",
345
- "%cmd.show_references_desc",
346
- "show_references_panel",
347
- "normal"
348
- );
349
-
350
- editor.registerCommand(
351
- "%cmd.hide_references",
352
- "%cmd.hide_references_desc",
353
- "hide_references_panel",
354
- "normal"
355
- );
356
-
357
- // Plugin initialization
358
- editor.setStatus(editor.t("status.ready"));
359
- editor.debug("Find References plugin initialized");
117
+ editor.debug("Find References plugin loaded (using Finder abstraction)");