@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.
- package/CHANGELOG.md +26 -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 +93 -17
- 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 +15 -9
- package/plugins/todo_highlighter.ts +1 -0
- package/plugins/vi_mode.ts +9 -5
- 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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
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
|
-
|
|
17
|
-
|
|
16
|
+
import { Finder, createLiveProvider, getRelativePath, type FinderProvider } from "./lib/finder.ts";
|
|
17
|
+
|
|
18
|
+
const editor = getEditor();
|
|
18
19
|
|
|
19
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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:
|
|
116
|
-
|
|
117
|
-
case
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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.
|
|
479
|
-
if (!
|
|
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
|
-
|
|
504
|
-
if (!state.isOpen) return;
|
|
183
|
+
showAllFiles = !showAllFiles;
|
|
505
184
|
|
|
506
|
-
|
|
507
|
-
|
|
185
|
+
// Update and refresh
|
|
186
|
+
finder.updateTitle(getTitle());
|
|
187
|
+
provider.notify();
|
|
508
188
|
|
|
509
|
-
const label =
|
|
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 (!
|
|
195
|
+
globalThis.diagnostics_refresh = function (): void {
|
|
196
|
+
if (!isOpen) return;
|
|
515
197
|
|
|
516
|
-
|
|
198
|
+
provider.notify();
|
|
517
199
|
editor.setStatus(editor.t("status.refreshed"));
|
|
518
200
|
};
|
|
519
201
|
|
|
520
|
-
globalThis.toggle_diagnostics_panel = function(): void {
|
|
521
|
-
if (
|
|
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
|
-
|
|
584
|
-
|
|
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 (
|
|
596
|
-
|
|
217
|
+
if (isOpen) {
|
|
218
|
+
provider.notify();
|
|
219
|
+
}
|
|
597
220
|
};
|
|
598
221
|
|
|
599
|
-
|
|
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 (!
|
|
226
|
+
if (!isOpen) return;
|
|
603
227
|
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
228
|
+
// Update source buffer
|
|
229
|
+
sourceBufferId = data.buffer_id;
|
|
608
230
|
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
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)");
|