@fresh-editor/fresh-editor 0.1.12 → 0.1.13

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.
@@ -23,7 +23,7 @@
23
23
  "hasInstallScript": true,
24
24
  "license": "GPL-2.0",
25
25
  "name": "@fresh-editor/fresh-editor",
26
- "version": "0.1.12"
26
+ "version": "0.1.13"
27
27
  },
28
28
  "node_modules/@isaacs/balanced-match": {
29
29
  "engines": {
@@ -896,5 +896,5 @@
896
896
  }
897
897
  },
898
898
  "requires": true,
899
- "version": "0.1.12"
899
+ "version": "0.1.13"
900
900
  }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.12",
2
+ "artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.13",
3
3
  "author": "Noam Lewis",
4
4
  "bin": {
5
5
  "fresh": "run-fresh.js"
@@ -92,7 +92,7 @@
92
92
  "zipExt": ".tar.xz"
93
93
  }
94
94
  },
95
- "version": "0.1.12",
95
+ "version": "0.1.13",
96
96
  "volta": {
97
97
  "node": "18.14.1",
98
98
  "npm": "9.5.0"
@@ -0,0 +1,245 @@
1
+ /// <reference path="../types/fresh.d.ts" />
2
+
3
+ /**
4
+ * Buffer Modified Plugin
5
+ *
6
+ * Shows indicators in the gutter for lines that have been modified since the last save.
7
+ * This tracks in-memory changes, not git changes.
8
+ *
9
+ * This plugin uses a simpler approach: it marks lines as modified when edits happen
10
+ * (after_insert/after_delete hooks), and clears all modified markers on save.
11
+ * It doesn't compare content - it just tracks which lines have been touched since save.
12
+ *
13
+ * Indicator symbols:
14
+ * - │ (blue): Line has been modified since last save
15
+ */
16
+
17
+ // =============================================================================
18
+ // Constants
19
+ // =============================================================================
20
+
21
+ const NAMESPACE = "buffer-modified";
22
+ const PRIORITY = 5; // Lower than git-gutter (10) and diagnostics
23
+
24
+ // Colors (RGB) - Blue to distinguish from git gutter (green/yellow/red)
25
+ const COLOR = [100, 149, 237] as [number, number, number]; // Cornflower blue
26
+
27
+ // Symbol
28
+ const SYMBOL = "│";
29
+
30
+ // =============================================================================
31
+ // Types
32
+ // =============================================================================
33
+
34
+ interface BufferState {
35
+ /** Whether we're tracking this buffer */
36
+ tracking: boolean;
37
+ }
38
+
39
+ // =============================================================================
40
+ // State
41
+ // =============================================================================
42
+
43
+ /** State per buffer */
44
+ const bufferStates: Map<number, BufferState> = new Map();
45
+
46
+ // =============================================================================
47
+ // Line Tracking
48
+ // =============================================================================
49
+
50
+ /**
51
+ * Initialize state for a buffer (on file open)
52
+ * Starts with no modified lines since file was just loaded
53
+ */
54
+ function initBufferState(bufferId: number): void {
55
+ bufferStates.set(bufferId, {
56
+ tracking: true,
57
+ });
58
+ // Clear any leftover indicators
59
+ editor.clearLineIndicators(bufferId, NAMESPACE);
60
+ }
61
+
62
+ /**
63
+ * Clear modified state for a buffer (on save)
64
+ * Removes all modified markers since buffer now matches disk
65
+ */
66
+ function clearModifiedState(bufferId: number): void {
67
+ editor.clearLineIndicators(bufferId, NAMESPACE);
68
+ }
69
+
70
+ /**
71
+ * Mark a range of lines as modified and set indicators
72
+ *
73
+ * Note: The indicator markers automatically track their byte positions,
74
+ * so we don't need to manually track which lines are modified - we just
75
+ * set indicators and they'll stay on the correct lines as edits happen.
76
+ */
77
+ function markLinesModified(bufferId: number, startLine: number, endLine: number): void {
78
+ const state = bufferStates.get(bufferId);
79
+ if (!state || !state.tracking) return;
80
+
81
+ // Add indicator for each affected line
82
+ // Note: If an indicator already exists at this position, it will be updated
83
+ for (let line = startLine; line <= endLine; line++) {
84
+ editor.setLineIndicator(
85
+ bufferId,
86
+ line,
87
+ NAMESPACE,
88
+ SYMBOL,
89
+ COLOR[0],
90
+ COLOR[1],
91
+ COLOR[2],
92
+ PRIORITY
93
+ );
94
+ }
95
+ }
96
+
97
+ // =============================================================================
98
+ // Event Handlers
99
+ // =============================================================================
100
+
101
+ /**
102
+ * Handle after file open - initialize state
103
+ */
104
+ globalThis.onBufferModifiedAfterFileOpen = function (args: {
105
+ buffer_id: number;
106
+ path: string;
107
+ }): boolean {
108
+ const bufferId = args.buffer_id;
109
+
110
+ if (!args.path || args.path === "") {
111
+ return true;
112
+ }
113
+
114
+ // Initialize tracking - file just loaded, no modifications yet
115
+ initBufferState(bufferId);
116
+ editor.debug(`Buffer Modified: initialized for ${args.path}`);
117
+
118
+ return true;
119
+ };
120
+
121
+ /**
122
+ * Handle buffer activation - ensure we're tracking
123
+ */
124
+ globalThis.onBufferModifiedBufferActivated = function (args: {
125
+ buffer_id: number;
126
+ }): boolean {
127
+ const bufferId = args.buffer_id;
128
+
129
+ // If we don't have state yet, initialize it
130
+ if (!bufferStates.has(bufferId)) {
131
+ const filePath = editor.getBufferPath(bufferId);
132
+ if (filePath && filePath !== "") {
133
+ initBufferState(bufferId);
134
+ }
135
+ }
136
+
137
+ return true;
138
+ };
139
+
140
+ /**
141
+ * Handle after file save - clear modified state
142
+ */
143
+ globalThis.onBufferModifiedAfterSave = function (args: {
144
+ buffer_id: number;
145
+ path: string;
146
+ }): boolean {
147
+ const bufferId = args.buffer_id;
148
+
149
+ // Clear all modified markers - buffer now matches disk
150
+ clearModifiedState(bufferId);
151
+ editor.debug("Buffer Modified: cleared on save");
152
+
153
+ return true;
154
+ };
155
+
156
+ /**
157
+ * Handle after insert - mark affected lines as modified
158
+ *
159
+ * Note: Line indicators automatically track position changes via byte-position markers.
160
+ * We only need to add new indicators for the modified lines; existing indicators
161
+ * will automatically shift to stay on the correct lines.
162
+ */
163
+ globalThis.onBufferModifiedAfterInsert = function (args: {
164
+ buffer_id: number;
165
+ position: number;
166
+ text: string;
167
+ affected_start: number;
168
+ affected_end: number;
169
+ start_line: number;
170
+ end_line: number;
171
+ lines_added: number;
172
+ }): boolean {
173
+ const bufferId = args.buffer_id;
174
+
175
+ if (!bufferStates.has(bufferId)) {
176
+ return true;
177
+ }
178
+
179
+ // Mark all affected lines (from start_line to end_line inclusive)
180
+ // The indicator markers will automatically track their positions
181
+ markLinesModified(bufferId, args.start_line, args.end_line);
182
+
183
+ return true;
184
+ };
185
+
186
+ /**
187
+ * Handle after delete - mark affected line as modified
188
+ *
189
+ * Note: Line indicators automatically track position changes via byte-position markers.
190
+ * Markers within deleted ranges are automatically removed. We only need to mark the
191
+ * line where the deletion occurred.
192
+ */
193
+ globalThis.onBufferModifiedAfterDelete = function (args: {
194
+ buffer_id: number;
195
+ range: { start: number; end: number };
196
+ deleted_text: string;
197
+ affected_start: number;
198
+ deleted_len: number;
199
+ start_line: number;
200
+ end_line: number;
201
+ lines_removed: number;
202
+ }): boolean {
203
+ const bufferId = args.buffer_id;
204
+
205
+ if (!bufferStates.has(bufferId)) {
206
+ return true;
207
+ }
208
+
209
+ // Mark the line where deletion occurred
210
+ // Markers for deleted lines are automatically cleaned up
211
+ markLinesModified(bufferId, args.start_line, args.start_line);
212
+
213
+ return true;
214
+ };
215
+
216
+ /**
217
+ * Handle buffer closed - cleanup state
218
+ */
219
+ globalThis.onBufferModifiedBufferClosed = function (args: {
220
+ buffer_id: number;
221
+ }): boolean {
222
+ bufferStates.delete(args.buffer_id);
223
+ return true;
224
+ };
225
+
226
+ // =============================================================================
227
+ // Registration
228
+ // =============================================================================
229
+
230
+ // Register event handlers
231
+ editor.on("after_file_open", "onBufferModifiedAfterFileOpen");
232
+ editor.on("buffer_activated", "onBufferModifiedBufferActivated");
233
+ editor.on("after_file_save", "onBufferModifiedAfterSave");
234
+ editor.on("after-insert", "onBufferModifiedAfterInsert");
235
+ editor.on("after-delete", "onBufferModifiedAfterDelete");
236
+ editor.on("buffer_closed", "onBufferModifiedBufferClosed");
237
+
238
+ // Initialize for the current buffer
239
+ const initBufferId = editor.getActiveBufferId();
240
+ const initPath = editor.getBufferPath(initBufferId);
241
+ if (initPath && initPath !== "") {
242
+ initBufferState(initBufferId);
243
+ }
244
+
245
+ editor.debug("Buffer Modified plugin loaded");
@@ -0,0 +1,475 @@
1
+ /// <reference path="../types/fresh.d.ts" />
2
+
3
+ /**
4
+ * Git Gutter Plugin
5
+ *
6
+ * Shows git diff indicators in the gutter for modified, added, and deleted lines.
7
+ * Uses `git diff` to compare the current buffer content against the index (staged changes)
8
+ * or HEAD if nothing is staged.
9
+ *
10
+ * Indicator symbols:
11
+ * - │ (green): Added line
12
+ * - │ (yellow): Modified line
13
+ * - ▾ (red): Deleted line(s) below
14
+ */
15
+
16
+ // =============================================================================
17
+ // Constants
18
+ // =============================================================================
19
+
20
+ const NAMESPACE = "git-gutter";
21
+ const PRIORITY = 10; // Lower than diagnostics
22
+
23
+ // Colors (RGB)
24
+ const COLORS = {
25
+ added: [80, 250, 123] as [number, number, number], // Green
26
+ modified: [255, 184, 108] as [number, number, number], // Orange/Yellow
27
+ deleted: [255, 85, 85] as [number, number, number], // Red
28
+ };
29
+
30
+ // Symbols
31
+ const SYMBOLS = {
32
+ added: "│",
33
+ modified: "│",
34
+ deleted: "▾",
35
+ };
36
+
37
+ // =============================================================================
38
+ // Types
39
+ // =============================================================================
40
+
41
+ interface DiffHunk {
42
+ /** Type of change */
43
+ type: "added" | "modified" | "deleted";
44
+ /** Starting line number in the new file (1-indexed) */
45
+ startLine: number;
46
+ /** Number of lines affected */
47
+ lineCount: number;
48
+ }
49
+
50
+ interface BufferGitState {
51
+ /** File path for this buffer */
52
+ filePath: string;
53
+ /** Last known hunks for this buffer */
54
+ hunks: DiffHunk[];
55
+ /** Whether we're currently updating */
56
+ updating: boolean;
57
+ }
58
+
59
+ // =============================================================================
60
+ // State
61
+ // =============================================================================
62
+
63
+ /** Git state per buffer */
64
+ const bufferStates: Map<number, BufferGitState> = new Map();
65
+
66
+
67
+ // =============================================================================
68
+ // Git Diff Parsing
69
+ // =============================================================================
70
+
71
+ /**
72
+ * Parse unified diff output to extract hunks
73
+ * Unified diff format:
74
+ * @@ -start,count +start,count @@
75
+ */
76
+ function parseDiffOutput(diffOutput: string): DiffHunk[] {
77
+ const hunks: DiffHunk[] = [];
78
+ const lines = diffOutput.split("\n");
79
+
80
+ let currentOldLine = 0;
81
+ let currentNewLine = 0;
82
+ let inHunk = false;
83
+ let addedStart = 0;
84
+ let addedCount = 0;
85
+ let modifiedStart = 0;
86
+ let modifiedCount = 0;
87
+ let deletedAtLine = 0;
88
+ let deletedCount = 0;
89
+
90
+ const flushAdded = () => {
91
+ if (addedCount > 0) {
92
+ hunks.push({ type: "added", startLine: addedStart, lineCount: addedCount });
93
+ addedCount = 0;
94
+ }
95
+ };
96
+
97
+ const flushModified = () => {
98
+ if (modifiedCount > 0) {
99
+ hunks.push({ type: "modified", startLine: modifiedStart, lineCount: modifiedCount });
100
+ modifiedCount = 0;
101
+ }
102
+ };
103
+
104
+ const flushDeleted = () => {
105
+ if (deletedCount > 0) {
106
+ // Deleted lines are shown as a marker on the line after the deletion
107
+ hunks.push({ type: "deleted", startLine: deletedAtLine, lineCount: deletedCount });
108
+ deletedCount = 0;
109
+ }
110
+ };
111
+
112
+ for (const line of lines) {
113
+ // Match hunk header: @@ -old_start,old_count +new_start,new_count @@
114
+ const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
115
+ if (hunkMatch) {
116
+ // Flush any pending changes from previous hunk
117
+ flushAdded();
118
+ flushModified();
119
+ flushDeleted();
120
+
121
+ currentOldLine = parseInt(hunkMatch[1], 10);
122
+ currentNewLine = parseInt(hunkMatch[3], 10);
123
+ inHunk = true;
124
+ continue;
125
+ }
126
+
127
+ if (!inHunk) continue;
128
+
129
+ if (line.startsWith("+") && !line.startsWith("+++")) {
130
+ // Added line
131
+ if (deletedCount > 0) {
132
+ // If there were deletions right before, this is a modification
133
+ if (modifiedCount === 0) {
134
+ modifiedStart = currentNewLine;
135
+ }
136
+ modifiedCount++;
137
+ deletedCount--;
138
+ } else {
139
+ // Pure addition
140
+ if (addedCount === 0) {
141
+ addedStart = currentNewLine;
142
+ }
143
+ addedCount++;
144
+ }
145
+ currentNewLine++;
146
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
147
+ // Deleted line - flush any pending additions first
148
+ flushAdded();
149
+
150
+ if (deletedCount === 0) {
151
+ deletedAtLine = currentNewLine;
152
+ }
153
+ deletedCount++;
154
+ currentOldLine++;
155
+ } else if (line.startsWith(" ")) {
156
+ // Context line (unchanged)
157
+ flushAdded();
158
+ flushModified();
159
+ flushDeleted();
160
+ currentOldLine++;
161
+ currentNewLine++;
162
+ } else if (line === "\") {
163
+ // Ignore this marker
164
+ continue;
165
+ }
166
+ }
167
+
168
+ // Flush any remaining changes
169
+ flushAdded();
170
+ flushModified();
171
+ flushDeleted();
172
+
173
+ return hunks;
174
+ }
175
+
176
+ // =============================================================================
177
+ // Git Operations
178
+ // =============================================================================
179
+
180
+ /**
181
+ * Get the directory containing a file
182
+ */
183
+ function getFileDirectory(filePath: string): string {
184
+ const lastSlash = filePath.lastIndexOf("/");
185
+ if (lastSlash > 0) {
186
+ return filePath.substring(0, lastSlash);
187
+ }
188
+ return ".";
189
+ }
190
+
191
+ /**
192
+ * Check if a file is tracked by git
193
+ */
194
+ async function isGitTracked(filePath: string): Promise<boolean> {
195
+ const cwd = getFileDirectory(filePath);
196
+ const result = await editor.spawnProcess("git", ["ls-files", "--error-unmatch", filePath], cwd);
197
+ return result.exit_code === 0;
198
+ }
199
+
200
+ /**
201
+ * Get git diff for a file
202
+ * Compares working tree against HEAD to show all uncommitted changes
203
+ * (both staged and unstaged)
204
+ */
205
+ async function getGitDiff(filePath: string): Promise<string> {
206
+ const cwd = getFileDirectory(filePath);
207
+
208
+ // Diff against HEAD to show all changes (staged + unstaged) vs last commit
209
+ const result = await editor.spawnProcess("git", [
210
+ "diff",
211
+ "HEAD",
212
+ "--no-color",
213
+ "--unified=0", // No context lines for cleaner parsing
214
+ "--",
215
+ filePath,
216
+ ], cwd);
217
+
218
+ // Exit code 0 = no differences, 1 = differences found, >1 = error
219
+ if (result.exit_code <= 1) {
220
+ return result.stdout;
221
+ }
222
+
223
+ return "";
224
+ }
225
+
226
+ // =============================================================================
227
+ // Indicator Management
228
+ // =============================================================================
229
+
230
+ /**
231
+ * Update git gutter indicators for a buffer
232
+ */
233
+ async function updateGitGutter(bufferId: number): Promise<void> {
234
+ const state = bufferStates.get(bufferId);
235
+ if (!state || state.updating) return;
236
+
237
+ state.updating = true;
238
+
239
+ try {
240
+ editor.debug(`Git Gutter: updating for ${state.filePath}`);
241
+
242
+ // Check if file is git tracked
243
+ const tracked = await isGitTracked(state.filePath);
244
+ if (!tracked) {
245
+ // Clear indicators for non-tracked files
246
+ editor.debug("Git Gutter: file not tracked by git");
247
+ editor.clearLineIndicators(bufferId, NAMESPACE);
248
+ state.hunks = [];
249
+ return;
250
+ }
251
+
252
+ editor.debug("Git Gutter: file is tracked, getting diff...");
253
+
254
+ // Get diff
255
+ const diffOutput = await getGitDiff(state.filePath);
256
+ editor.debug(`Git Gutter: diff output length = ${diffOutput.length}`);
257
+ if (diffOutput.length > 0 && diffOutput.length < 500) {
258
+ editor.debug(`Git Gutter: diff = ${diffOutput.replace(/\n/g, "\\n")}`);
259
+ }
260
+ const hunks = parseDiffOutput(diffOutput);
261
+ editor.debug(`Git Gutter: parsed ${hunks.length} hunks`);
262
+
263
+ // Clear existing indicators
264
+ editor.clearLineIndicators(bufferId, NAMESPACE);
265
+
266
+ // Apply new indicators
267
+ for (const hunk of hunks) {
268
+ const color = COLORS[hunk.type];
269
+ const symbol = SYMBOLS[hunk.type];
270
+
271
+ if (hunk.type === "deleted") {
272
+ // Deleted indicator shows on a single line
273
+ // Line numbers are 1-indexed in diff, but 0-indexed in editor
274
+ const line = Math.max(0, hunk.startLine - 1);
275
+ editor.setLineIndicator(
276
+ bufferId,
277
+ line,
278
+ NAMESPACE,
279
+ symbol,
280
+ color[0],
281
+ color[1],
282
+ color[2],
283
+ PRIORITY
284
+ );
285
+ } else {
286
+ // Added/modified indicators show on each affected line
287
+ for (let i = 0; i < hunk.lineCount; i++) {
288
+ // Line numbers are 1-indexed in diff, but 0-indexed in editor
289
+ const line = hunk.startLine - 1 + i;
290
+ editor.setLineIndicator(
291
+ bufferId,
292
+ line,
293
+ NAMESPACE,
294
+ symbol,
295
+ color[0],
296
+ color[1],
297
+ color[2],
298
+ PRIORITY
299
+ );
300
+ }
301
+ }
302
+ }
303
+
304
+ state.hunks = hunks;
305
+ } finally {
306
+ state.updating = false;
307
+ }
308
+ }
309
+
310
+
311
+ // =============================================================================
312
+ // Event Handlers
313
+ // =============================================================================
314
+
315
+ /**
316
+ * Handle after file open - initialize git state and update indicators
317
+ */
318
+ globalThis.onGitGutterAfterFileOpen = function (args: {
319
+ buffer_id: number;
320
+ path: string;
321
+ }): boolean {
322
+ const bufferId = args.buffer_id;
323
+ const filePath = args.path;
324
+
325
+ if (!filePath || filePath === "") {
326
+ return true;
327
+ }
328
+
329
+ // Initialize state for this buffer
330
+ bufferStates.set(bufferId, {
331
+ filePath,
332
+ hunks: [],
333
+ updating: false,
334
+ });
335
+
336
+ // Update immediately (no debounce for file open)
337
+ updateGitGutter(bufferId);
338
+
339
+ return true;
340
+ };
341
+
342
+ /**
343
+ * Handle buffer activation - update if we have state but indicators might be stale
344
+ */
345
+ globalThis.onGitGutterBufferActivated = function (args: {
346
+ buffer_id: number;
347
+ }): boolean {
348
+ const bufferId = args.buffer_id;
349
+
350
+ // If we don't have state yet, try to initialize from buffer path
351
+ if (!bufferStates.has(bufferId)) {
352
+ const filePath = editor.getBufferPath(bufferId);
353
+ if (filePath && filePath !== "") {
354
+ bufferStates.set(bufferId, {
355
+ filePath,
356
+ hunks: [],
357
+ updating: false,
358
+ });
359
+ updateGitGutter(bufferId);
360
+ }
361
+ }
362
+ // If we already have state, the indicators should be current
363
+ // (they update on file open and save)
364
+
365
+ return true;
366
+ };
367
+
368
+ /**
369
+ * Handle after file save - refresh indicators
370
+ */
371
+ globalThis.onGitGutterAfterSave = function (args: {
372
+ buffer_id: number;
373
+ path: string;
374
+ }): boolean {
375
+ const bufferId = args.buffer_id;
376
+
377
+ // Update state with new path (in case of save-as)
378
+ const state = bufferStates.get(bufferId);
379
+ if (state) {
380
+ state.filePath = args.path;
381
+ } else {
382
+ bufferStates.set(bufferId, {
383
+ filePath: args.path,
384
+ hunks: [],
385
+ updating: false,
386
+ });
387
+ }
388
+
389
+ // Update immediately after save (no debounce)
390
+ updateGitGutter(bufferId);
391
+
392
+ return true;
393
+ };
394
+
395
+ // Note: Git diff compares the file on disk, not the in-memory buffer.
396
+ // Line indicators automatically track position changes via byte-position markers.
397
+ // A full re-diff happens on save. For unsaved changes, see buffer_modified plugin.
398
+
399
+ /**
400
+ * Handle buffer closed - cleanup state
401
+ */
402
+ globalThis.onGitGutterBufferClosed = function (args: {
403
+ buffer_id: number;
404
+ }): boolean {
405
+ bufferStates.delete(args.buffer_id);
406
+ return true;
407
+ };
408
+
409
+ // =============================================================================
410
+ // Commands
411
+ // =============================================================================
412
+
413
+ /**
414
+ * Manually refresh git gutter for the current buffer
415
+ */
416
+ globalThis.git_gutter_refresh = function (): void {
417
+ const bufferId = editor.getActiveBufferId();
418
+ const filePath = editor.getBufferPath(bufferId);
419
+
420
+ if (!filePath || filePath === "") {
421
+ editor.setStatus("Git Gutter: No file open");
422
+ return;
423
+ }
424
+
425
+ // Ensure state exists
426
+ if (!bufferStates.has(bufferId)) {
427
+ bufferStates.set(bufferId, {
428
+ filePath,
429
+ hunks: [],
430
+ updating: false,
431
+ });
432
+ }
433
+
434
+ // Force immediate update
435
+ updateGitGutter(bufferId).then(() => {
436
+ const state = bufferStates.get(bufferId);
437
+ const count = state?.hunks.length || 0;
438
+ editor.setStatus(`Git Gutter: ${count} change${count !== 1 ? "s" : ""} detected`);
439
+ });
440
+ };
441
+
442
+ // =============================================================================
443
+ // Registration
444
+ // =============================================================================
445
+
446
+ // Register event handlers
447
+ // Note: No need to register after-insert/after-delete hooks - indicators
448
+ // automatically track position changes via byte-position markers in the editor.
449
+ editor.on("after_file_open", "onGitGutterAfterFileOpen");
450
+ editor.on("buffer_activated", "onGitGutterBufferActivated");
451
+ editor.on("after_file_save", "onGitGutterAfterSave");
452
+ editor.on("buffer_closed", "onGitGutterBufferClosed");
453
+
454
+ // Register commands
455
+ editor.registerCommand(
456
+ "Git Gutter: Refresh",
457
+ "Refresh git gutter indicators for the current buffer",
458
+ "git_gutter_refresh",
459
+ "normal"
460
+ );
461
+
462
+ // Initialize for the current buffer
463
+ const initBufferId = editor.getActiveBufferId();
464
+ const initPath = editor.getBufferPath(initBufferId);
465
+ if (initPath && initPath !== "") {
466
+ bufferStates.set(initBufferId, {
467
+ filePath: initPath,
468
+ hunks: [],
469
+ updating: false,
470
+ });
471
+ updateGitGutter(initBufferId);
472
+ }
473
+
474
+ editor.debug("Git Gutter plugin loaded");
475
+ editor.setStatus("Git Gutter plugin ready");
@@ -447,6 +447,26 @@ interface EditorAPI {
447
447
  * @returns true if virtual line was added
448
448
  */
449
449
  addVirtualLine(buffer_id: number, position: number, text: string, fg_r: number, fg_g: number, fg_b: number, bg_r: i16, bg_g: i16, bg_b: i16, above: boolean, namespace: string, priority: number): boolean;
450
+ /**
451
+ * Set a line indicator in the gutter's indicator column
452
+ * @param buffer_id - The buffer ID
453
+ * @param line - Line number (0-indexed)
454
+ * @param namespace - Namespace for grouping (e.g., "git-gutter", "breakpoints")
455
+ * @param symbol - Symbol to display (e.g., "│", "●", "★")
456
+ * @param r - Red color component (0-255)
457
+ * @param g - Green color component (0-255)
458
+ * @param b - Blue color component (0-255)
459
+ * @param priority - Priority for display when multiple indicators exist (higher wins)
460
+ * @returns true if indicator was set
461
+ */
462
+ setLineIndicator(buffer_id: number, line: number, namespace: string, symbol: string, r: number, g: number, b: number, priority: number): boolean;
463
+ /**
464
+ * Clear all line indicators for a specific namespace
465
+ * @param buffer_id - The buffer ID
466
+ * @param namespace - Namespace to clear (e.g., "git-gutter")
467
+ * @returns true if indicators were cleared
468
+ */
469
+ clearLineIndicators(buffer_id: number, namespace: string): boolean;
450
470
  /**
451
471
  * Submit a transformed view stream for a viewport
452
472
  * @param buffer_id - Buffer to apply the transform to