mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
Adds the API 1.3 `endpoints-provider` export and `[[contributes.endpoints]]` (file globs, ^1.3, `project.read`). A plugin's routes join the Spring ones in Search Everywhere, `.http` URL completion and the declaration's Open in HTTP Client gutter action. The UI asks `plugin_endpoints` on demand; the host runs the plugin on a blocking thread, validates the whole list (bounds, project-relative locations, UTF-16 ranges) and caches it per project until a file matching the globs changes, then emits `EndpointsChanged`. A failed or invalid run keeps the last list; disabling, crashing or closing the project withdraws it, and late results can't restore it. Closes #1960. Co-authored-by: arnaud-ritti <[email protected]>
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { EditorView } from "@codemirror/view";
|
|
import { activeEditor, editorsFor, useEditorRegistry, type TextEditorHandle } from "@/editor/api";
|
|
import { tx } from "@/platform/i18n";
|
|
import { call, commands, emitLocalIdeEvent, IpcError } from "@/platform/ipc";
|
|
import { ipcPath } from "@/platform/paths";
|
|
import { editorService } from "@/workbench/contributions";
|
|
import { appendRequests } from "./spring/request";
|
|
|
|
export const GENERATED_REQUESTS = "generated-requests.http";
|
|
|
|
async function scratchFile(): Promise<{ path: string; created: boolean }> {
|
|
const { appDataDir } = await import("@tauri-apps/api/path");
|
|
// `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;
|
|
};
|
|
await call(commands.fsCreateDir(dir)).catch(ignoreExisting);
|
|
const path = `${dir}/${GENERATED_REQUESTS}`;
|
|
let created = true;
|
|
await call(commands.fsCreateFile(path)).catch((e) => {
|
|
ignoreExisting(e);
|
|
created = false;
|
|
});
|
|
return { path, created };
|
|
}
|
|
|
|
/** The text editor of `path`, once the just opened tab has created it. */
|
|
function editorFor(path: string, timeoutMs = 5000): Promise<TextEditorHandle | null> {
|
|
const find = () => editorsFor(path).find((x) => x === activeEditor()) ?? editorsFor(path)[0] ?? null;
|
|
const now = find();
|
|
if (now) return Promise.resolve(now);
|
|
return new Promise((resolve) => {
|
|
const done = (e: TextEditorHandle | null) => {
|
|
clearTimeout(timer);
|
|
unsubscribe();
|
|
resolve(e);
|
|
};
|
|
const timer = setTimeout(() => done(null), timeoutMs);
|
|
const unsubscribe = useEditorRegistry.subscribe(() => {
|
|
const e = find();
|
|
if (e) done(e);
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function appendGeneratedRequests(blocks: string[]): Promise<void> {
|
|
const file = await scratchFile();
|
|
if (file.created) emitLocalIdeEvent({ type: "fsChanged", created: [file.path], modified: [], removed: [] });
|
|
await editorService().openFile(file.path, { focus: true });
|
|
const e = await editorFor(file.path);
|
|
if (!e) throw new Error(`${tx("Cannot open file")}: ${file.path}`);
|
|
const { insert, from, caret } = appendRequests(e.view.state.doc.toString(), blocks);
|
|
e.view.dispatch({
|
|
changes: { from, insert },
|
|
selection: { anchor: caret },
|
|
effects: EditorView.scrollIntoView(caret, { y: "center" }),
|
|
userEvent: "input",
|
|
});
|
|
}
|