mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Fixes #11592. ## Problem WSL starts Windows programs in the current directory, translated to `\\wsl.localhost\<distro>\<cwd>`. When that path exceeds MAX_PATH, every Windows program fails to start with `Invalid argument`. pi inherits its cwd when spawning `powershell.exe`, so in a deep directory: - clipboard image paste returns `null` (the PowerShell backend fails, stderr is discarded) - copy via `powershell.exe` (WSL without WSLg) fails - opening the browser for sign-in fails (`xdg-open` on WSL runs `explorer.exe`) Reproduced on WSL (FedoraLinux-44): `powershell.exe` and `cmd.exe` work from a 225-char cwd and fail from 230. With an image on the Windows clipboard, `readClipboardImage()` returns the PNG from `/tmp` and `null` from a 245-char cwd; with this change it returns the PNG from both. `LongPathsEnabled=1` does not help. ## Changes - `runClipboardCommand` and `openBrowser` run from `/` on POSIX. These tools never use the cwd (temp files are passed by absolute path), and `/` is always the shortest UNC path. `tmpdir()` was not used because `$TMPDIR` can itself be deep. - The external editor keeps pi's cwd on purpose, so project-relative paths and integrated terminals keep working. A Windows editor such as `code` can still fail from a deep WSL directory, as with git's `core.editor`. A comment documents this. - External editor failures were silent: the UI redrew over the error immediately. pi now shows `Failed to launch external editor: <cmd>`, `External editor exited with code N; prompt unchanged`, or the signal name. This shows in the status line for the main editor and inline in the extension editor dialog. ## Testing - New tests: clipboard commands run from `/` (POSIX); an editor that cannot be launched is reported (POSIX); the existing failure test checks the message. - Verified on WSL by running the real modules: clipboard image paste from a deep cwd, and editor success, exit code 1, launch failure, and SIGTERM. - On Windows, the 3 existing `external-editor.test.ts` tests already fail on `main`: the editor command is split on spaces, so `C:\Program Files\nodejs\node.exe` breaks. Not addressed here. This PR is AI-generated.
72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { basename, dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { describe, expect, it } from "vitest";
|
|
import { type ExternalEditorResult, editInExternalEditor } from "../src/modes/interactive/external-editor.ts";
|
|
|
|
const editorFixturePath = fileURLToPath(new URL("./fixtures/fake-external-editor.mjs", import.meta.url));
|
|
|
|
interface EditorCapture {
|
|
filePath: string;
|
|
content: string;
|
|
entries: string[];
|
|
directoryMode: number;
|
|
}
|
|
|
|
async function runExternalEditor(fixtureFlag?: "--fail" | "--empty"): Promise<{
|
|
result: ExternalEditorResult;
|
|
capture: EditorCapture;
|
|
}> {
|
|
const testDirectory = mkdtempSync(join(tmpdir(), "pi-external-editor-test-"));
|
|
const capturePath = join(testDirectory, "capture.json");
|
|
try {
|
|
const result = await editInExternalEditor({
|
|
command: `${process.execPath} ${editorFixturePath} ${capturePath}${fixtureFlag ? ` ${fixtureFlag}` : ""}`,
|
|
content: "original",
|
|
});
|
|
const capture = JSON.parse(readFileSync(capturePath, "utf-8")) as EditorCapture;
|
|
return { result, capture };
|
|
} finally {
|
|
rmSync(testDirectory, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("editInExternalEditor", () => {
|
|
it("edits a prompt inside a private temporary directory", async () => {
|
|
const { result, capture } = await runExternalEditor();
|
|
const directory = dirname(capture.filePath);
|
|
|
|
expect(result).toEqual({ status: "complete", content: "edited" });
|
|
expect(dirname(directory)).toBe(tmpdir());
|
|
expect(basename(directory)).toMatch(/^pi-editor-.+$/);
|
|
expect(basename(capture.filePath)).toBe("prompt.md");
|
|
expect(capture.entries).toEqual(["prompt.md"]);
|
|
expect(capture.content).toBe("original");
|
|
if (process.platform !== "win32") {
|
|
expect(capture.directoryMode & 0o077).toBe(0);
|
|
}
|
|
expect(existsSync(directory)).toBe(false);
|
|
});
|
|
|
|
it("keeps the original content when the editor exits unsuccessfully", async () => {
|
|
const { result, capture } = await runExternalEditor("--fail");
|
|
|
|
expect(result).toEqual({ status: "failed", message: "External editor exited with code 1; prompt unchanged" });
|
|
expect(existsSync(dirname(capture.filePath))).toBe(false);
|
|
});
|
|
it("returns empty content when the editor clears the prompt", async () => {
|
|
const { result } = await runExternalEditor("--empty");
|
|
|
|
expect(result).toEqual({ status: "complete", content: "" });
|
|
});
|
|
|
|
// #11592: launch failures must be reported, not silently ignored.
|
|
it.skipIf(process.platform === "win32")("reports an editor that cannot be launched", async () => {
|
|
const command = "pi-external-editor-does-not-exist";
|
|
expect(await editInExternalEditor({ command, content: "original" })).toEqual({
|
|
status: "failed",
|
|
message: `Failed to launch external editor: ${command}`,
|
|
});
|
|
});
|
|
});
|