@agnishc/edb-diff-files 0.4.0 → 0.5.0
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 +5 -0
- package/package.json +1 -1
- package/src/index.ts +52 -38
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
});
|