@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,329 @@
|
|
|
1
|
+
/// <reference path="../../types/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bookmarks Plugin for Fresh Editor (TypeScript)
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates the new TypeScript plugin ops:
|
|
7
|
+
* - editor.registerCommand() - Register plugin commands
|
|
8
|
+
* - editor.openFile() - Open file at specific location
|
|
9
|
+
* - editor.getActiveSplitId() - Get current split ID
|
|
10
|
+
* - editor.openFileInSplit() - Open file in specific split
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Add bookmarks at current cursor position
|
|
14
|
+
* - List all bookmarks
|
|
15
|
+
* - Jump to bookmarks
|
|
16
|
+
* - Remove bookmarks
|
|
17
|
+
* - Split-aware navigation
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Bookmark storage
|
|
21
|
+
interface Bookmark {
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
path: string;
|
|
25
|
+
line: number;
|
|
26
|
+
column: number;
|
|
27
|
+
splitId: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const bookmarks: Map<number, Bookmark> = new Map();
|
|
31
|
+
let nextBookmarkId = 1;
|
|
32
|
+
|
|
33
|
+
// Helper: Get current location info
|
|
34
|
+
function getCurrentLocation(): {
|
|
35
|
+
path: string;
|
|
36
|
+
position: number;
|
|
37
|
+
splitId: number;
|
|
38
|
+
} {
|
|
39
|
+
const bufferId = editor.getActiveBufferId();
|
|
40
|
+
const path = editor.getBufferPath(bufferId);
|
|
41
|
+
const position = editor.getCursorPosition();
|
|
42
|
+
const splitId = editor.getActiveSplitId();
|
|
43
|
+
|
|
44
|
+
return { path, position, splitId };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper: Get actual line number using the API
|
|
48
|
+
function getCurrentLineCol(): { line: number; column: number } {
|
|
49
|
+
// Use the actual getCursorLine API for accurate line number
|
|
50
|
+
const lineNumber = editor.getCursorLine();
|
|
51
|
+
|
|
52
|
+
// Get cursor position within the line by reading buffer content
|
|
53
|
+
const bufferId = editor.getActiveBufferId();
|
|
54
|
+
const cursorPos = editor.getCursorPosition();
|
|
55
|
+
const bufferInfo = editor.getBufferInfo(bufferId);
|
|
56
|
+
|
|
57
|
+
// Calculate column by finding start of current line
|
|
58
|
+
let column = 1;
|
|
59
|
+
if (bufferInfo && cursorPos > 0) {
|
|
60
|
+
// Read a small chunk before cursor to find line start
|
|
61
|
+
const readStart = Math.max(0, cursorPos - 1000);
|
|
62
|
+
const textBefore = editor.getBufferText(bufferId, readStart, cursorPos);
|
|
63
|
+
const lastNewline = textBefore.lastIndexOf("\n");
|
|
64
|
+
if (lastNewline !== -1) {
|
|
65
|
+
column = cursorPos - (readStart + lastNewline);
|
|
66
|
+
} else {
|
|
67
|
+
// No newline found, column is position from readStart
|
|
68
|
+
column = cursorPos - readStart + 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { line: lineNumber, column };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Action: Add bookmark at current position
|
|
76
|
+
globalThis.bookmark_add = function (): void {
|
|
77
|
+
const { path, position, splitId } = getCurrentLocation();
|
|
78
|
+
const { line, column } = getCurrentLineCol();
|
|
79
|
+
|
|
80
|
+
if (!path) {
|
|
81
|
+
editor.setStatus("Cannot bookmark: buffer has no file path");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const id = nextBookmarkId++;
|
|
86
|
+
const name = `Bookmark ${id}`;
|
|
87
|
+
|
|
88
|
+
const bookmark: Bookmark = {
|
|
89
|
+
id,
|
|
90
|
+
name,
|
|
91
|
+
path,
|
|
92
|
+
line,
|
|
93
|
+
column,
|
|
94
|
+
splitId,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
bookmarks.set(id, bookmark);
|
|
98
|
+
|
|
99
|
+
// Add visual indicator with bookmark namespace
|
|
100
|
+
const bufferId = editor.getActiveBufferId();
|
|
101
|
+
editor.addOverlay(
|
|
102
|
+
bufferId,
|
|
103
|
+
"bookmark", // namespace for all bookmarks
|
|
104
|
+
position,
|
|
105
|
+
position + 1,
|
|
106
|
+
0, // Red
|
|
107
|
+
128, // Green (teal color)
|
|
108
|
+
255, // Blue
|
|
109
|
+
true // Underline
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
editor.setStatus(`Added ${name} at ${path}:${line}:${column}`);
|
|
113
|
+
editor.debug(`Bookmark ${id} created: ${JSON.stringify(bookmark)}`);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Action: List all bookmarks
|
|
117
|
+
globalThis.bookmark_list = function (): void {
|
|
118
|
+
if (bookmarks.size === 0) {
|
|
119
|
+
editor.setStatus("No bookmarks");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const list: string[] = [];
|
|
124
|
+
bookmarks.forEach((bm, id) => {
|
|
125
|
+
list.push(`[${id}] ${bm.path}:${bm.line}:${bm.column}`);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
editor.setStatus(`Bookmarks: ${list.join(" | ")}`);
|
|
129
|
+
editor.debug(`All bookmarks: ${JSON.stringify([...bookmarks.values()])}`);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Action: Jump to bookmark by ID
|
|
133
|
+
globalThis.bookmark_goto = function (): void {
|
|
134
|
+
if (bookmarks.size === 0) {
|
|
135
|
+
editor.setStatus("No bookmarks to jump to");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Jump to the first bookmark (simplified)
|
|
140
|
+
const firstBookmark = bookmarks.values().next().value;
|
|
141
|
+
if (firstBookmark) {
|
|
142
|
+
const success = editor.openFile(
|
|
143
|
+
firstBookmark.path,
|
|
144
|
+
firstBookmark.line,
|
|
145
|
+
firstBookmark.column
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (success) {
|
|
149
|
+
editor.setStatus(
|
|
150
|
+
`Jumped to ${firstBookmark.name}: ${firstBookmark.path}:${firstBookmark.line}`
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
editor.setStatus(`Failed to open ${firstBookmark.path}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Action: Jump to bookmark in same split (split-aware)
|
|
159
|
+
globalThis.bookmark_goto_split = function (): void {
|
|
160
|
+
if (bookmarks.size === 0) {
|
|
161
|
+
editor.setStatus("No bookmarks");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentSplit = editor.getActiveSplitId();
|
|
166
|
+
const firstBookmark = bookmarks.values().next().value;
|
|
167
|
+
|
|
168
|
+
if (firstBookmark) {
|
|
169
|
+
// Open in the current split, not the bookmark's original split
|
|
170
|
+
const success = editor.openFileInSplit(
|
|
171
|
+
currentSplit,
|
|
172
|
+
firstBookmark.path,
|
|
173
|
+
firstBookmark.line,
|
|
174
|
+
firstBookmark.column
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (success) {
|
|
178
|
+
editor.setStatus(
|
|
179
|
+
`Opened ${firstBookmark.name} in split ${currentSplit}`
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
editor.setStatus(`Failed to open in split ${currentSplit}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Action: Remove all bookmarks
|
|
188
|
+
globalThis.bookmark_clear = function (): void {
|
|
189
|
+
const bufferId = editor.getActiveBufferId();
|
|
190
|
+
|
|
191
|
+
// Remove all bookmark overlays using namespace
|
|
192
|
+
editor.clearNamespace(bufferId, "bookmark");
|
|
193
|
+
|
|
194
|
+
const count = bookmarks.size;
|
|
195
|
+
bookmarks.clear();
|
|
196
|
+
|
|
197
|
+
editor.setStatus(`Cleared ${count} bookmark(s)`);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Action: Show current split info
|
|
201
|
+
globalThis.show_split_info = function (): void {
|
|
202
|
+
const splitId = editor.getActiveSplitId();
|
|
203
|
+
const bufferId = editor.getActiveBufferId();
|
|
204
|
+
const path = editor.getBufferPath(bufferId);
|
|
205
|
+
|
|
206
|
+
editor.setStatus(`Split ${splitId} | Buffer ${bufferId} | ${path || "[untitled]"}`);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Interactive bookmark selection using prompt API
|
|
210
|
+
let bookmarkSuggestionIds: number[] = [];
|
|
211
|
+
|
|
212
|
+
globalThis.bookmark_select = function (): void {
|
|
213
|
+
if (bookmarks.size === 0) {
|
|
214
|
+
editor.setStatus("No bookmarks to select");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Create suggestions from bookmarks
|
|
219
|
+
const suggestions: PromptSuggestion[] = [];
|
|
220
|
+
bookmarkSuggestionIds = [];
|
|
221
|
+
|
|
222
|
+
bookmarks.forEach((bm) => {
|
|
223
|
+
const filename = bm.path.split("/").pop() || bm.path;
|
|
224
|
+
suggestions.push({
|
|
225
|
+
text: `${bm.name}: ${bm.path}:${bm.line}:${bm.column}`,
|
|
226
|
+
description: `${filename} at line ${bm.line}`,
|
|
227
|
+
value: String(bm.id),
|
|
228
|
+
disabled: false,
|
|
229
|
+
});
|
|
230
|
+
bookmarkSuggestionIds.push(bm.id);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
editor.startPrompt("Select bookmark: ", "bookmark-select");
|
|
234
|
+
editor.setPromptSuggestions(suggestions);
|
|
235
|
+
editor.setStatus(`${bookmarks.size} bookmark(s) available`);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Handle bookmark selection confirmation
|
|
239
|
+
globalThis.onBookmarkSelectConfirmed = function (args: {
|
|
240
|
+
prompt_type: string;
|
|
241
|
+
selected_index: number | null;
|
|
242
|
+
input: string;
|
|
243
|
+
}): boolean {
|
|
244
|
+
if (args.prompt_type !== "bookmark-select") {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (args.selected_index !== null && bookmarkSuggestionIds[args.selected_index] !== undefined) {
|
|
249
|
+
const bookmarkId = bookmarkSuggestionIds[args.selected_index];
|
|
250
|
+
const bookmark = bookmarks.get(bookmarkId);
|
|
251
|
+
|
|
252
|
+
if (bookmark) {
|
|
253
|
+
editor.openFile(bookmark.path, bookmark.line, bookmark.column);
|
|
254
|
+
editor.setStatus(`Jumped to ${bookmark.name}: ${bookmark.path}:${bookmark.line}`);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
editor.setStatus("No bookmark selected");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return true;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Handle bookmark selection cancellation
|
|
264
|
+
globalThis.onBookmarkSelectCancelled = function (args: { prompt_type: string }): boolean {
|
|
265
|
+
if (args.prompt_type !== "bookmark-select") {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
editor.setStatus("Bookmark selection cancelled");
|
|
270
|
+
return true;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Register bookmark event handlers
|
|
274
|
+
editor.on("prompt_confirmed", "onBookmarkSelectConfirmed");
|
|
275
|
+
editor.on("prompt_cancelled", "onBookmarkSelectCancelled");
|
|
276
|
+
|
|
277
|
+
// Register commands on plugin load
|
|
278
|
+
editor.registerCommand(
|
|
279
|
+
"Add Bookmark",
|
|
280
|
+
"Add a bookmark at the current cursor position",
|
|
281
|
+
"bookmark_add",
|
|
282
|
+
"normal"
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
editor.registerCommand(
|
|
286
|
+
"List Bookmarks",
|
|
287
|
+
"Show all bookmarks",
|
|
288
|
+
"bookmark_list",
|
|
289
|
+
"normal"
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
editor.registerCommand(
|
|
293
|
+
"Go to Bookmark",
|
|
294
|
+
"Jump to the first bookmark",
|
|
295
|
+
"bookmark_goto",
|
|
296
|
+
"normal"
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
editor.registerCommand(
|
|
300
|
+
"Go to Bookmark (Current Split)",
|
|
301
|
+
"Jump to bookmark in current split",
|
|
302
|
+
"bookmark_goto_split",
|
|
303
|
+
"normal"
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
editor.registerCommand(
|
|
307
|
+
"Clear Bookmarks",
|
|
308
|
+
"Remove all bookmarks",
|
|
309
|
+
"bookmark_clear",
|
|
310
|
+
"normal"
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
editor.registerCommand(
|
|
314
|
+
"Show Split Info",
|
|
315
|
+
"Display current split and buffer information",
|
|
316
|
+
"show_split_info",
|
|
317
|
+
"" // Available in all contexts
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
editor.registerCommand(
|
|
321
|
+
"Select Bookmark",
|
|
322
|
+
"Interactively select and jump to a bookmark",
|
|
323
|
+
"bookmark_select",
|
|
324
|
+
"normal"
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Plugin initialized
|
|
328
|
+
editor.setStatus("Bookmarks plugin loaded - 7 commands registered");
|
|
329
|
+
editor.debug("Bookmarks plugin initialized with command registration and prompt API support");
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/// <reference path="../../types/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Buffer Query Demo Plugin
|
|
5
|
+
* Demonstrates the buffer query APIs in Phase 2
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Show buffer info
|
|
9
|
+
globalThis.show_buffer_info_demo = function(): void {
|
|
10
|
+
const bufferId = editor.getActiveBufferId();
|
|
11
|
+
const info = editor.getBufferInfo(bufferId);
|
|
12
|
+
|
|
13
|
+
if (info) {
|
|
14
|
+
const msg = `Buffer ${info.id}: ${info.path || "[No Name]"} (${
|
|
15
|
+
info.modified ? "modified" : "saved"
|
|
16
|
+
}, ${info.length} bytes)`;
|
|
17
|
+
editor.setStatus(msg);
|
|
18
|
+
} else {
|
|
19
|
+
editor.setStatus("No buffer info available");
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
editor.registerCommand(
|
|
24
|
+
"Query Demo: Show Buffer Info",
|
|
25
|
+
"Display information about the current buffer",
|
|
26
|
+
"show_buffer_info_demo",
|
|
27
|
+
"normal"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Show cursor position with selection info
|
|
31
|
+
globalThis.show_cursor_info_demo = function(): void {
|
|
32
|
+
const cursor = editor.getPrimaryCursor();
|
|
33
|
+
|
|
34
|
+
if (cursor) {
|
|
35
|
+
let msg: string;
|
|
36
|
+
if (cursor.selection) {
|
|
37
|
+
msg = `Cursor at ${cursor.position}, selection: ${cursor.selection.start}-${cursor.selection.end} (${
|
|
38
|
+
cursor.selection.end - cursor.selection.start
|
|
39
|
+
} chars)`;
|
|
40
|
+
} else {
|
|
41
|
+
msg = `Cursor at byte position ${cursor.position} (no selection)`;
|
|
42
|
+
}
|
|
43
|
+
editor.setStatus(msg);
|
|
44
|
+
} else {
|
|
45
|
+
editor.setStatus("No cursor info available");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
editor.registerCommand(
|
|
50
|
+
"Query Demo: Show Cursor Position",
|
|
51
|
+
"Display cursor position and selection info",
|
|
52
|
+
"show_cursor_info_demo",
|
|
53
|
+
"normal"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Count all cursors (multi-cursor support)
|
|
57
|
+
globalThis.count_cursors_demo = function(): void {
|
|
58
|
+
const cursors = editor.getAllCursors();
|
|
59
|
+
editor.setStatus(`Active cursors: ${cursors.length}`);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
editor.registerCommand(
|
|
63
|
+
"Query Demo: Count All Cursors",
|
|
64
|
+
"Display the number of active cursors",
|
|
65
|
+
"count_cursors_demo",
|
|
66
|
+
"normal"
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// List all buffers
|
|
70
|
+
globalThis.list_all_buffers_demo = function(): void {
|
|
71
|
+
const buffers = editor.listBuffers();
|
|
72
|
+
let modifiedCount = 0;
|
|
73
|
+
|
|
74
|
+
for (const buf of buffers) {
|
|
75
|
+
if (buf.modified) {
|
|
76
|
+
modifiedCount++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
editor.setStatus(`Open buffers: ${buffers.length} (${modifiedCount} modified)`);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
editor.registerCommand(
|
|
84
|
+
"Query Demo: List All Buffers",
|
|
85
|
+
"Show count of open buffers",
|
|
86
|
+
"list_all_buffers_demo",
|
|
87
|
+
"normal"
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Show viewport info
|
|
91
|
+
globalThis.show_viewport_demo = function(): void {
|
|
92
|
+
const vp = editor.getViewport();
|
|
93
|
+
|
|
94
|
+
if (vp) {
|
|
95
|
+
const msg = `Viewport: ${vp.width}x${vp.height}, top_byte=${vp.top_byte}, left_col=${vp.left_column}`;
|
|
96
|
+
editor.setStatus(msg);
|
|
97
|
+
} else {
|
|
98
|
+
editor.setStatus("No viewport info available");
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
editor.registerCommand(
|
|
103
|
+
"Query Demo: Show Viewport Info",
|
|
104
|
+
"Display viewport dimensions and scroll position",
|
|
105
|
+
"show_viewport_demo",
|
|
106
|
+
"normal"
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
editor.setStatus("Buffer Query Demo plugin loaded! Try the 'Query Demo' commands.");
|
|
110
|
+
editor.debug("Buffer Query Demo plugin initialized (TypeScript version)");
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/// <reference path="../../types/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Git Grep TypeScript Plugin for Fresh Editor
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates async process spawning with native Promises:
|
|
7
|
+
* - Uses editor.spawnProcess() for async git operations
|
|
8
|
+
* - Parses git grep output and displays results
|
|
9
|
+
* - Shows file opening with line/column positioning
|
|
10
|
+
* - Registers multiple commands on plugin load
|
|
11
|
+
*
|
|
12
|
+
* This is a significant improvement over the Lua version because:
|
|
13
|
+
* - Native async/await instead of callback-based pattern
|
|
14
|
+
* - Cleaner error handling with try/catch
|
|
15
|
+
* - Type safety from TypeScript
|
|
16
|
+
* - Better state management with closures
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Store search results for navigation
|
|
20
|
+
interface GrepMatch {
|
|
21
|
+
file: string;
|
|
22
|
+
line: number;
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let searchResults: GrepMatch[] = [];
|
|
27
|
+
let currentResultIndex = 0;
|
|
28
|
+
|
|
29
|
+
// Parse git grep output into structured results
|
|
30
|
+
function parseGitGrepOutput(output: string): GrepMatch[] {
|
|
31
|
+
const matches: GrepMatch[] = [];
|
|
32
|
+
const lines = output.split("\n").filter((line) => line.trim());
|
|
33
|
+
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
// git grep output format: file:line:text
|
|
36
|
+
const match = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
37
|
+
if (match) {
|
|
38
|
+
matches.push({
|
|
39
|
+
file: match[1],
|
|
40
|
+
line: parseInt(match[2], 10),
|
|
41
|
+
text: match[3].trim(),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return matches;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Action: Search for pattern in repository
|
|
50
|
+
globalThis.git_grep_search = async function (): Promise<void> {
|
|
51
|
+
// For now, search for a hardcoded pattern
|
|
52
|
+
// In a full implementation, this would use a prompt
|
|
53
|
+
const pattern = "TODO";
|
|
54
|
+
|
|
55
|
+
editor.setStatus(`Searching for "${pattern}"...`);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await editor.spawnProcess("git", [
|
|
59
|
+
"grep",
|
|
60
|
+
"-n", // Show line numbers
|
|
61
|
+
"-I", // Skip binary files
|
|
62
|
+
pattern,
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
if (result.exit_code === 0 && result.stdout.trim()) {
|
|
66
|
+
searchResults = parseGitGrepOutput(result.stdout);
|
|
67
|
+
currentResultIndex = 0;
|
|
68
|
+
|
|
69
|
+
if (searchResults.length > 0) {
|
|
70
|
+
editor.setStatus(
|
|
71
|
+
`Found ${searchResults.length} matches for "${pattern}"`
|
|
72
|
+
);
|
|
73
|
+
editor.debug(`Git grep results: ${JSON.stringify(searchResults)}`);
|
|
74
|
+
|
|
75
|
+
// Jump to first result
|
|
76
|
+
const first = searchResults[0];
|
|
77
|
+
editor.openFile(first.file, first.line, 1);
|
|
78
|
+
editor.setStatus(
|
|
79
|
+
`[1/${searchResults.length}] ${first.file}:${first.line}: ${first.text}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
} else if (result.exit_code === 1) {
|
|
83
|
+
// git grep returns 1 when no matches found
|
|
84
|
+
editor.setStatus(`No matches found for "${pattern}"`);
|
|
85
|
+
searchResults = [];
|
|
86
|
+
} else {
|
|
87
|
+
editor.setStatus(`Git grep error: ${result.stderr || "Unknown error"}`);
|
|
88
|
+
editor.debug(`Git grep failed with exit code ${result.exit_code}`);
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
editor.setStatus(`Git grep failed: ${error}`);
|
|
92
|
+
editor.debug(`Git grep exception: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Action: Go to next search result
|
|
97
|
+
globalThis.git_grep_next = function (): void {
|
|
98
|
+
if (searchResults.length === 0) {
|
|
99
|
+
editor.setStatus("No search results. Run git_grep_search first.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
currentResultIndex = (currentResultIndex + 1) % searchResults.length;
|
|
104
|
+
const result = searchResults[currentResultIndex];
|
|
105
|
+
|
|
106
|
+
editor.openFile(result.file, result.line, 1);
|
|
107
|
+
editor.setStatus(
|
|
108
|
+
`[${currentResultIndex + 1}/${searchResults.length}] ${result.file}:${result.line}: ${result.text}`
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Action: Go to previous search result
|
|
113
|
+
globalThis.git_grep_prev = function (): void {
|
|
114
|
+
if (searchResults.length === 0) {
|
|
115
|
+
editor.setStatus("No search results. Run git_grep_search first.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
currentResultIndex =
|
|
120
|
+
(currentResultIndex - 1 + searchResults.length) % searchResults.length;
|
|
121
|
+
const result = searchResults[currentResultIndex];
|
|
122
|
+
|
|
123
|
+
editor.openFile(result.file, result.line, 1);
|
|
124
|
+
editor.setStatus(
|
|
125
|
+
`[${currentResultIndex + 1}/${searchResults.length}] ${result.file}:${result.line}: ${result.text}`
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Action: Show current git status
|
|
130
|
+
globalThis.git_status = async function (): Promise<void> {
|
|
131
|
+
editor.setStatus("Getting git status...");
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const result = await editor.spawnProcess("git", ["status", "--short"]);
|
|
135
|
+
|
|
136
|
+
if (result.exit_code === 0) {
|
|
137
|
+
const lines = result.stdout.trim().split("\n").filter((l) => l);
|
|
138
|
+
if (lines.length === 0) {
|
|
139
|
+
editor.setStatus("Git: Clean working directory");
|
|
140
|
+
} else {
|
|
141
|
+
editor.setStatus(`Git: ${lines.length} changed file(s)`);
|
|
142
|
+
editor.debug(`Git status:\n${result.stdout}`);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
editor.setStatus(`Not a git repository or git error`);
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
editor.setStatus(`Git status failed: ${error}`);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Action: Show current branch
|
|
153
|
+
globalThis.git_branch = async function (): Promise<void> {
|
|
154
|
+
try {
|
|
155
|
+
const result = await editor.spawnProcess("git", [
|
|
156
|
+
"rev-parse",
|
|
157
|
+
"--abbrev-ref",
|
|
158
|
+
"HEAD",
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
if (result.exit_code === 0) {
|
|
162
|
+
const branch = result.stdout.trim();
|
|
163
|
+
editor.setStatus(`Git branch: ${branch}`);
|
|
164
|
+
} else {
|
|
165
|
+
editor.setStatus("Not a git repository");
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
editor.setStatus(`Git branch failed: ${error}`);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Action: Show recent commits
|
|
173
|
+
globalThis.git_log = async function (): Promise<void> {
|
|
174
|
+
editor.setStatus("Fetching recent commits...");
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const result = await editor.spawnProcess("git", [
|
|
178
|
+
"log",
|
|
179
|
+
"--oneline",
|
|
180
|
+
"-10", // Last 10 commits
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
if (result.exit_code === 0) {
|
|
184
|
+
const lines = result.stdout.trim().split("\n");
|
|
185
|
+
editor.setStatus(`Git: ${lines.length} recent commits`);
|
|
186
|
+
editor.debug(`Recent commits:\n${result.stdout}`);
|
|
187
|
+
|
|
188
|
+
// Show first commit in status
|
|
189
|
+
if (lines.length > 0) {
|
|
190
|
+
editor.setStatus(`Latest: ${lines[0]}`);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
editor.setStatus("Git log failed");
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
editor.setStatus(`Git log failed: ${error}`);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Register commands on plugin load
|
|
201
|
+
editor.registerCommand(
|
|
202
|
+
"Git Grep: Search TODOs",
|
|
203
|
+
"Search for TODO comments in the repository",
|
|
204
|
+
"git_grep_search",
|
|
205
|
+
"normal"
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
editor.registerCommand(
|
|
209
|
+
"Git Grep: Next Result",
|
|
210
|
+
"Jump to next search result",
|
|
211
|
+
"git_grep_next",
|
|
212
|
+
"normal"
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
editor.registerCommand(
|
|
216
|
+
"Git Grep: Previous Result",
|
|
217
|
+
"Jump to previous search result",
|
|
218
|
+
"git_grep_prev",
|
|
219
|
+
"normal"
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
editor.registerCommand(
|
|
223
|
+
"Git: Show Status",
|
|
224
|
+
"Display git status summary",
|
|
225
|
+
"git_status",
|
|
226
|
+
"" // Available in all contexts
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
editor.registerCommand(
|
|
230
|
+
"Git: Show Branch",
|
|
231
|
+
"Display current git branch",
|
|
232
|
+
"git_branch",
|
|
233
|
+
""
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
editor.registerCommand(
|
|
237
|
+
"Git: Recent Commits",
|
|
238
|
+
"Show recent commit history",
|
|
239
|
+
"git_log",
|
|
240
|
+
""
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Plugin initialized
|
|
244
|
+
editor.setStatus("Git Grep plugin loaded - 6 commands registered");
|
|
245
|
+
editor.debug("Git Grep TypeScript plugin initialized");
|
|
246
|
+
|
|
247
|
+
// Automatically show git branch on load
|
|
248
|
+
(async () => {
|
|
249
|
+
try {
|
|
250
|
+
const result = await editor.spawnProcess("git", [
|
|
251
|
+
"rev-parse",
|
|
252
|
+
"--abbrev-ref",
|
|
253
|
+
"HEAD",
|
|
254
|
+
]);
|
|
255
|
+
if (result.exit_code === 0) {
|
|
256
|
+
const branch = result.stdout.trim();
|
|
257
|
+
editor.setStatus(`Git Grep plugin ready | Branch: ${branch}`);
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// Silently fail if not in a git repo
|
|
261
|
+
}
|
|
262
|
+
})();
|