| 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`); |
|
|
| |
| |
| |
| 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 { |
| |
| } |
| } |
| } |
|
|