diff --git a/packages/coding-agent/src/utils/external-editor.ts b/packages/coding-agent/src/utils/external-editor.ts index 60bcc238c..287f10c8a 100644 --- a/packages/coding-agent/src/utils/external-editor.ts +++ b/packages/coding-agent/src/utils/external-editor.ts @@ -53,8 +53,10 @@ export async function openInEditor( const [editor, ...editorArgs] = editorCmd.split(" "); const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"]; - - const child = spawn(editor, [...editorArgs, tmpFile], { stdio, shell: process.platform === "win32" }); + const child = + process.platform === "win32" + ? spawn(editor, [...editorArgs, tmpFile], { stdio, shell: true }) + : spawn("/bin/sh", ["-c", `${editorCmd} "$1"`, "sh", tmpFile], { stdio }); const { promise, reject, resolve } = Promise.withResolvers(); child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0))); child.once("error", error => reject(error)); diff --git a/packages/coding-agent/test/external-editor.test.ts b/packages/coding-agent/test/external-editor.test.ts index 0e1b4eacd..4e1b7ab17 100644 --- a/packages/coding-agent/test/external-editor.test.ts +++ b/packages/coding-agent/test/external-editor.test.ts @@ -1,5 +1,8 @@ import { afterEach, describe, expect, it } from "bun:test"; -import { getEditorCommand } from "../src/utils/external-editor"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { TempDir } from "@oh-my-pi/pi-utils"; +import { getEditorCommand, openInEditor } from "../src/utils/external-editor"; interface MutableProcess { platform: NodeJS.Platform; @@ -61,3 +64,21 @@ describe("getEditorCommand", () => { expect(getEditorCommand()).toBeUndefined(); }); }); + +describe("openInEditor", () => { + it.skipIf(process.platform === "win32")("supports quoted editor paths containing spaces", async () => { + const tempDir = TempDir.createSync("@external-editor-"); + try { + const editorPath = path.join(tempDir.path(), "My Editor", "edit"); + fs.mkdirSync(path.dirname(editorPath), { recursive: true }); + await Bun.write(editorPath, '#!/bin/sh\nprintf "edited" > "$1"\n'); + fs.chmodSync(editorPath, 0o755); + + const result = await openInEditor(`"${editorPath}"`, "original"); + + expect(result).toBe("edited"); + } finally { + await tempDir.remove(); + } + }); +});