@fresh-editor/fresh-editor 0.1.75 → 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 (37) hide show
  1. package/CHANGELOG.md +26 -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 +93 -17
  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 +15 -9
  35. package/plugins/todo_highlighter.ts +1 -0
  36. package/plugins/vi_mode.ts +9 -5
  37. package/plugins/welcome.ts +1 -1
@@ -1,125 +1,55 @@
1
1
  /// <reference path="./lib/fresh.d.ts" />
2
- const editor = getEditor();
3
-
4
2
 
5
3
  /**
6
4
  * Diagnostics Panel Plugin
7
5
  *
8
- * Interactive diagnostics panel showing LSP diagnostics with:
9
- * - Real-time updates when diagnostics change
10
- * - Filter by current file or show all files
11
- * - Cursor navigation with highlighting
12
- * - Enter to jump to diagnostic location
6
+ * Uses the Finder abstraction with livePanel mode for reactive diagnostics display.
7
+ * Supports toggling between current file and all files.
8
+ *
9
+ * Key features:
10
+ * - livePanel mode for reactive data updates
11
+ * - Toggle between current file and all files (press 'a')
12
+ * - groupBy: "file" for organized display
13
+ * - syncWithEditor for bidirectional cursor sync
13
14
  */
14
15
 
15
- // =============================================================================
16
- // Types and Interfaces
17
- // =============================================================================
16
+ import { Finder, createLiveProvider, getRelativePath, type FinderProvider } from "./lib/finder.ts";
17
+
18
+ const editor = getEditor();
18
19
 
19
- interface DiagnosticLocation {
20
+ // Diagnostic item with severity
21
+ interface DiagnosticItem {
22
+ uri: string;
20
23
  file: string;
21
24
  line: number;
22
25
  column: number;
26
+ message: string;
27
+ severity: number; // 1=error, 2=warning, 3=info, 4=hint
28
+ source?: string;
23
29
  }
24
30
 
25
- interface DiagnosticLineMapping {
26
- panelLine: number; // 1-based line in panel
27
- location: DiagnosticLocation;
28
- }
29
-
30
- interface DiagnosticsState {
31
- isOpen: boolean;
32
- bufferId: number | null;
33
- splitId: number | null;
34
- sourceSplitId: number | null; // The split that was active when panel opened
35
- sourceBufferId: number | null;
36
- showAllFiles: boolean;
37
- cachedContent: string;
38
- // Maps panel line numbers to diagnostic locations for sync
39
- lineMappings: DiagnosticLineMapping[];
40
- // Current cursor line in the panel (1-indexed)
41
- panelCursorLine: number;
42
- }
43
-
44
- // =============================================================================
45
- // State Management
46
- // =============================================================================
47
-
48
- const state: DiagnosticsState = {
49
- isOpen: false,
50
- bufferId: null,
51
- splitId: null,
52
- sourceSplitId: null,
53
- sourceBufferId: null,
54
- showAllFiles: false, // Default to filtering by current file
55
- cachedContent: "",
56
- lineMappings: [],
57
- panelCursorLine: 1,
58
- };
59
-
60
- // =============================================================================
61
- // Color Definitions
62
- // =============================================================================
63
-
64
- const colors = {
65
- error: [255, 100, 100] as [number, number, number],
66
- warning: [255, 200, 100] as [number, number, number],
67
- info: [100, 200, 255] as [number, number, number],
68
- hint: [150, 150, 150] as [number, number, number],
69
- file: [180, 180, 255] as [number, number, number],
70
- location: [150, 255, 150] as [number, number, number],
71
- header: [255, 200, 100] as [number, number, number],
72
- selected: [80, 80, 120] as [number, number, number],
73
- };
74
-
75
- // =============================================================================
76
- // Keybindings
77
- // =============================================================================
78
-
79
- const keybindings = {
80
- goto: "Enter",
81
- gotoAlt: "Tab",
82
- toggleAll: "a",
83
- refresh: "r",
84
- close: "q",
85
- closeAlt: "Escape",
86
- // These are global keybindings, not part of the mode
87
- nextDiag: "F8",
88
- prevDiag: "Shift+F8",
89
- };
90
-
91
- // =============================================================================
92
- // Mode Definition
93
- // =============================================================================
31
+ // State
32
+ let showAllFiles = false;
33
+ let sourceBufferId: number | null = null;
34
+ let isOpen = false;
94
35
 
95
- editor.defineMode(
96
- "diagnostics-list",
97
- "normal",
98
- [
99
- ["Return", "diagnostics_goto"],
100
- [keybindings.gotoAlt, "diagnostics_goto"],
101
- [keybindings.toggleAll, "diagnostics_toggle_all"],
102
- [keybindings.refresh, "diagnostics_refresh"],
103
- [keybindings.close, "diagnostics_close"],
104
- [keybindings.closeAlt, "diagnostics_close"],
105
- ],
106
- true
107
- );
108
-
109
- // =============================================================================
110
- // Helpers
111
- // =============================================================================
112
-
113
- function severityIcon(severity: number): string {
36
+ // Convert severity number to string
37
+ function severityToString(severity: number): "error" | "warning" | "info" | "hint" {
114
38
  switch (severity) {
115
- case 1: return "[E]";
116
- case 2: return "[W]";
117
- case 3: return "[I]";
118
- case 4: return "[H]";
119
- default: return "[?]";
39
+ case 1:
40
+ return "error";
41
+ case 2:
42
+ return "warning";
43
+ case 3:
44
+ return "info";
45
+ case 4:
46
+ return "hint";
47
+ default:
48
+ return "info";
120
49
  }
121
50
  }
122
51
 
52
+ // Convert URI to file path
123
53
  function uriToPath(uri: string): string {
124
54
  if (uri.startsWith("file://")) {
125
55
  return uri.slice(7);
@@ -127,499 +57,200 @@ function uriToPath(uri: string): string {
127
57
  return uri;
128
58
  }
129
59
 
130
- function getActiveFileUri(): string | null {
131
- const bufferId = state.sourceBufferId ?? editor.getActiveBufferId();
132
- const path = editor.getBufferPath(bufferId);
133
- if (!path) return null;
134
- return "file://" + path;
135
- }
136
-
137
- function entriesToContent(entries: TextPropertyEntry[]): string {
138
- return entries.map(e => e.text).join("");
139
- }
140
-
141
- // =============================================================================
142
- // Panel Content Building
143
- // =============================================================================
144
-
145
- function buildPanelEntries(): TextPropertyEntry[] {
146
- const entries: TextPropertyEntry[] = [];
60
+ // Get diagnostics based on current filter
61
+ function getDiagnostics(): DiagnosticItem[] {
147
62
  const diagnostics = editor.getAllDiagnostics();
148
63
 
149
- // Clear and rebuild line mappings
150
- state.lineMappings = [];
151
-
152
- const activeUri = getActiveFileUri();
153
- const filterUri = state.showAllFiles ? null : activeUri;
64
+ // Get active file URI for filtering
65
+ let activeUri: string | null = null;
66
+ if (sourceBufferId !== null) {
67
+ const path = editor.getBufferPath(sourceBufferId);
68
+ if (path) {
69
+ activeUri = "file://" + path;
70
+ }
71
+ }
154
72
 
155
73
  // Filter diagnostics
74
+ const filterUri = showAllFiles ? null : activeUri;
156
75
  const filtered = filterUri
157
- ? diagnostics.filter(d => d.uri === filterUri)
76
+ ? diagnostics.filter((d) => d.uri === filterUri)
158
77
  : diagnostics;
159
78
 
160
- // Group by file
161
- const byFile = new Map<string, TsDiagnostic[]>();
162
- for (const diag of filtered) {
163
- const existing = byFile.get(diag.uri) || [];
164
- existing.push(diag);
165
- byFile.set(diag.uri, existing);
166
- }
167
-
168
- // Sort files, with active file first if filtering
169
- const files = Array.from(byFile.keys()).sort((a, b) => {
170
- if (activeUri) {
171
- if (a === activeUri) return -1;
172
- if (b === activeUri) return 1;
173
- }
174
- // Simple string comparison (localeCompare has ICU issues in Deno)
175
- if (a < b) return -1;
176
- if (a > b) return 1;
177
- return 0;
178
- });
179
-
180
- // Help line (line 1)
181
- const helpText = `${keybindings.goto}:goto ${keybindings.close}:close ${keybindings.toggleAll}:toggle all ${keybindings.refresh}:refresh ${keybindings.nextDiag}/${keybindings.prevDiag}:next/prev\n`;
182
- entries.push({
183
- text: helpText,
184
- properties: { type: "help" },
185
- });
186
-
187
- // Header (line 2)
188
- let filterLabel: string;
189
- if (state.showAllFiles) {
190
- filterLabel = editor.t("panel.all_files");
191
- } else if (activeUri) {
192
- const fileName = editor.pathBasename(uriToPath(activeUri));
193
- filterLabel = fileName;
194
- } else {
195
- filterLabel = editor.t("panel.current_file");
196
- }
197
- entries.push({
198
- text: editor.t("panel.header", { filter: filterLabel }) + "\n",
199
- properties: { type: "header" },
200
- });
201
-
202
- let currentPanelLine = 3; // Start after help + header
203
-
204
- if (filtered.length === 0) {
205
- entries.push({
206
- text: " " + editor.t("panel.no_diagnostics") + "\n",
207
- properties: { type: "empty" },
208
- });
209
- currentPanelLine++;
210
- } else {
211
- let diagIndex = 0;
212
- for (const uri of files) {
213
- const fileDiags = byFile.get(uri) || [];
214
- const filePath = uriToPath(uri);
215
- const fileName = editor.pathBasename(filePath);
216
-
217
- // File header (blank line + filename)
218
- entries.push({
219
- text: `\n${fileName}:\n`,
220
- properties: { type: "file-header", uri },
221
- });
222
- currentPanelLine += 2; // blank line + file header
223
-
224
- // Sort diagnostics by line, then severity
225
- fileDiags.sort((a, b) => {
226
- const lineDiff = a.range.start.line - b.range.start.line;
227
- if (lineDiff !== 0) return lineDiff;
228
- return a.severity - b.severity;
229
- });
230
-
231
- for (const diag of fileDiags) {
232
- const icon = severityIcon(diag.severity);
233
- const line = diag.range.start.line + 1;
234
- const col = diag.range.start.character + 1;
235
- const msg = diag.message.split("\n")[0]; // First line only
236
-
237
- const location: DiagnosticLocation = {
238
- file: filePath,
239
- line: line,
240
- column: col,
241
- };
242
-
243
- // Track mapping for cursor sync
244
- state.lineMappings.push({
245
- panelLine: currentPanelLine,
246
- location: location,
247
- });
248
-
249
- entries.push({
250
- text: ` ${icon} ${line}:${col} ${msg}\n`,
251
- properties: {
252
- type: "diagnostic",
253
- index: diagIndex,
254
- severity: diag.severity,
255
- location: location,
256
- },
257
- });
258
- diagIndex++;
259
- currentPanelLine++;
79
+ // Sort by file, then line, then severity
80
+ filtered.sort((a, b) => {
81
+ // File comparison
82
+ if (a.uri !== b.uri) {
83
+ // Active file first
84
+ if (activeUri) {
85
+ if (a.uri === activeUri) return -1;
86
+ if (b.uri === activeUri) return 1;
260
87
  }
88
+ return a.uri < b.uri ? -1 : 1;
261
89
  }
262
- }
263
-
264
- // Summary
265
- const errorCount = filtered.filter(d => d.severity === 1).length;
266
- const warningCount = filtered.filter(d => d.severity === 2).length;
267
- const infoCount = filtered.filter(d => d.severity === 3).length;
268
-
269
- entries.push({
270
- text: "\n",
271
- properties: { type: "blank" },
272
- });
273
- entries.push({
274
- text: `${errorCount}E ${warningCount}W ${infoCount}I | a: toggle filter | r: refresh | RET: goto | q: close\n`,
275
- properties: { type: "footer" },
90
+ // Line comparison
91
+ const lineDiff = a.range.start.line - b.range.start.line;
92
+ if (lineDiff !== 0) return lineDiff;
93
+ // Severity comparison
94
+ return a.severity - b.severity;
276
95
  });
277
96
 
278
- return entries;
97
+ // Convert to DiagnosticItem
98
+ return filtered.map((diag) => ({
99
+ uri: diag.uri,
100
+ file: uriToPath(diag.uri),
101
+ line: diag.range.start.line + 1,
102
+ column: diag.range.start.character + 1,
103
+ message: diag.message.split("\n")[0], // First line only
104
+ severity: diag.severity,
105
+ source: diag.source ?? undefined,
106
+ }));
279
107
  }
280
108
 
281
- // =============================================================================
282
- // Highlighting
283
- // =============================================================================
284
-
285
- function applyHighlighting(): void {
286
- if (state.bufferId === null) return;
287
-
288
- const bufferId = state.bufferId;
289
- editor.clearNamespace(bufferId, "diag");
290
-
291
- const content = state.cachedContent;
292
- if (!content) return;
293
-
294
- const lines = content.split("\n");
295
- const cursorLine = state.panelCursorLine;
296
-
297
- let byteOffset = 0;
298
-
299
- for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
300
- const line = lines[lineIdx];
301
- const lineStart = byteOffset;
302
- const lineEnd = byteOffset + line.length;
303
- const isCurrentLine = (lineIdx + 1) === cursorLine;
304
- const isDiagnosticLine = line.trim().startsWith("[");
305
-
306
- // Highlight current line if it's a diagnostic line (entire line gets background)
307
- if (isCurrentLine && isDiagnosticLine) {
308
- editor.addOverlay(
309
- bufferId, "diag", lineStart, lineEnd,
310
- colors.selected[0], colors.selected[1], colors.selected[2],
311
- true, true, false
312
- );
313
- }
314
-
315
- // Help line highlighting (dimmed)
316
- if (line.startsWith("Enter:")) {
317
- editor.addOverlay(
318
- bufferId, "diag", lineStart, lineEnd,
319
- colors.hint[0], colors.hint[1], colors.hint[2],
320
- false, true, false
321
- );
322
- }
323
-
324
- // Header highlighting
325
- if (line.startsWith("Diagnostics")) {
326
- editor.addOverlay(
327
- bufferId, "diag", lineStart, lineEnd,
328
- colors.header[0], colors.header[1], colors.header[2],
329
- true, true, false
330
- );
331
- }
332
-
333
- // File header highlighting
334
- if (line.endsWith(":") && !line.startsWith("Diagnostics") && !line.startsWith(" ")) {
335
- editor.addOverlay(
336
- bufferId, "diag", lineStart, lineEnd,
337
- colors.file[0], colors.file[1], colors.file[2],
338
- false, true, false
339
- );
340
- }
341
-
342
- // Severity icon highlighting
343
- const iconMatch = line.match(/^\s+\[([EWIH?])\]/);
344
- if (iconMatch) {
345
- const iconStart = lineStart + line.indexOf("[");
346
- const iconEnd = iconStart + 3;
347
-
348
- let color: [number, number, number];
349
- switch (iconMatch[1]) {
350
- case "E": color = colors.error; break;
351
- case "W": color = colors.warning; break;
352
- case "I": color = colors.info; break;
353
- case "H": color = colors.hint; break;
354
- default: color = colors.hint;
355
- }
356
-
357
- editor.addOverlay(
358
- bufferId, "diag", iconStart, iconEnd,
359
- color[0], color[1], color[2],
360
- false, true, false
361
- );
362
-
363
- // Location highlighting (line:col after icon)
364
- const locMatch = line.match(/\[.\]\s+(\d+:\d+)/);
365
- if (locMatch && locMatch.index !== undefined) {
366
- const locStart = lineStart + line.indexOf(locMatch[1]);
367
- const locEnd = locStart + locMatch[1].length;
368
- editor.addOverlay(
369
- bufferId, "diag", locStart, locEnd,
370
- colors.location[0], colors.location[1], colors.location[2],
371
- false, false, false
372
- );
373
- }
374
- }
375
-
376
- byteOffset += line.length + 1;
377
- }
378
- }
379
-
380
- function updatePanel(): void {
381
- if (state.bufferId === null) return;
382
-
383
- const entries = buildPanelEntries();
384
- state.cachedContent = entriesToContent(entries);
385
- editor.setVirtualBufferContent(state.bufferId, entries);
386
- applyHighlighting();
109
+ // Create the live provider
110
+ const provider = createLiveProvider(getDiagnostics);
111
+
112
+ // Create the finder instance
113
+ const finder = new Finder<DiagnosticItem>(editor, {
114
+ id: "diagnostics",
115
+ format: (d) => ({
116
+ label: `${d.line}:${d.column} ${d.message}`,
117
+ location: {
118
+ file: d.file,
119
+ line: d.line,
120
+ column: d.column,
121
+ },
122
+ severity: severityToString(d.severity),
123
+ metadata: { uri: d.uri, message: d.message },
124
+ }),
125
+ groupBy: "file",
126
+ syncWithEditor: true,
127
+ onSelect: (d) => {
128
+ const displayPath = getRelativePath(editor, d.file);
129
+ editor.setStatus(
130
+ editor.t("status.jumped_to", {
131
+ file: displayPath,
132
+ line: String(d.line),
133
+ })
134
+ );
135
+ },
136
+ });
137
+
138
+ // Get title based on current filter state
139
+ function getTitle(): string {
140
+ const filterLabel = showAllFiles
141
+ ? editor.t("panel.all_files")
142
+ : editor.t("panel.current_file");
143
+ return editor.t("panel.header", { filter: filterLabel });
387
144
  }
388
145
 
389
- // =============================================================================
390
146
  // Commands
391
- // =============================================================================
392
-
393
- globalThis.show_diagnostics_panel = async function(): Promise<void> {
394
- if (state.isOpen) {
395
- // If already open, just focus the panel
396
- if (state.splitId !== null) {
397
- editor.focusSplit(state.splitId);
398
- }
147
+ globalThis.show_diagnostics_panel = async function (): Promise<void> {
148
+ if (isOpen) {
149
+ // Already open - just notify to refresh
150
+ provider.notify();
399
151
  return;
400
152
  }
401
153
 
402
- state.sourceSplitId = editor.getActiveSplitId();
403
- state.sourceBufferId = editor.getActiveBufferId();
404
-
405
- const entries = buildPanelEntries();
406
- state.cachedContent = entriesToContent(entries);
407
-
408
- // Create a horizontal split below the current buffer
409
- const result = await editor.createVirtualBufferInSplit({
410
- name: "*Diagnostics*",
411
- mode: "diagnostics-list",
412
- read_only: true,
413
- entries: entries,
414
- ratio: 0.7, // Source keeps 70%, panel takes 30%
415
- direction: "horizontal", // Split below
416
- panel_id: "diagnostics", // Enable idempotent updates
417
- show_line_numbers: false,
418
- show_cursors: true,
419
- editing_disabled: true,
154
+ // Capture source context
155
+ sourceBufferId = editor.getActiveBufferId();
156
+
157
+ // Show the panel
158
+ await finder.livePanel({
159
+ title: getTitle(),
160
+ provider: provider as FinderProvider<DiagnosticItem>,
161
+ ratio: 0.3,
420
162
  });
421
163
 
422
- if (result.buffer_id !== null) {
423
- state.isOpen = true;
424
- state.bufferId = result.buffer_id;
425
- state.splitId = result.split_id ?? null;
426
- applyHighlighting();
164
+ isOpen = true;
427
165
 
428
- const diagnostics = editor.getAllDiagnostics();
429
- editor.setStatus(editor.t("status.diagnostics_count", { count: String(diagnostics.length) }));
430
- } else {
431
- state.sourceSplitId = null;
432
- state.sourceBufferId = null;
433
- editor.setStatus(editor.t("status.failed_to_open"));
434
- }
166
+ // Show count
167
+ const diagnostics = editor.getAllDiagnostics();
168
+ editor.setStatus(
169
+ editor.t("status.diagnostics_count", { count: String(diagnostics.length) })
170
+ );
435
171
  };
436
172
 
437
- globalThis.diagnostics_close = function(): void {
438
- if (!state.isOpen) return;
439
-
440
- // Capture values before clearing state
441
- const splitId = state.splitId;
442
- const sourceSplitId = state.sourceSplitId;
443
- const sourceBufferId = state.sourceBufferId;
444
- const bufferId = state.bufferId;
445
-
446
- // Clear state FIRST to prevent event handlers from trying to update
447
- state.isOpen = false;
448
- state.bufferId = null;
449
- state.splitId = null;
450
- state.sourceSplitId = null;
451
- state.sourceBufferId = null;
452
- state.cachedContent = "";
453
-
454
- // Try to close the split first
455
- let splitClosed = false;
456
- if (splitId !== null) {
457
- splitClosed = editor.closeSplit(splitId);
458
- }
459
-
460
- // If split couldn't be closed (only split), switch buffer back to source
461
- if (!splitClosed && splitId !== null && sourceBufferId !== null) {
462
- editor.setSplitBuffer(splitId, sourceBufferId);
463
- }
464
-
465
- // Always delete the virtual buffer completely (removes from all splits)
466
- if (bufferId !== null) {
467
- editor.closeBuffer(bufferId);
468
- }
469
-
470
- // Focus back on the source split
471
- if (sourceSplitId !== null) {
472
- editor.focusSplit(sourceSplitId);
473
- }
474
-
173
+ globalThis.diagnostics_close = function (): void {
174
+ finder.close();
175
+ isOpen = false;
176
+ sourceBufferId = null;
475
177
  editor.setStatus(editor.t("status.closed"));
476
178
  };
477
179
 
478
- globalThis.diagnostics_goto = function(): void {
479
- if (!state.isOpen || state.bufferId === null) return;
480
-
481
- const props = editor.getTextPropertiesAtCursor(state.bufferId);
482
-
483
- if (props.length > 0) {
484
- const location = props[0].location as { file: string; line: number; column: number } | undefined;
485
- if (location) {
486
- const file = location.file;
487
- const line = location.line;
488
- const col = location.column;
489
-
490
- // Focus back on the source split and navigate to the location
491
- if (state.sourceSplitId !== null) {
492
- editor.focusSplit(state.sourceSplitId);
493
- }
494
- editor.openFile(file, line, col);
495
- editor.setStatus(editor.t("status.jumped_to", { file: editor.pathBasename(file), line: String(line) }));
496
- return;
497
- }
498
- }
499
-
500
- editor.setStatus(editor.t("status.move_to_diagnostic"));
501
- };
180
+ globalThis.diagnostics_toggle_all = function (): void {
181
+ if (!isOpen) return;
502
182
 
503
- globalThis.diagnostics_toggle_all = function(): void {
504
- if (!state.isOpen) return;
183
+ showAllFiles = !showAllFiles;
505
184
 
506
- state.showAllFiles = !state.showAllFiles;
507
- updatePanel();
185
+ // Update and refresh
186
+ finder.updateTitle(getTitle());
187
+ provider.notify();
508
188
 
509
- const label = state.showAllFiles ? editor.t("panel.all_files") : editor.t("panel.current_file");
189
+ const label = showAllFiles
190
+ ? editor.t("panel.all_files")
191
+ : editor.t("panel.current_file");
510
192
  editor.setStatus(editor.t("status.showing", { label }));
511
193
  };
512
194
 
513
- globalThis.diagnostics_refresh = function(): void {
514
- if (!state.isOpen) return;
195
+ globalThis.diagnostics_refresh = function (): void {
196
+ if (!isOpen) return;
515
197
 
516
- updatePanel();
198
+ provider.notify();
517
199
  editor.setStatus(editor.t("status.refreshed"));
518
200
  };
519
201
 
520
- globalThis.toggle_diagnostics_panel = function(): void {
521
- if (state.isOpen) {
202
+ globalThis.toggle_diagnostics_panel = function (): void {
203
+ if (isOpen) {
522
204
  globalThis.diagnostics_close();
523
205
  } else {
524
206
  globalThis.show_diagnostics_panel();
525
207
  }
526
208
  };
527
209
 
528
- // =============================================================================
529
210
  // Event Handlers
530
- // =============================================================================
531
-
532
- // Find the panel line that matches a source file location
533
- function findPanelLineForLocation(file: string, sourceLine: number): number | null {
534
- // Find the first diagnostic on this source line for this file
535
- for (const mapping of state.lineMappings) {
536
- if (mapping.location.file === file && mapping.location.line === sourceLine) {
537
- return mapping.panelLine;
538
- }
539
- }
540
- return null;
541
- }
542
-
543
- // Convert a 1-based line number to byte offset in the cached content
544
- function lineToByteOffset(lineNumber: number): number {
545
- const lines = state.cachedContent.split("\n");
546
- let offset = 0;
547
- for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
548
- offset += lines[i].length + 1; // +1 for newline
549
- }
550
- return offset;
551
- }
552
-
553
- // Sync the panel cursor to match a source location
554
- function syncPanelCursorToSourceLine(file: string, sourceLine: number): void {
555
- if (state.bufferId === null) return;
556
-
557
- const panelLine = findPanelLineForLocation(file, sourceLine);
558
- if (panelLine !== null) {
559
- // Convert panel line number to byte offset and move cursor
560
- const byteOffset = lineToByteOffset(panelLine);
561
- state.panelCursorLine = panelLine;
562
- editor.setBufferCursor(state.bufferId, byteOffset);
563
- applyHighlighting();
564
- }
565
- }
566
-
567
- globalThis.on_diagnostics_cursor_moved = function(data: {
568
- buffer_id: number;
569
- cursor_id: number;
570
- old_position: number;
571
- new_position: number;
572
- line: number;
573
- }): void {
574
- if (!state.isOpen || state.bufferId === null) return;
575
-
576
- // If cursor moved in the diagnostics panel, update the tracked line and highlighting
577
- if (data.buffer_id === state.bufferId) {
578
- state.panelCursorLine = data.line;
579
- applyHighlighting();
580
- return;
581
- }
582
211
 
583
- // Cursor moved in a non-panel buffer - sync the panel cursor to match
584
- // This handles F8/Shift+F8 jumps and normal cursor movement in source buffers
585
- const path = editor.getBufferPath(data.buffer_id);
586
- if (path) {
587
- syncPanelCursorToSourceLine(path, data.line);
588
- }
589
- };
590
-
591
- globalThis.on_diagnostics_updated = function(_data: {
212
+ // When diagnostics update, notify the provider
213
+ globalThis.on_diagnostics_updated = function (_data: {
592
214
  uri: string;
593
215
  count: number;
594
216
  }): void {
595
- if (!state.isOpen) return;
596
- updatePanel();
217
+ if (isOpen) {
218
+ provider.notify();
219
+ }
597
220
  };
598
221
 
599
- globalThis.on_diagnostics_buffer_activated = function(data: {
222
+ // When a different buffer becomes active, update filter context
223
+ globalThis.on_diagnostics_buffer_activated = function (data: {
600
224
  buffer_id: number;
601
225
  }): void {
602
- if (!state.isOpen) return;
226
+ if (!isOpen) return;
603
227
 
604
- // If the diagnostics panel itself became active, don't update source tracking
605
- if (data.buffer_id === state.bufferId) {
606
- return;
607
- }
228
+ // Update source buffer
229
+ sourceBufferId = data.buffer_id;
608
230
 
609
- // A different buffer became active - update source buffer and refresh the panel
610
- state.sourceBufferId = data.buffer_id;
611
- updatePanel();
231
+ // Refresh if not showing all files
232
+ if (!showAllFiles) {
233
+ provider.notify();
234
+ finder.updateTitle(getTitle());
235
+ }
612
236
  };
613
237
 
614
238
  // Register event handlers
615
- editor.on("cursor_moved", "on_diagnostics_cursor_moved");
616
239
  editor.on("diagnostics_updated", "on_diagnostics_updated");
617
240
  editor.on("buffer_activated", "on_diagnostics_buffer_activated");
618
241
 
619
- // =============================================================================
620
- // Command Registration
621
- // =============================================================================
242
+ // Mode Definition (for custom keybindings beyond Enter/Escape)
243
+ editor.defineMode(
244
+ "diagnostics-extra",
245
+ "diagnostics-results",
246
+ [
247
+ ["a", "diagnostics_toggle_all"],
248
+ ["r", "diagnostics_refresh"],
249
+ ],
250
+ true
251
+ );
622
252
 
253
+ // Command Registration
623
254
  editor.registerCommand(
624
255
  "%cmd.show_diagnostics_panel",
625
256
  "%cmd.show_diagnostics_panel_desc",
@@ -634,9 +265,6 @@ editor.registerCommand(
634
265
  "normal"
635
266
  );
636
267
 
637
- // =============================================================================
638
268
  // Initialization
639
- // =============================================================================
640
-
641
269
  editor.setStatus(editor.t("status.loaded"));
642
- editor.debug("Diagnostics Panel plugin initialized");
270
+ editor.debug("Diagnostics Panel plugin initialized (using Finder abstraction)");