File size: 1,708 Bytes
f500658 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import { spawn } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { stripBom } from "../../utils/text.ts";
export interface ExternalEditorOptions {
command: string;
content: string;
}
export type ExternalEditorResult = { status: "complete"; content: string } | { status: "failed" };
export async function editInExternalEditor(options: ExternalEditorOptions): Promise<ExternalEditorResult> {
const directory = mkdtempSync(join(tmpdir(), "pi-editor-"));
const filePath = join(directory, "prompt.md");
try {
writeFileSync(filePath, options.content, "utf-8");
const [editor, ...editorArgs] = options.command.split(" ");
process.stdout.write(`Launching external editor: ${options.command}\nPi will resume when the editor exits.\n`);
// Do not use spawnSync here. On Windows, synchronous child_process calls can keep
// Node/libuv's console input read active after the parent pauses stdin, racing
// vim/nvim for the console input buffer until Ctrl+C cancels the pending read.
const exitCode = await new Promise<number | null>((resolve) => {
const child = spawn(editor, [...editorArgs, filePath], {
stdio: "inherit",
shell: process.platform === "win32",
});
child.on("error", () => resolve(null));
child.on("close", (code) => resolve(code));
});
if (exitCode !== 0) {
return { status: "failed" };
}
return { status: "complete", content: stripBom(readFileSync(filePath, "utf-8")).replace(/\n$/, "") };
} finally {
try {
rmSync(directory, { recursive: true, force: true });
} catch {
// Cleanup is best effort.
}
}
}
|