fix(http-client): find the generated request scratch on Windows (#356)

Generate Request in HTTP Client (and Generate Requests for Controller, and the gutter icon) left the scratch file empty on Windows with "Cannot open file …\ai.l0ng.openidea/scratches/generated-requests.http": the scratch path joined the native app data directory (backslashes) with a /-separated suffix, so the opened editor could not be found by its path. Scratch paths now normalise the app data directory to the UI's /-separated form through a shared ipcPath helper, used by scratches, the HTTP client and Spring request generation.

Co-authored-by: kevinh458 <[email protected]>

未在 Windows 实机验证。
Цей коміт міститься в:
kevinh authored and GitHub committed 2026-10-30 15:09:50 +08:00
1 джерело 9575500dd6
коміт 9d1fe17894
7 files changed
+144 -4

No files matched your search

+3 -1
Переглянути файл
@@ -16,6 +16,7 @@ import {
} from "@/editor/api";
import { tx } from "@/platform/i18n";
import { call, commands, emitLocalIdeEvent, IpcError } from "@/platform/ipc";
import { ipcPath } from "@/platform/paths";
import { useProject } from "@/platform/project";
import { isVirtualUri } from "@/platform/virtualDocs";
import { gutterIconMarkup, showContextMenu, type MenuItem } from "@/ui";
@@ -48,7 +49,8 @@ export interface Target {
async function scratchFile(): Promise<{ path: string; created: boolean }> {
const { appDataDir } = await import("@tauri-apps/api/path");
const dir = `${(await appDataDir()).replace(/[\\/]+$/, "")}/scratches`;
// `appDataDir()` is native (`C:\…`): the editor is found by its `/` path.
const dir = `${ipcPath(await appDataDir()).replace(/\/+$/, "")}/scratches`;
const ignoreExisting = (e: unknown) => {
if (!(e instanceof IpcError && e.kind === "alreadyExists")) throw e;
};
+84
Переглянути файл
@@ -0,0 +1,84 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { _addEditor, _removeEditor, type TextEditorHandle } from "@/editor/api";
import { provideEditorService, provideNotificationService } from "@/workbench/contributions";
import { springControllers } from "./endpoints";
import { generateRequests } from "./editor";
// `appDataDir()` is a native path: `\`-separated on Windows.
vi.mock("@tauri-apps/api/path", () => ({
appDataDir: async () => String.raw`C:\Users\me\AppData\Roaming\ai.l0ng.openidea`,
}));
// No backend here: the file commands answer like a project without Spring
// configuration files (empty configs, the files the body lookup probes missing).
vi.mock("@/platform/ipc", async (importOriginal) => {
const m = await importOriginal<typeof import("@/platform/ipc")>();
return {
...m,
call: async (p: Promise<unknown>) => await p,
commands: {
...m.commands,
fsCreateDir: async () => null,
fsCreateFile: async () => null,
fsReadText: async () => "",
},
};
});
const SCRATCH = "C:/Users/me/AppData/Roaming/ai.l0ng.openidea/scratches/generated-requests.http";
const JAVA = `package com.example.demo.web;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class DemoController {
@PostMapping("/h66")
public String h66() { return "ok"; }
}
`;
const notifications: { level: string; title: string; body?: unknown }[] = [];
function fakeEditor(path: string, dispatched: { changes?: { insert?: string } }[]): TextEditorHandle {
return {
view: {
state: { doc: { toString: () => "" } },
dispatch: (spec: { changes?: { insert?: string } }) => void dispatched.push(spec),
},
ctx: { doc: 1, path, language: "http", uiMode: null, readOnly: false },
} as unknown as TextEditorHandle;
}
describe("generate requests from a mapping", () => {
afterEach(() => {
notifications.length = 0;
});
it("opens the scratch at its UI path and appends the request", async () => {
const opened: string[] = [];
const dispatched: { changes?: { insert?: string } }[] = [];
provideEditorService({ openFile: async (path: string) => void opened.push(path) } as never);
provideNotificationService({
notify: (n: { level: string; title: string; body?: unknown }) => void notifications.push(n),
} as never);
const editor = fakeEditor(SCRATCH, dispatched);
_addEditor(editor);
try {
const controller = springControllers(JAVA)[0]!;
await generateRequests("D:/p/src/main/java/com/example/demo/web/DemoController.java", JAVA, [
{ endpoint: controller.endpoints[0]! },
]);
} finally {
_removeEditor(editor);
}
expect(notifications).toEqual([]);
// The scratch is opened — and found again by the editor registry — as the
// `C:/…` path every other path in the UI uses, not as a native `C:\…` one.
expect(opened).toEqual([SCRATCH]);
expect(dispatched[0]?.changes?.insert).toContain("### DemoController.h66");
expect(dispatched[0]?.changes?.insert).toContain("POST http://localhost:8080/api/h66");
});
});