@fresh-editor/fresh-editor 0.1.4
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/.gitignore +2 -0
- package/LICENSE +117 -0
- package/README.md +54 -0
- package/binary-install.js +212 -0
- package/binary.js +126 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +900 -0
- package/package.json +100 -0
- package/plugins/README.md +121 -0
- package/plugins/clangd_support.md +20 -0
- package/plugins/clangd_support.ts +323 -0
- package/plugins/color_highlighter.ts +302 -0
- package/plugins/diagnostics_panel.ts +308 -0
- package/plugins/examples/README.md +245 -0
- package/plugins/examples/async_demo.ts +165 -0
- package/plugins/examples/bookmarks.ts +329 -0
- package/plugins/examples/buffer_query_demo.ts +110 -0
- package/plugins/examples/git_grep.ts +262 -0
- package/plugins/examples/hello_world.ts +93 -0
- package/plugins/examples/virtual_buffer_demo.ts +116 -0
- package/plugins/find_references.ts +357 -0
- package/plugins/git_find_file.ts +298 -0
- package/plugins/git_grep.ts +188 -0
- package/plugins/git_log.ts +1283 -0
- package/plugins/lib/fresh.d.ts +849 -0
- package/plugins/lib/index.ts +24 -0
- package/plugins/lib/navigation-controller.ts +214 -0
- package/plugins/lib/panel-manager.ts +218 -0
- package/plugins/lib/types.ts +72 -0
- package/plugins/lib/virtual-buffer-factory.ts +158 -0
- package/plugins/manual_help.ts +243 -0
- package/plugins/markdown_compose.ts +1207 -0
- package/plugins/merge_conflict.ts +1811 -0
- package/plugins/path_complete.ts +163 -0
- package/plugins/search_replace.ts +481 -0
- package/plugins/todo_highlighter.ts +204 -0
- package/plugins/welcome.ts +74 -0
- package/run-fresh.js +4 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// TypeScript Color Highlighter Plugin
|
|
2
|
+
// Highlights color codes in source code with a colored swatch
|
|
3
|
+
// Supports: #RGB, #RRGGBB, rgb(), rgba(), hsl(), hsla(), Color::Rgb()
|
|
4
|
+
|
|
5
|
+
interface ColorHighlighterConfig {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Plugin configuration
|
|
10
|
+
const config: ColorHighlighterConfig = {
|
|
11
|
+
enabled: false, // Start disabled, use Enable or Toggle to activate
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Track which buffers need their virtual texts refreshed (content changed)
|
|
15
|
+
const dirtyBuffers = new Set<number>();
|
|
16
|
+
|
|
17
|
+
// Color block character for display
|
|
18
|
+
const COLOR_BLOCK = "█";
|
|
19
|
+
|
|
20
|
+
// Parse a hex color string to RGB
|
|
21
|
+
function parseHexColor(hex: string): [number, number, number] | null {
|
|
22
|
+
// Remove # prefix
|
|
23
|
+
hex = hex.replace(/^#/, "");
|
|
24
|
+
|
|
25
|
+
let r: number, g: number, b: number;
|
|
26
|
+
|
|
27
|
+
if (hex.length === 3) {
|
|
28
|
+
// #RGB format
|
|
29
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
30
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
31
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
32
|
+
} else if (hex.length === 6 || hex.length === 8) {
|
|
33
|
+
// #RRGGBB or #RRGGBBAA format
|
|
34
|
+
r = parseInt(hex.substring(0, 2), 16);
|
|
35
|
+
g = parseInt(hex.substring(2, 4), 16);
|
|
36
|
+
b = parseInt(hex.substring(4, 6), 16);
|
|
37
|
+
} else {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [r, g, b];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse rgb() or rgba() color to RGB
|
|
49
|
+
function parseRgbColor(match: string): [number, number, number] | null {
|
|
50
|
+
const rgbMatch = match.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
51
|
+
if (!rgbMatch) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const r = parseInt(rgbMatch[1], 10);
|
|
56
|
+
const g = parseInt(rgbMatch[2], 10);
|
|
57
|
+
const b = parseInt(rgbMatch[3], 10);
|
|
58
|
+
|
|
59
|
+
if (r > 255 || g > 255 || b > 255) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [r, g, b];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Convert HSL to RGB
|
|
67
|
+
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
|
68
|
+
// Normalize h to 0-360, s and l to 0-1
|
|
69
|
+
h = h % 360;
|
|
70
|
+
if (h < 0) h += 360;
|
|
71
|
+
s = Math.max(0, Math.min(1, s / 100));
|
|
72
|
+
l = Math.max(0, Math.min(1, l / 100));
|
|
73
|
+
|
|
74
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
75
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
76
|
+
const m = l - c / 2;
|
|
77
|
+
|
|
78
|
+
let r = 0, g = 0, b = 0;
|
|
79
|
+
|
|
80
|
+
if (h >= 0 && h < 60) {
|
|
81
|
+
r = c; g = x; b = 0;
|
|
82
|
+
} else if (h >= 60 && h < 120) {
|
|
83
|
+
r = x; g = c; b = 0;
|
|
84
|
+
} else if (h >= 120 && h < 180) {
|
|
85
|
+
r = 0; g = c; b = x;
|
|
86
|
+
} else if (h >= 180 && h < 240) {
|
|
87
|
+
r = 0; g = x; b = c;
|
|
88
|
+
} else if (h >= 240 && h < 300) {
|
|
89
|
+
r = x; g = 0; b = c;
|
|
90
|
+
} else {
|
|
91
|
+
r = c; g = 0; b = x;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [
|
|
95
|
+
Math.round((r + m) * 255),
|
|
96
|
+
Math.round((g + m) * 255),
|
|
97
|
+
Math.round((b + m) * 255),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Parse hsl() or hsla() color to RGB
|
|
102
|
+
function parseHslColor(match: string): [number, number, number] | null {
|
|
103
|
+
const hslMatch = match.match(/hsla?\s*\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%/);
|
|
104
|
+
if (!hslMatch) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const h = parseFloat(hslMatch[1]);
|
|
109
|
+
const s = parseFloat(hslMatch[2]);
|
|
110
|
+
const l = parseFloat(hslMatch[3]);
|
|
111
|
+
|
|
112
|
+
return hslToRgb(h, s, l);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Parse Rust Color::Rgb(r, g, b) to RGB
|
|
116
|
+
function parseRustRgbColor(match: string): [number, number, number] | null {
|
|
117
|
+
const rustMatch = match.match(/Color::Rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
|
|
118
|
+
if (!rustMatch) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const r = parseInt(rustMatch[1], 10);
|
|
123
|
+
const g = parseInt(rustMatch[2], 10);
|
|
124
|
+
const b = parseInt(rustMatch[3], 10);
|
|
125
|
+
|
|
126
|
+
if (r > 255 || g > 255 || b > 255) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return [r, g, b];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Color patterns to match
|
|
134
|
+
const colorPatterns = [
|
|
135
|
+
{
|
|
136
|
+
// Hex colors: #RGB, #RRGGBB, #RRGGBBAA
|
|
137
|
+
regex: /#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b/g,
|
|
138
|
+
parse: parseHexColor,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
// CSS rgb() and rgba()
|
|
142
|
+
regex: /rgba?\s*\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*[\d.]+\s*)?\)/g,
|
|
143
|
+
parse: parseRgbColor,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
// CSS hsl() and hsla()
|
|
147
|
+
regex: /hsla?\s*\(\s*-?\d+(?:\.\d+)?\s*,\s*\d+(?:\.\d+)?%\s*,\s*\d+(?:\.\d+)?%\s*(?:,\s*[\d.]+\s*)?\)/g,
|
|
148
|
+
parse: parseHslColor,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
// Rust Color::Rgb(r, g, b)
|
|
152
|
+
regex: /Color::Rgb\s*\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)/g,
|
|
153
|
+
parse: parseRustRgbColor,
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// Process a single line for color highlighting
|
|
158
|
+
function highlightLine(
|
|
159
|
+
bufferId: number,
|
|
160
|
+
lineNumber: number,
|
|
161
|
+
byteStart: number,
|
|
162
|
+
content: string
|
|
163
|
+
): void {
|
|
164
|
+
// Search for color patterns
|
|
165
|
+
for (const pattern of colorPatterns) {
|
|
166
|
+
// Reset regex lastIndex
|
|
167
|
+
pattern.regex.lastIndex = 0;
|
|
168
|
+
|
|
169
|
+
let match;
|
|
170
|
+
while ((match = pattern.regex.exec(content)) !== null) {
|
|
171
|
+
const matchText = match[0];
|
|
172
|
+
const pos = match.index;
|
|
173
|
+
const color = pattern.parse(matchText);
|
|
174
|
+
|
|
175
|
+
if (color) {
|
|
176
|
+
const absolutePos = byteStart + pos;
|
|
177
|
+
const virtualTextId = `color-${bufferId}-${lineNumber}-${pos}`;
|
|
178
|
+
|
|
179
|
+
// Add virtual text with color swatch before the color code
|
|
180
|
+
editor.addVirtualText(
|
|
181
|
+
bufferId,
|
|
182
|
+
virtualTextId,
|
|
183
|
+
absolutePos,
|
|
184
|
+
COLOR_BLOCK + " ",
|
|
185
|
+
color[0],
|
|
186
|
+
color[1],
|
|
187
|
+
color[2],
|
|
188
|
+
true // before the character
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Clear color highlights for a buffer
|
|
196
|
+
function clearHighlights(bufferId: number): void {
|
|
197
|
+
editor.removeVirtualTextsByPrefix(bufferId, "color-");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle render-start events (only clear virtual texts if buffer content changed)
|
|
201
|
+
globalThis.onColorRenderStart = function(data: { buffer_id: number }): void {
|
|
202
|
+
if (!config.enabled) return;
|
|
203
|
+
|
|
204
|
+
// Only clear and recreate virtual texts if the buffer content changed
|
|
205
|
+
if (dirtyBuffers.has(data.buffer_id)) {
|
|
206
|
+
clearHighlights(data.buffer_id);
|
|
207
|
+
dirtyBuffers.delete(data.buffer_id);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Handle lines_changed events (batched for efficiency)
|
|
212
|
+
globalThis.onColorLinesChanged = function(data: {
|
|
213
|
+
buffer_id: number;
|
|
214
|
+
lines: Array<{
|
|
215
|
+
line_number: number;
|
|
216
|
+
byte_start: number;
|
|
217
|
+
byte_end: number;
|
|
218
|
+
content: string;
|
|
219
|
+
}>;
|
|
220
|
+
}): void {
|
|
221
|
+
if (!config.enabled) return;
|
|
222
|
+
|
|
223
|
+
// Process all changed lines
|
|
224
|
+
for (const line of data.lines) {
|
|
225
|
+
highlightLine(data.buffer_id, line.line_number, line.byte_start, line.content);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Handle buffer content changes - mark buffer as needing virtual text refresh
|
|
230
|
+
globalThis.onColorAfterInsert = function(data: { buffer_id: number }): void {
|
|
231
|
+
dirtyBuffers.add(data.buffer_id);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
globalThis.onColorAfterDelete = function(data: { buffer_id: number }): void {
|
|
235
|
+
dirtyBuffers.add(data.buffer_id);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Handle buffer close events
|
|
239
|
+
globalThis.onColorBufferClosed = function(data: { buffer_id: number }): void {
|
|
240
|
+
dirtyBuffers.delete(data.buffer_id);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Register hooks
|
|
244
|
+
editor.on("render_start", "onColorRenderStart");
|
|
245
|
+
editor.on("lines_changed", "onColorLinesChanged");
|
|
246
|
+
editor.on("after-insert", "onColorAfterInsert");
|
|
247
|
+
editor.on("after-delete", "onColorAfterDelete");
|
|
248
|
+
editor.on("buffer_closed", "onColorBufferClosed");
|
|
249
|
+
|
|
250
|
+
// Plugin commands
|
|
251
|
+
globalThis.colorHighlighterEnable = function(): void {
|
|
252
|
+
config.enabled = true;
|
|
253
|
+
// Refresh lines so next render processes all visible lines
|
|
254
|
+
const bufferId = editor.getActiveBufferId();
|
|
255
|
+
editor.refreshLines(bufferId);
|
|
256
|
+
editor.setStatus("Color Highlighter: Enabled");
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
globalThis.colorHighlighterDisable = function(): void {
|
|
260
|
+
config.enabled = false;
|
|
261
|
+
const bufferId = editor.getActiveBufferId();
|
|
262
|
+
clearHighlights(bufferId);
|
|
263
|
+
editor.setStatus("Color Highlighter: Disabled");
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
globalThis.colorHighlighterToggle = function(): void {
|
|
267
|
+
config.enabled = !config.enabled;
|
|
268
|
+
const bufferId = editor.getActiveBufferId();
|
|
269
|
+
if (config.enabled) {
|
|
270
|
+
// Refresh lines so next render processes all visible lines
|
|
271
|
+
editor.refreshLines(bufferId);
|
|
272
|
+
} else {
|
|
273
|
+
clearHighlights(bufferId);
|
|
274
|
+
}
|
|
275
|
+
editor.setStatus(`Color Highlighter: ${config.enabled ? "Enabled" : "Disabled"}`);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Register commands
|
|
279
|
+
editor.registerCommand(
|
|
280
|
+
"Color Highlighter: Enable",
|
|
281
|
+
"Enable color code highlighting with swatches",
|
|
282
|
+
"colorHighlighterEnable",
|
|
283
|
+
"normal"
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
editor.registerCommand(
|
|
287
|
+
"Color Highlighter: Disable",
|
|
288
|
+
"Disable color code highlighting",
|
|
289
|
+
"colorHighlighterDisable",
|
|
290
|
+
"normal"
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
editor.registerCommand(
|
|
294
|
+
"Color Highlighter: Toggle",
|
|
295
|
+
"Toggle color code highlighting",
|
|
296
|
+
"colorHighlighterToggle",
|
|
297
|
+
"normal"
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Initialization
|
|
301
|
+
editor.setStatus("Color Highlighter plugin loaded");
|
|
302
|
+
editor.debug("Color Highlighter initialized - supports hex, rgb, hsl, and Rust Color::Rgb");
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/// <reference path="../types/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Diagnostics Panel Plugin (TypeScript)
|
|
5
|
+
*
|
|
6
|
+
* Full diagnostics panel implementation with virtual buffer split view.
|
|
7
|
+
* Provides LSP-like diagnostics display with severity icons and navigation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Panel state
|
|
11
|
+
let panelOpen = false;
|
|
12
|
+
let diagnosticsBufferId: number | null = null;
|
|
13
|
+
let sourceSplitId: number | null = null; // The split where source code is displayed
|
|
14
|
+
let currentDiagnostics: DiagnosticItem[] = [];
|
|
15
|
+
let selectedIndex = 0;
|
|
16
|
+
|
|
17
|
+
// Diagnostic item structure
|
|
18
|
+
interface DiagnosticItem {
|
|
19
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
20
|
+
message: string;
|
|
21
|
+
file: string;
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Severity icons
|
|
27
|
+
const severityIcons: Record<string, string> = {
|
|
28
|
+
error: "[E]",
|
|
29
|
+
warning: "[W]",
|
|
30
|
+
info: "[I]",
|
|
31
|
+
hint: "[H]",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Define the diagnostics mode with keybindings
|
|
35
|
+
editor.defineMode(
|
|
36
|
+
"diagnostics-list",
|
|
37
|
+
null, // no parent mode
|
|
38
|
+
[
|
|
39
|
+
["Return", "diagnostics_goto"],
|
|
40
|
+
["n", "diagnostics_next"],
|
|
41
|
+
["p", "diagnostics_prev"],
|
|
42
|
+
["j", "diagnostics_next"],
|
|
43
|
+
["k", "diagnostics_prev"],
|
|
44
|
+
["q", "diagnostics_close"],
|
|
45
|
+
["Escape", "diagnostics_close"],
|
|
46
|
+
],
|
|
47
|
+
true // read-only
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Format a diagnostic for display
|
|
51
|
+
function formatDiagnostic(item: DiagnosticItem, index: number): string {
|
|
52
|
+
const icon = severityIcons[item.severity] || "[?]";
|
|
53
|
+
const marker = index === selectedIndex ? ">" : " ";
|
|
54
|
+
return `${marker} ${icon} ${item.file}:${item.line}:${item.column} - ${item.message}\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build entries for the virtual buffer
|
|
58
|
+
function buildPanelEntries(): TextPropertyEntry[] {
|
|
59
|
+
const entries: TextPropertyEntry[] = [];
|
|
60
|
+
|
|
61
|
+
// Header
|
|
62
|
+
entries.push({
|
|
63
|
+
text: "═══ LSP Diagnostics ═══\n",
|
|
64
|
+
properties: { type: "header" },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (currentDiagnostics.length === 0) {
|
|
68
|
+
entries.push({
|
|
69
|
+
text: " No diagnostics available\n",
|
|
70
|
+
properties: { type: "empty" },
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
// Add each diagnostic
|
|
74
|
+
for (let i = 0; i < currentDiagnostics.length; i++) {
|
|
75
|
+
const diag = currentDiagnostics[i];
|
|
76
|
+
entries.push({
|
|
77
|
+
text: formatDiagnostic(diag, i),
|
|
78
|
+
properties: {
|
|
79
|
+
type: "diagnostic",
|
|
80
|
+
index: i,
|
|
81
|
+
severity: diag.severity,
|
|
82
|
+
location: {
|
|
83
|
+
file: diag.file,
|
|
84
|
+
line: diag.line,
|
|
85
|
+
column: diag.column,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Footer with summary
|
|
93
|
+
const errorCount = currentDiagnostics.filter((d) => d.severity === "error").length;
|
|
94
|
+
const warningCount = currentDiagnostics.filter((d) => d.severity === "warning").length;
|
|
95
|
+
entries.push({
|
|
96
|
+
text: `───────────────────────\n`,
|
|
97
|
+
properties: { type: "separator" },
|
|
98
|
+
});
|
|
99
|
+
entries.push({
|
|
100
|
+
text: `Total: ${errorCount} error(s), ${warningCount} warning(s)\n`,
|
|
101
|
+
properties: { type: "summary" },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return entries;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Update the panel content
|
|
108
|
+
function updatePanelContent(): void {
|
|
109
|
+
if (diagnosticsBufferId !== null) {
|
|
110
|
+
const entries = buildPanelEntries();
|
|
111
|
+
editor.setVirtualBufferContent(diagnosticsBufferId, entries);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Generate sample diagnostics for the current file
|
|
116
|
+
function generateSampleDiagnostics(): DiagnosticItem[] {
|
|
117
|
+
const bufferId = editor.getActiveBufferId();
|
|
118
|
+
const filePath = editor.getBufferPath(bufferId);
|
|
119
|
+
|
|
120
|
+
// Return sample diagnostics
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
severity: "error",
|
|
124
|
+
message: "unused import",
|
|
125
|
+
file: filePath || "unknown.rs",
|
|
126
|
+
line: 1,
|
|
127
|
+
column: 1,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
severity: "warning",
|
|
131
|
+
message: "variable never used",
|
|
132
|
+
file: filePath || "unknown.rs",
|
|
133
|
+
line: 2,
|
|
134
|
+
column: 5,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
severity: "info",
|
|
138
|
+
message: "consider using pattern matching",
|
|
139
|
+
file: filePath || "unknown.rs",
|
|
140
|
+
line: 3,
|
|
141
|
+
column: 10,
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Show diagnostics panel
|
|
147
|
+
globalThis.show_diagnostics_panel = async function (): Promise<void> {
|
|
148
|
+
if (panelOpen) {
|
|
149
|
+
editor.setStatus("Diagnostics panel already open");
|
|
150
|
+
updatePanelContent();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Save the current split ID before creating the diagnostics split
|
|
155
|
+
// This is where we'll open files when jumping to diagnostics
|
|
156
|
+
sourceSplitId = editor.getActiveSplitId();
|
|
157
|
+
|
|
158
|
+
// Generate sample diagnostics
|
|
159
|
+
currentDiagnostics = generateSampleDiagnostics();
|
|
160
|
+
selectedIndex = 0;
|
|
161
|
+
|
|
162
|
+
// Build panel entries
|
|
163
|
+
const entries = buildPanelEntries();
|
|
164
|
+
|
|
165
|
+
// Create virtual buffer in horizontal split
|
|
166
|
+
try {
|
|
167
|
+
diagnosticsBufferId = await editor.createVirtualBufferInSplit({
|
|
168
|
+
name: "*Diagnostics*",
|
|
169
|
+
mode: "diagnostics-list",
|
|
170
|
+
read_only: true,
|
|
171
|
+
entries: entries,
|
|
172
|
+
ratio: 0.7, // Original pane takes 70%, diagnostics takes 30%
|
|
173
|
+
panel_id: "diagnostics-panel",
|
|
174
|
+
show_line_numbers: false,
|
|
175
|
+
show_cursors: true,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
panelOpen = true;
|
|
179
|
+
editor.setStatus(`Diagnostics: ${currentDiagnostics.length} item(s) - Press RET to jump, n/p to navigate, q to close`);
|
|
180
|
+
editor.debug(`Diagnostics panel opened with buffer ID ${diagnosticsBufferId}`);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
183
|
+
editor.setStatus("Failed to open diagnostics panel");
|
|
184
|
+
editor.debug(`ERROR: createVirtualBufferInSplit failed: ${errorMessage}`);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Hide diagnostics panel
|
|
189
|
+
globalThis.hide_diagnostics_panel = function (): void {
|
|
190
|
+
if (!panelOpen) {
|
|
191
|
+
editor.setStatus("Diagnostics panel not open");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
panelOpen = false;
|
|
196
|
+
diagnosticsBufferId = null;
|
|
197
|
+
sourceSplitId = null;
|
|
198
|
+
selectedIndex = 0;
|
|
199
|
+
currentDiagnostics = [];
|
|
200
|
+
editor.setStatus("Diagnostics panel closed");
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Toggle diagnostics panel
|
|
204
|
+
globalThis.toggle_diagnostics_panel = function (): void {
|
|
205
|
+
if (panelOpen) {
|
|
206
|
+
globalThis.hide_diagnostics_panel();
|
|
207
|
+
} else {
|
|
208
|
+
globalThis.show_diagnostics_panel();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Show diagnostic count
|
|
213
|
+
globalThis.show_diagnostics_count = function (): void {
|
|
214
|
+
const errorCount = currentDiagnostics.filter((d) => d.severity === "error").length;
|
|
215
|
+
const warningCount = currentDiagnostics.filter((d) => d.severity === "warning").length;
|
|
216
|
+
editor.setStatus(`Diagnostics: ${errorCount} errors, ${warningCount} warnings`);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Navigation: go to selected diagnostic
|
|
220
|
+
globalThis.diagnostics_goto = function (): void {
|
|
221
|
+
if (currentDiagnostics.length === 0) {
|
|
222
|
+
editor.setStatus("No diagnostics to jump to");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (sourceSplitId === null) {
|
|
227
|
+
editor.setStatus("Source split not available");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const bufferId = editor.getActiveBufferId();
|
|
232
|
+
const props = editor.getTextPropertiesAtCursor(bufferId);
|
|
233
|
+
|
|
234
|
+
if (props.length > 0) {
|
|
235
|
+
const location = props[0].location as { file: string; line: number; column: number } | undefined;
|
|
236
|
+
if (location) {
|
|
237
|
+
// Open file in the source split, not the diagnostics split
|
|
238
|
+
editor.openFileInSplit(sourceSplitId, location.file, location.line, location.column || 0);
|
|
239
|
+
editor.setStatus(`Jumped to ${location.file}:${location.line}`);
|
|
240
|
+
} else {
|
|
241
|
+
editor.setStatus("No location info for this diagnostic");
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
// Fallback: use selectedIndex
|
|
245
|
+
const diag = currentDiagnostics[selectedIndex];
|
|
246
|
+
if (diag) {
|
|
247
|
+
// Open file in the source split, not the diagnostics split
|
|
248
|
+
editor.openFileInSplit(sourceSplitId, diag.file, diag.line, diag.column);
|
|
249
|
+
editor.setStatus(`Jumped to ${diag.file}:${diag.line}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Navigation: next diagnostic
|
|
255
|
+
globalThis.diagnostics_next = function (): void {
|
|
256
|
+
if (currentDiagnostics.length === 0) return;
|
|
257
|
+
|
|
258
|
+
selectedIndex = (selectedIndex + 1) % currentDiagnostics.length;
|
|
259
|
+
updatePanelContent();
|
|
260
|
+
editor.setStatus(`Diagnostic ${selectedIndex + 1}/${currentDiagnostics.length}`);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Navigation: previous diagnostic
|
|
264
|
+
globalThis.diagnostics_prev = function (): void {
|
|
265
|
+
if (currentDiagnostics.length === 0) return;
|
|
266
|
+
|
|
267
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : currentDiagnostics.length - 1;
|
|
268
|
+
updatePanelContent();
|
|
269
|
+
editor.setStatus(`Diagnostic ${selectedIndex + 1}/${currentDiagnostics.length}`);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Close the diagnostics panel
|
|
273
|
+
globalThis.diagnostics_close = function (): void {
|
|
274
|
+
globalThis.hide_diagnostics_panel();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Register commands
|
|
278
|
+
editor.registerCommand(
|
|
279
|
+
"Show Diagnostics Panel",
|
|
280
|
+
"Open the diagnostics panel",
|
|
281
|
+
"show_diagnostics_panel",
|
|
282
|
+
"normal"
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
editor.registerCommand(
|
|
286
|
+
"Hide Diagnostics Panel",
|
|
287
|
+
"Close the diagnostics panel",
|
|
288
|
+
"hide_diagnostics_panel",
|
|
289
|
+
"normal"
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
editor.registerCommand(
|
|
293
|
+
"Toggle Diagnostics Panel",
|
|
294
|
+
"Toggle diagnostics panel visibility",
|
|
295
|
+
"toggle_diagnostics_panel",
|
|
296
|
+
"normal"
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
editor.registerCommand(
|
|
300
|
+
"Diagnostics Count",
|
|
301
|
+
"Show count of current diagnostics",
|
|
302
|
+
"show_diagnostics_count",
|
|
303
|
+
"normal"
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Plugin initialization
|
|
307
|
+
editor.setStatus("Diagnostics Panel plugin loaded (TypeScript)");
|
|
308
|
+
editor.debug("Diagnostics Panel plugin initialized - 4 commands registered");
|