@agnishc/edb-diff-files 0.4.0 → 0.5.1

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.1] - 2026-05-05
4
+
5
+ ## [0.5.0] - 2026-05-05
6
+
7
+ ### Fixed
8
+ - Open in editor (`o` key) now properly opens terminal editors like nvim by stopping the TUI first, matching pi core behavior
9
+
3
10
  ## [0.2.0] - 2026-04-29
4
11
 
5
12
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agnishc/edb-diff-files",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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
@@ -1,4 +1,4 @@
1
- import { spawn, spawnSync } from "node:child_process";
1
+ import { spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { basename, relative, resolve } from "node:path";
4
4
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
@@ -22,35 +22,6 @@ const FILTER_LABEL: Record<FilterMode, string> = {
22
22
  deleted: "-deleted",
23
23
  };
24
24
 
25
- // ─── Open a file in the best available GUI editor ───────────────────────────
26
-
27
- function openInEditor(filePath: string): string {
28
- const candidates = ["nvim", "cursor", "code", "subl", "zed", "atom"];
29
-
30
- // Prefer $VISUAL / $EDITOR if they reference a known GUI editor
31
- const configured = (process.env.VISUAL ?? process.env.EDITOR ?? "").split(" ")[0];
32
- if (configured) {
33
- const base = basename(configured);
34
- if (candidates.some((e) => base.includes(e))) {
35
- spawn(configured, [filePath], { detached: true, stdio: "ignore" }).unref();
36
- return `Opened in ${base}`;
37
- }
38
- }
39
-
40
- for (const editor of candidates) {
41
- const w = spawnSync("which", [editor], { encoding: "utf-8", stdio: "pipe" });
42
- if (w.status === 0) {
43
- spawn(editor, [filePath], { detached: true, stdio: "ignore" }).unref();
44
- return `Opened in ${editor}`;
45
- }
46
- }
47
-
48
- // Platform fallback
49
- const opener = process.platform === "darwin" ? "open" : "xdg-open";
50
- spawn(opener, [filePath], { detached: true, stdio: "ignore" }).unref();
51
- return "Opened in default app";
52
- }
53
-
54
25
  // ─── Interactive /diff-files viewer ──────────────────────────────────────────
55
26
 
56
27
  class FilesViewComponent {
@@ -61,13 +32,17 @@ class FilesViewComponent {
61
32
  private statusMsg = "";
62
33
  private cachedWidth?: number;
63
34
  private cachedLines?: string[];
35
+ private readonly tui: any;
64
36
 
65
37
  constructor(
66
38
  private readonly entries: ReadonlyArray<FileEntry>,
67
39
  private readonly diffs: Map<string, string[]>,
68
40
  private readonly theme: any,
69
41
  private readonly onClose: () => void,
70
- ) {}
42
+ tui: any,
43
+ ) {
44
+ this.tui = tui;
45
+ }
71
46
 
72
47
  // ── Keyboard handling ──────────────────────────────────────────────────────
73
48
 
@@ -96,7 +71,7 @@ class FilesViewComponent {
96
71
  } else if (matchesKey(data, "o")) {
97
72
  const entry = this.entries[this.cursor];
98
73
  if (entry) {
99
- this.statusMsg = openInEditor(entry.path);
74
+ this.statusMsg = this.openFileInEditor(entry.path);
100
75
  this.invalidate();
101
76
  }
102
77
  } else if (matchesKey(data, "f")) {
@@ -128,7 +103,7 @@ class FilesViewComponent {
128
103
  } else if (matchesKey(data, "o")) {
129
104
  const entry = this.entries[this.cursor];
130
105
  if (entry) {
131
- this.statusMsg = openInEditor(entry.path);
106
+ this.statusMsg = this.openFileInEditor(entry.path);
132
107
  this.invalidate();
133
108
  }
134
109
  } else if (matchesKey(data, "escape") || matchesKey(data, "q")) {
@@ -138,6 +113,45 @@ class FilesViewComponent {
138
113
  }
139
114
  }
140
115
 
116
+ private openFileInEditor(filePath: string): string {
117
+ // Use same logic as pi core: respect $VISUAL / $EDITOR
118
+ const editorCmd = process.env.VISUAL || process.env.EDITOR;
119
+ if (!editorCmd) {
120
+ this.statusMsg = "No editor configured (set $VISUAL or $EDITOR)";
121
+ return this.statusMsg;
122
+ }
123
+
124
+ try {
125
+ // Stop TUI to release terminal for interactive editor
126
+ this.tui.stop();
127
+
128
+ // Split by space to support editor arguments (e.g., "code --wait")
129
+ const [editor, ...editorArgs] = editorCmd.split(" ");
130
+
131
+ // Spawn editor synchronously with inherited stdio for interactive editing
132
+ const result = spawnSync(editor, [...editorArgs, filePath], {
133
+ stdio: "inherit",
134
+ shell: process.platform === "win32",
135
+ });
136
+
137
+ // On non-zero exit, show status
138
+ if (result.status !== 0) {
139
+ this.statusMsg = `Editor exited with status ${result.status}`;
140
+ } else {
141
+ this.statusMsg = `Opened in ${basename(editor)}`;
142
+ }
143
+ } catch {
144
+ this.statusMsg = "Failed to open editor";
145
+ } finally {
146
+ // Restart TUI
147
+ this.tui.start();
148
+ // Force full re-render since external editor uses alternate screen
149
+ this.tui.requestRender(true);
150
+ }
151
+
152
+ return this.statusMsg;
153
+ }
154
+
141
155
  private filteredEntries(): ReadonlyArray<FileEntry> {
142
156
  if (this.filterMode === "all") return this.entries;
143
157
  const target =
@@ -347,9 +361,9 @@ export default function diffFilesExtension(pi: any): void {
347
361
  if (entries.length === 0 || !uiTheme?.fg) {
348
362
  ctx.ui.setStatus(STATUS_KEY, undefined);
349
363
  } else {
350
- const created = entries.filter((e) => e.changeType === ChangeType.Created).length;
351
- const edited = entries.filter((e) => e.changeType === ChangeType.Edited).length;
352
- const deleted = entries.filter((e) => e.changeType === ChangeType.Deleted).length;
364
+ const created = entries.filter((e: FileEntry) => e.changeType === ChangeType.Created).length;
365
+ const edited = entries.filter((e: FileEntry) => e.changeType === ChangeType.Edited).length;
366
+ const deleted = entries.filter((e: FileEntry) => e.changeType === ChangeType.Deleted).length;
353
367
  const inner: string[] = [];
354
368
  if (created > 0) inner.push(uiTheme.fg("toolDiffAdded", `+${created}`));
355
369
  if (edited > 0) inner.push(uiTheme.fg("warning", `~${edited}`));
@@ -516,8 +530,8 @@ export default function diffFilesExtension(pi: any): void {
516
530
  }
517
531
  }
518
532
 
519
- await ctx.ui.custom((_tui: any, theme: any, _kb: any, done: () => void) => {
520
- return new FilesViewComponent(entries, diffs, theme, () => done());
533
+ await ctx.ui.custom((tui: any, theme: any, _kb: any, done: () => void) => {
534
+ return new FilesViewComponent(entries, diffs, theme, () => done(), tui);
521
535
  });
522
536
  },
523
537
  });