@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.
Files changed (38) hide show
  1. package/.gitignore +2 -0
  2. package/LICENSE +117 -0
  3. package/README.md +54 -0
  4. package/binary-install.js +212 -0
  5. package/binary.js +126 -0
  6. package/install.js +4 -0
  7. package/npm-shrinkwrap.json +900 -0
  8. package/package.json +100 -0
  9. package/plugins/README.md +121 -0
  10. package/plugins/clangd_support.md +20 -0
  11. package/plugins/clangd_support.ts +323 -0
  12. package/plugins/color_highlighter.ts +302 -0
  13. package/plugins/diagnostics_panel.ts +308 -0
  14. package/plugins/examples/README.md +245 -0
  15. package/plugins/examples/async_demo.ts +165 -0
  16. package/plugins/examples/bookmarks.ts +329 -0
  17. package/plugins/examples/buffer_query_demo.ts +110 -0
  18. package/plugins/examples/git_grep.ts +262 -0
  19. package/plugins/examples/hello_world.ts +93 -0
  20. package/plugins/examples/virtual_buffer_demo.ts +116 -0
  21. package/plugins/find_references.ts +357 -0
  22. package/plugins/git_find_file.ts +298 -0
  23. package/plugins/git_grep.ts +188 -0
  24. package/plugins/git_log.ts +1283 -0
  25. package/plugins/lib/fresh.d.ts +849 -0
  26. package/plugins/lib/index.ts +24 -0
  27. package/plugins/lib/navigation-controller.ts +214 -0
  28. package/plugins/lib/panel-manager.ts +218 -0
  29. package/plugins/lib/types.ts +72 -0
  30. package/plugins/lib/virtual-buffer-factory.ts +158 -0
  31. package/plugins/manual_help.ts +243 -0
  32. package/plugins/markdown_compose.ts +1207 -0
  33. package/plugins/merge_conflict.ts +1811 -0
  34. package/plugins/path_complete.ts +163 -0
  35. package/plugins/search_replace.ts +481 -0
  36. package/plugins/todo_highlighter.ts +204 -0
  37. package/plugins/welcome.ts +74 -0
  38. package/run-fresh.js +4 -0
@@ -0,0 +1,93 @@
1
+ /// <reference path="../../types/fresh.d.ts" />
2
+
3
+ /**
4
+ * Hello World TypeScript Plugin for Fresh Editor
5
+ *
6
+ * This is a simple example plugin that demonstrates:
7
+ * - Querying editor state (buffer info, cursor position)
8
+ * - Sending commands (status messages, text insertion)
9
+ * - Using async/await for plugin actions
10
+ */
11
+
12
+ // Global action: Display buffer information
13
+ globalThis.show_buffer_info = function (): void {
14
+ const bufferId = editor.getActiveBufferId();
15
+ const path = editor.getBufferPath(bufferId);
16
+ const length = editor.getBufferLength(bufferId);
17
+ const modified = editor.isBufferModified(bufferId);
18
+ const cursorPos = editor.getCursorPosition();
19
+
20
+ const status = `Buffer ${bufferId}: ${path || "[untitled]"} | ${length} bytes | ${
21
+ modified ? "modified" : "saved"
22
+ } | cursor@${cursorPos}`;
23
+
24
+ editor.setStatus(status);
25
+ editor.debug(`Buffer info: ${status}`);
26
+ };
27
+
28
+ // Global action: Insert timestamp at cursor
29
+ globalThis.insert_timestamp = function (): void {
30
+ const bufferId = editor.getActiveBufferId();
31
+ const cursorPos = editor.getCursorPosition();
32
+ const timestamp = new Date().toISOString();
33
+
34
+ const success = editor.insertText(bufferId, cursorPos, timestamp);
35
+ if (success) {
36
+ editor.setStatus(`Inserted timestamp: ${timestamp}`);
37
+ } else {
38
+ editor.setStatus("Failed to insert timestamp");
39
+ }
40
+ };
41
+
42
+ // Global action: Highlight current line (demo overlay)
43
+ globalThis.highlight_region = function (): void {
44
+ const bufferId = editor.getActiveBufferId();
45
+ const cursorPos = editor.getCursorPosition();
46
+
47
+ // Highlight 10 characters around cursor
48
+ const start = Math.max(0, cursorPos - 5);
49
+ const end = cursorPos + 5;
50
+
51
+ // Use namespace "demo" for batch operations
52
+ const success = editor.addOverlay(
53
+ bufferId,
54
+ "demo", // namespace
55
+ start,
56
+ end,
57
+ 255, // Red
58
+ 255, // Green
59
+ 0, // Blue (yellow highlight)
60
+ false // No underline
61
+ );
62
+
63
+ if (success) {
64
+ editor.setStatus(`Highlighted region ${start}-${end}`);
65
+ }
66
+ };
67
+
68
+ // Global action: Remove highlight
69
+ globalThis.clear_highlight = function (): void {
70
+ const bufferId = editor.getActiveBufferId();
71
+ // Clear all overlays in the "demo" namespace
72
+ const success = editor.clearNamespace(bufferId, "demo");
73
+ if (success) {
74
+ editor.setStatus("Cleared highlight");
75
+ }
76
+ };
77
+
78
+ // Global async action: Demonstrate async/await
79
+ globalThis.async_demo = async function (): Promise<void> {
80
+ editor.setStatus("Starting async operation...");
81
+
82
+ // Simulate some async work
83
+ await Promise.resolve();
84
+
85
+ const bufferId = editor.getActiveBufferId();
86
+ const length = editor.getBufferLength(bufferId);
87
+
88
+ editor.setStatus(`Async operation complete! Buffer has ${length} bytes`);
89
+ };
90
+
91
+ // Log that plugin loaded
92
+ editor.debug("Hello World plugin loaded!");
93
+ editor.setStatus("Hello World plugin ready");
@@ -0,0 +1,116 @@
1
+ // Virtual Buffer Demo Plugin
2
+ // Demonstrates the virtual buffer API for creating diagnostic panels, search results, etc.
3
+
4
+ // Register a command to show a demo virtual buffer
5
+ editor.registerCommand(
6
+ "Virtual Buffer Demo",
7
+ "Show a demo virtual buffer with sample diagnostics",
8
+ "show_virtual_buffer_demo",
9
+ "normal"
10
+ );
11
+
12
+ // Define a custom mode for the demo buffer
13
+ editor.defineMode(
14
+ "demo-list", // mode name
15
+ null, // no parent mode
16
+ [
17
+ ["Return", "demo_goto_item"],
18
+ ["n", "demo_next_item"],
19
+ ["p", "demo_prev_item"],
20
+ ["q", "demo_close_buffer"],
21
+ ],
22
+ true // read-only
23
+ );
24
+
25
+ // Register actions for the mode
26
+ globalThis.demo_goto_item = () => {
27
+ const bufferId = editor.getActiveBufferId();
28
+ const props = editor.getTextPropertiesAtCursor(bufferId);
29
+
30
+ if (props.length > 0) {
31
+ const location = props[0].location as { file: string; line: number; column: number } | undefined;
32
+ if (location) {
33
+ editor.openFile(location.file, location.line, location.column || 0);
34
+ editor.setStatus(`Jumped to ${location.file}:${location.line}`);
35
+ } else {
36
+ editor.setStatus("No location info for this item");
37
+ }
38
+ } else {
39
+ editor.setStatus("No properties at cursor position");
40
+ }
41
+ };
42
+
43
+ globalThis.demo_next_item = () => {
44
+ editor.setStatus("Next item (not implemented in demo)");
45
+ };
46
+
47
+ globalThis.demo_prev_item = () => {
48
+ editor.setStatus("Previous item (not implemented in demo)");
49
+ };
50
+
51
+ globalThis.demo_close_buffer = () => {
52
+ editor.setStatus("Close buffer (not implemented in demo)");
53
+ };
54
+
55
+ // Main action: show the virtual buffer
56
+ globalThis.show_virtual_buffer_demo = async () => {
57
+ editor.setStatus("Creating virtual buffer demo...");
58
+
59
+ // Create sample diagnostic entries
60
+ const entries = [
61
+ {
62
+ text: "[ERROR] src/main.rs:42:10 - undefined variable 'foo'\n",
63
+ properties: {
64
+ severity: "error",
65
+ location: { file: "src/main.rs", line: 42, column: 10 },
66
+ message: "undefined variable 'foo'",
67
+ },
68
+ },
69
+ {
70
+ text: "[WARNING] src/lib.rs:100:5 - unused variable 'bar'\n",
71
+ properties: {
72
+ severity: "warning",
73
+ location: { file: "src/lib.rs", line: 100, column: 5 },
74
+ message: "unused variable 'bar'",
75
+ },
76
+ },
77
+ {
78
+ text: "[INFO] src/utils.rs:25:1 - consider using 'if let' instead of 'match'\n",
79
+ properties: {
80
+ severity: "info",
81
+ location: { file: "src/utils.rs", line: 25, column: 1 },
82
+ message: "consider using 'if let' instead of 'match'",
83
+ },
84
+ },
85
+ {
86
+ text: "[HINT] src/config.rs:8:20 - type annotation unnecessary\n",
87
+ properties: {
88
+ severity: "hint",
89
+ location: { file: "src/config.rs", line: 8, column: 20 },
90
+ message: "type annotation unnecessary",
91
+ },
92
+ },
93
+ ];
94
+
95
+ // Create the virtual buffer in a horizontal split
96
+ try {
97
+ const bufferId = await editor.createVirtualBufferInSplit({
98
+ name: "*Demo Diagnostics*",
99
+ mode: "demo-list",
100
+ read_only: true,
101
+ entries: entries,
102
+ ratio: 0.7, // Original pane takes 70%, demo buffer takes 30%
103
+ panel_id: "demo-diagnostics",
104
+ show_line_numbers: false,
105
+ show_cursors: true,
106
+ });
107
+
108
+ editor.setStatus(`Created demo virtual buffer (ID: ${bufferId}) with ${entries.length} items - Press RET to jump to location`);
109
+ } catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ editor.setStatus(`Failed to create virtual buffer: ${errorMessage}`);
112
+ }
113
+ };
114
+
115
+ // Log that the plugin loaded
116
+ editor.debug("Virtual buffer demo plugin loaded");
@@ -0,0 +1,357 @@
1
+ /// <reference path="../types/fresh.d.ts" />
2
+
3
+ /**
4
+ * Find References Plugin (TypeScript)
5
+ *
6
+ * Displays LSP find references results in a virtual buffer split view.
7
+ * Listens for lsp_references hook from the editor and shows results.
8
+ * Uses cursor movement for navigation (Up/Down/j/k work naturally).
9
+ */
10
+
11
+ // Panel state
12
+ let panelOpen = false;
13
+ let referencesBufferId: number | null = null;
14
+ let sourceSplitId: number | null = null;
15
+ let referencesSplitId: number | null = null; // Track the split we created
16
+ let currentReferences: ReferenceItem[] = [];
17
+ let currentSymbol: string = "";
18
+ let lineCache: Map<string, string[]> = new Map(); // Cache file contents
19
+
20
+ // Maximum number of results to display
21
+ const MAX_RESULTS = 100;
22
+
23
+ // Reference item structure
24
+ interface ReferenceItem {
25
+ file: string;
26
+ line: number;
27
+ column: number;
28
+ lineText?: string; // Cached line text
29
+ }
30
+
31
+ // Define the references mode with minimal keybindings
32
+ // Navigation uses normal cursor movement (arrows, j/k work naturally)
33
+ editor.defineMode(
34
+ "references-list",
35
+ null, // no parent mode
36
+ [
37
+ ["Return", "references_goto"],
38
+ ["q", "references_close"],
39
+ ["Escape", "references_close"],
40
+ ],
41
+ true // read-only
42
+ );
43
+
44
+ // Get relative path for display
45
+ function getRelativePath(filePath: string): string {
46
+ const cwd = editor.getCwd();
47
+ if (filePath.startsWith(cwd)) {
48
+ return filePath.slice(cwd.length + 1); // Remove cwd and leading /
49
+ }
50
+ return filePath;
51
+ }
52
+
53
+ // Format a reference for display with line preview
54
+ function formatReference(item: ReferenceItem): string {
55
+ const displayPath = getRelativePath(item.file);
56
+ const location = `${displayPath}:${item.line}:${item.column}`;
57
+
58
+ // Truncate location if too long, leaving room for line text
59
+ const maxLocationLen = 50;
60
+ const truncatedLocation = location.length > maxLocationLen
61
+ ? "..." + location.slice(-(maxLocationLen - 3))
62
+ : location.padEnd(maxLocationLen);
63
+
64
+ // Get line text preview (truncated)
65
+ const lineText = item.lineText || "";
66
+ const trimmedLine = lineText.trim();
67
+ const maxLineLen = 60;
68
+ const displayLine = trimmedLine.length > maxLineLen
69
+ ? trimmedLine.slice(0, maxLineLen - 3) + "..."
70
+ : trimmedLine;
71
+
72
+ return ` ${truncatedLocation} ${displayLine}\n`;
73
+ }
74
+
75
+ // Build entries for the virtual buffer
76
+ function buildPanelEntries(): TextPropertyEntry[] {
77
+ const entries: TextPropertyEntry[] = [];
78
+
79
+ // Header with symbol name
80
+ const totalCount = currentReferences.length;
81
+ const limitNote = totalCount >= MAX_RESULTS ? ` (limited to ${MAX_RESULTS})` : "";
82
+ const symbolDisplay = currentSymbol ? `'${currentSymbol}'` : "symbol";
83
+ entries.push({
84
+ text: `═══ References to ${symbolDisplay} (${totalCount}${limitNote}) ═══\n`,
85
+ properties: { type: "header" },
86
+ });
87
+
88
+ if (currentReferences.length === 0) {
89
+ entries.push({
90
+ text: " No references found\n",
91
+ properties: { type: "empty" },
92
+ });
93
+ } else {
94
+ // Add each reference
95
+ for (let i = 0; i < currentReferences.length; i++) {
96
+ const ref = currentReferences[i];
97
+ entries.push({
98
+ text: formatReference(ref),
99
+ properties: {
100
+ type: "reference",
101
+ index: i,
102
+ location: {
103
+ file: ref.file,
104
+ line: ref.line,
105
+ column: ref.column,
106
+ },
107
+ },
108
+ });
109
+ }
110
+ }
111
+
112
+ // Footer
113
+ entries.push({
114
+ text: `───────────────────────────────────────────────────────────────────────────────\n`,
115
+ properties: { type: "separator" },
116
+ });
117
+ entries.push({
118
+ text: `[↑/↓] navigate [RET] jump [q/Esc] close\n`,
119
+ properties: { type: "help" },
120
+ });
121
+
122
+ return entries;
123
+ }
124
+
125
+ // Load line text for references
126
+ async function loadLineTexts(references: ReferenceItem[]): Promise<void> {
127
+ // Group references by file
128
+ const fileRefs: Map<string, ReferenceItem[]> = new Map();
129
+ for (const ref of references) {
130
+ if (!fileRefs.has(ref.file)) {
131
+ fileRefs.set(ref.file, []);
132
+ }
133
+ fileRefs.get(ref.file)!.push(ref);
134
+ }
135
+
136
+ // Load each file and extract lines
137
+ for (const [filePath, refs] of fileRefs) {
138
+ try {
139
+ // Check cache first
140
+ let lines = lineCache.get(filePath);
141
+ if (!lines) {
142
+ const content = await editor.readFile(filePath);
143
+ lines = content.split("\n");
144
+ lineCache.set(filePath, lines);
145
+ }
146
+
147
+ // Set line text for each reference
148
+ for (const ref of refs) {
149
+ const lineIndex = ref.line - 1; // Convert 1-based to 0-based
150
+ if (lineIndex >= 0 && lineIndex < lines.length) {
151
+ ref.lineText = lines[lineIndex];
152
+ } else {
153
+ ref.lineText = "";
154
+ }
155
+ }
156
+ } catch (error) {
157
+ // If file can't be read, leave lineText empty
158
+ for (const ref of refs) {
159
+ ref.lineText = "";
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ // Show references panel
166
+ async function showReferencesPanel(symbol: string, references: ReferenceItem[]): Promise<void> {
167
+ // Only save the source split ID if panel is not already open
168
+ // (avoid overwriting it with the references split ID on subsequent calls)
169
+ if (!panelOpen) {
170
+ sourceSplitId = editor.getActiveSplitId();
171
+ }
172
+
173
+ // Limit results
174
+ const limitedRefs = references.slice(0, MAX_RESULTS);
175
+
176
+ // Set references and symbol
177
+ currentSymbol = symbol;
178
+ currentReferences = limitedRefs;
179
+
180
+ // Load line texts for preview
181
+ await loadLineTexts(currentReferences);
182
+
183
+ // Build panel entries
184
+ const entries = buildPanelEntries();
185
+
186
+ // Create or update virtual buffer in horizontal split
187
+ // The panel_id mechanism will reuse the existing buffer/split if it exists
188
+ try {
189
+ referencesBufferId = await editor.createVirtualBufferInSplit({
190
+ name: "*References*",
191
+ mode: "references-list",
192
+ read_only: true,
193
+ entries: entries,
194
+ ratio: 0.7, // Original pane takes 70%, references takes 30%
195
+ panel_id: "references-panel",
196
+ show_line_numbers: false,
197
+ show_cursors: true, // Enable cursor for navigation
198
+ });
199
+
200
+ panelOpen = true;
201
+ // Track the references split (it becomes active after creation)
202
+ referencesSplitId = editor.getActiveSplitId();
203
+
204
+ const limitMsg = references.length > MAX_RESULTS
205
+ ? ` (showing first ${MAX_RESULTS})`
206
+ : "";
207
+ editor.setStatus(
208
+ `Found ${references.length} reference(s)${limitMsg} - ↑/↓ navigate, RET jump, q close`
209
+ );
210
+ editor.debug(`References panel opened with buffer ID ${referencesBufferId}, split ID ${referencesSplitId}`);
211
+ } catch (error) {
212
+ const errorMessage = error instanceof Error ? error.message : String(error);
213
+ editor.setStatus("Failed to open references panel");
214
+ editor.debug(`ERROR: createVirtualBufferInSplit failed: ${errorMessage}`);
215
+ }
216
+ }
217
+
218
+ // Handle lsp_references hook
219
+ globalThis.on_lsp_references = function (data: { symbol: string; locations: ReferenceItem[] }): void {
220
+ editor.debug(`Received ${data.locations.length} references for '${data.symbol}'`);
221
+
222
+ if (data.locations.length === 0) {
223
+ editor.setStatus(`No references found for '${data.symbol}'`);
224
+ return;
225
+ }
226
+
227
+ // Clear line cache for fresh results
228
+ lineCache.clear();
229
+
230
+ // Show the references panel
231
+ showReferencesPanel(data.symbol, data.locations);
232
+ };
233
+
234
+ // Register the hook handler
235
+ editor.on("lsp_references", "on_lsp_references");
236
+
237
+ // Handle cursor movement to show current reference info
238
+ globalThis.on_references_cursor_moved = function (data: {
239
+ buffer_id: number;
240
+ cursor_id: number;
241
+ old_position: number;
242
+ new_position: number;
243
+ }): void {
244
+ // Only handle cursor movement in our references buffer
245
+ if (referencesBufferId === null || data.buffer_id !== referencesBufferId) {
246
+ return;
247
+ }
248
+
249
+ // Get cursor line to determine which reference is selected
250
+ // getCursorLine() returns the line for the active buffer
251
+ const cursorLine = editor.getCursorLine();
252
+
253
+ // Line 0 is header, lines 1 to N are references
254
+ const refIndex = cursorLine - 1;
255
+
256
+ if (refIndex >= 0 && refIndex < currentReferences.length) {
257
+ editor.setStatus(`Reference ${refIndex + 1}/${currentReferences.length}`);
258
+ }
259
+ };
260
+
261
+ // Register cursor movement handler
262
+ editor.on("cursor_moved", "on_references_cursor_moved");
263
+
264
+ // Hide references panel
265
+ globalThis.hide_references_panel = function (): void {
266
+ if (!panelOpen) {
267
+ return;
268
+ }
269
+
270
+ if (referencesBufferId !== null) {
271
+ editor.closeBuffer(referencesBufferId);
272
+ }
273
+
274
+ // Close the split we created (if it exists and is different from source)
275
+ if (referencesSplitId !== null && referencesSplitId !== sourceSplitId) {
276
+ editor.closeSplit(referencesSplitId);
277
+ }
278
+
279
+ panelOpen = false;
280
+ referencesBufferId = null;
281
+ sourceSplitId = null;
282
+ referencesSplitId = null;
283
+ currentReferences = [];
284
+ currentSymbol = "";
285
+ lineCache.clear();
286
+ editor.setStatus("References panel closed");
287
+ };
288
+
289
+ // Navigation: go to selected reference (based on cursor position)
290
+ globalThis.references_goto = function (): void {
291
+ if (currentReferences.length === 0) {
292
+ editor.setStatus("No references to jump to");
293
+ return;
294
+ }
295
+
296
+ if (sourceSplitId === null) {
297
+ editor.setStatus("Source split not available");
298
+ return;
299
+ }
300
+
301
+ if (referencesBufferId === null) {
302
+ return;
303
+ }
304
+
305
+ // Get text properties at cursor position
306
+ const props = editor.getTextPropertiesAtCursor(referencesBufferId);
307
+ editor.debug(`references_goto: props.length=${props.length}, referencesBufferId=${referencesBufferId}, sourceSplitId=${sourceSplitId}`);
308
+
309
+ if (props.length > 0) {
310
+ editor.debug(`references_goto: props[0]=${JSON.stringify(props[0])}`);
311
+ const location = props[0].location as
312
+ | { file: string; line: number; column: number }
313
+ | undefined;
314
+ if (location) {
315
+ editor.debug(`references_goto: opening ${location.file}:${location.line}:${location.column} in split ${sourceSplitId}`);
316
+ // Open file in the source split, not the references split
317
+ editor.openFileInSplit(
318
+ sourceSplitId,
319
+ location.file,
320
+ location.line,
321
+ location.column || 0
322
+ );
323
+ const displayPath = getRelativePath(location.file);
324
+ editor.setStatus(`Jumped to ${displayPath}:${location.line}`);
325
+ } else {
326
+ editor.debug(`references_goto: no location in props[0]`);
327
+ editor.setStatus("Move cursor to a reference line");
328
+ }
329
+ } else {
330
+ editor.debug(`references_goto: no props found at cursor`);
331
+ editor.setStatus("Move cursor to a reference line");
332
+ }
333
+ };
334
+
335
+ // Close the references panel
336
+ globalThis.references_close = function (): void {
337
+ globalThis.hide_references_panel();
338
+ };
339
+
340
+ // Register commands
341
+ editor.registerCommand(
342
+ "Show References Panel",
343
+ "Display current references",
344
+ "show_references_panel",
345
+ "normal"
346
+ );
347
+
348
+ editor.registerCommand(
349
+ "Hide References Panel",
350
+ "Close the references panel",
351
+ "hide_references_panel",
352
+ "normal"
353
+ );
354
+
355
+ // Plugin initialization
356
+ editor.setStatus("Find References plugin loaded");
357
+ editor.debug("Find References plugin initialized");