@agnishc/edb-diff-files 0.6.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.2] - 2026-05-11
4
+
5
+ ## [0.8.1] - 2026-05-11
6
+
3
7
  ## [0.6.0] - 2026-05-11
4
8
 
5
9
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agnishc/edb-diff-files",
3
- "version": "0.6.1",
3
+ "version": "0.8.2",
4
4
  "description": "Pi extension: live widget tracking files changed this session with an inline diff viewer",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/index.ts CHANGED
@@ -56,29 +56,29 @@ class FilesViewComponent {
56
56
 
57
57
  private handleListInput(data: string): void {
58
58
  const n = this.entries.length;
59
- if (matchesKey(data, "j") || matchesKey(data, "down")) {
59
+ if (data === "j" || matchesKey(data, "down")) {
60
60
  this.cursor = n > 0 ? Math.min(this.cursor + 1, n - 1) : 0;
61
- this.invalidate();
62
- } else if (matchesKey(data, "k") || matchesKey(data, "up")) {
61
+ this.rerender();
62
+ } else if (data === "k" || matchesKey(data, "up")) {
63
63
  this.cursor = Math.max(0, this.cursor - 1);
64
- this.invalidate();
65
- } else if (matchesKey(data, "return")) {
64
+ this.rerender();
65
+ } else if (matchesKey(data, "enter")) {
66
66
  if (this.entries[this.cursor]) {
67
67
  this.mode = "diff";
68
68
  this.diffScroll = 0;
69
- this.invalidate();
69
+ this.rerender();
70
70
  }
71
- } else if (matchesKey(data, "o")) {
71
+ } else if (data === "o") {
72
72
  const entry = this.entries[this.cursor];
73
73
  if (entry) {
74
74
  this.statusMsg = this.openFileInEditor(entry.path);
75
- this.invalidate();
75
+ this.rerender();
76
76
  }
77
- } else if (matchesKey(data, "f")) {
77
+ } else if (data === "f") {
78
78
  const idx = FILTER_CYCLE.indexOf(this.filterMode);
79
79
  this.filterMode = FILTER_CYCLE[(idx + 1) % FILTER_CYCLE.length]!;
80
80
  this.cursor = 0;
81
- this.invalidate();
81
+ this.rerender();
82
82
  } else if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
83
83
  this.onClose();
84
84
  }
@@ -88,33 +88,32 @@ class FilesViewComponent {
88
88
  const viewH = this.viewportHeight();
89
89
  const maxScroll = Math.max(0, this.currentDiffLines().length - viewH);
90
90
 
91
- if (matchesKey(data, "j") || matchesKey(data, "down")) {
91
+ if (data === "j" || matchesKey(data, "down")) {
92
92
  this.diffScroll = Math.min(this.diffScroll + 1, maxScroll);
93
- this.invalidate();
94
- } else if (matchesKey(data, "k") || matchesKey(data, "up")) {
93
+ this.rerender();
94
+ } else if (data === "k" || matchesKey(data, "up")) {
95
95
  this.diffScroll = Math.max(0, this.diffScroll - 1);
96
- this.invalidate();
96
+ this.rerender();
97
97
  } else if (matchesKey(data, "ctrl+d")) {
98
98
  this.diffScroll = Math.min(this.diffScroll + Math.floor(viewH / 2), maxScroll);
99
- this.invalidate();
99
+ this.rerender();
100
100
  } else if (matchesKey(data, "ctrl+u")) {
101
101
  this.diffScroll = Math.max(0, this.diffScroll - Math.floor(viewH / 2));
102
- this.invalidate();
103
- } else if (matchesKey(data, "o")) {
102
+ this.rerender();
103
+ } else if (data === "o") {
104
104
  const entry = this.entries[this.cursor];
105
105
  if (entry) {
106
106
  this.statusMsg = this.openFileInEditor(entry.path);
107
- this.invalidate();
107
+ this.rerender();
108
108
  }
109
- } else if (matchesKey(data, "escape") || matchesKey(data, "q")) {
109
+ } else if (matchesKey(data, "escape") || data === "q") {
110
110
  this.mode = "list";
111
111
  this.statusMsg = "";
112
- this.invalidate();
112
+ this.rerender();
113
113
  }
114
114
  }
115
115
 
116
116
  private openFileInEditor(filePath: string): string {
117
- // Use same logic as pi core: respect $VISUAL / $EDITOR
118
117
  const editorCmd = process.env.VISUAL || process.env.EDITOR;
119
118
  if (!editorCmd) {
120
119
  this.statusMsg = "No editor configured (set $VISUAL or $EDITOR)";
@@ -122,19 +121,15 @@ class FilesViewComponent {
122
121
  }
123
122
 
124
123
  try {
125
- // Stop TUI to release terminal for interactive editor
126
124
  this.tui.stop();
127
125
 
128
- // Split by space to support editor arguments (e.g., "code --wait")
129
126
  const [editor, ...editorArgs] = editorCmd.split(" ");
130
127
 
131
- // Spawn editor synchronously with inherited stdio for interactive editing
132
128
  const result = spawnSync(editor, [...editorArgs, filePath], {
133
129
  stdio: "inherit",
134
130
  shell: process.platform === "win32",
135
131
  });
136
132
 
137
- // On non-zero exit, show status
138
133
  if (result.status !== 0) {
139
134
  this.statusMsg = `Editor exited with status ${result.status}`;
140
135
  } else {
@@ -143,9 +138,7 @@ class FilesViewComponent {
143
138
  } catch {
144
139
  this.statusMsg = "Failed to open editor";
145
140
  } finally {
146
- // Restart TUI
147
141
  this.tui.start();
148
- // Force full re-render since external editor uses alternate screen
149
142
  this.tui.requestRender(true);
150
143
  }
151
144
 
@@ -176,7 +169,6 @@ class FilesViewComponent {
176
169
  private renderList(width: number): string[] {
177
170
  const { entries, theme: th } = this;
178
171
  const visible = this.filteredEntries();
179
- // Clamp cursor to visible range
180
172
  const cursor = Math.min(this.cursor, Math.max(0, visible.length - 1));
181
173
  const lines: string[] = [];
182
174
 
@@ -318,7 +310,6 @@ class FilesViewComponent {
318
310
  }
319
311
 
320
312
  private viewportHeight(): number {
321
- // Terminal rows minus header (≈5) and footer (≈4) lines
322
313
  return Math.max(5, (process.stdout.rows ?? 40) - 9);
323
314
  }
324
315
 
@@ -326,6 +317,12 @@ class FilesViewComponent {
326
317
  this.cachedWidth = undefined;
327
318
  this.cachedLines = undefined;
328
319
  }
320
+
321
+ /** Invalidate cache and request TUI re-render. */
322
+ private rerender(): void {
323
+ this.invalidate();
324
+ this.tui.requestRender();
325
+ }
329
326
  }
330
327
 
331
328
  // ─── Extension ───────────────────────────────────────────────────────────────
@@ -374,7 +371,6 @@ export default function diffFilesExtension(pi: any): void {
374
371
  }
375
372
 
376
373
  // ── write/edit tool_call ──────────────────────────────────────────────
377
- // Record file paths the agent is about to touch
378
374
  pi.on("tool_call", async (event: any, _ctx: any) => {
379
375
  if (event.toolName === "write" || event.toolName === "edit") {
380
376
  const fp = event.input?.path ?? event.input?.file_path ?? "";
@@ -394,8 +390,6 @@ export default function diffFilesExtension(pi: any): void {
394
390
  return;
395
391
  }
396
392
 
397
- // ── bash tool_call ────────────────────────────────────────────────
398
- // Snapshot git state before the bash command runs
399
393
  if (event.toolName === "bash" && inGit) {
400
394
  const snapshot = getGitDiff(config.cwd);
401
395
  bashSnapshots.set(event.toolCallId, snapshot);
@@ -403,7 +397,6 @@ export default function diffFilesExtension(pi: any): void {
403
397
  });
404
398
 
405
399
  // ── bash tool_result ────────────────────────────────────────────────
406
- // Diff current state vs snapshot to find new changes
407
400
  pi.on("tool_result", async (event: any, _ctx: any) => {
408
401
  if (event.toolName !== "bash") return;
409
402
  if (event.isError) return;
@@ -414,14 +407,11 @@ export default function diffFilesExtension(pi: any): void {
414
407
 
415
408
  const after = getGitDiff(config.cwd);
416
409
 
417
- // Find entries in after that are new or changed compared to before
418
410
  const newEntries: GitDiffSnapshot = new Map();
419
411
  for (const [path, changeType] of after) {
420
412
  if (!before.has(path)) {
421
- // New entry — file wasn't in the before snapshot
422
413
  newEntries.set(path, changeType);
423
414
  } else if (before.get(path) !== changeType) {
424
- // Changed type (e.g., was M, now A because the file was deleted and recreated)
425
415
  newEntries.set(path, changeType);
426
416
  }
427
417
  }
@@ -443,11 +433,9 @@ export default function diffFilesExtension(pi: any): void {
443
433
  : entries.filter((e: FileEntry) => e.changeType !== ChangeType.Deleted);
444
434
 
445
435
  const lines: string[] = [];
446
- // Flash header — briefly shows how many files changed this turn
447
436
  lines.push(
448
437
  `${colors.fgCreated}↯ ${addedCount} ${addedCount === 1 ? "file" : "files"} added this turn${colors.rst}`,
449
438
  );
450
- // Entries up to maxLines − 1 (the flash line takes one slot)
451
439
  const shown = visible.slice(0, Math.max(1, config.maxLines - 1));
452
440
  for (const e of shown) {
453
441
  const prefix =
@@ -471,7 +459,6 @@ export default function diffFilesExtension(pi: any): void {
471
459
  });
472
460
 
473
461
  // ── turn_end ────────────────────────────────────────────────────────
474
- // Flash widget briefly when new files appear, then settle to normal.
475
462
  pi.on("turn_end", async (_event: any, ctx: any) => {
476
463
  if (tracker.size === 0) return;
477
464
 
@@ -494,7 +481,6 @@ export default function diffFilesExtension(pi: any): void {
494
481
  });
495
482
 
496
483
  // ── session_start ───────────────────────────────────────────────────
497
- // Reset state for new sessions
498
484
  pi.on("session_start", async (_event: any, ctx: any) => {
499
485
  tracker.clear();
500
486
  bashSnapshots.clear();