Files
openidea/src/platform/projectPresentation.ts
T
yoo hyeong chan 0dfdbbc323 feat(gradle): link and unlink independent Gradle builds (#1413)
Opening a folder of independent Gradle builds selected only the first build. Link Gradle Projects… (pick a build's settings or build file) and Unlink on a build's root let an ordinary project window manage several independent builds without changing their scripts. The build model holds a list of Gradle builds; each keeps its own wrapper, working directory, environment, task routing and sync cache, keyed by the owning build directory. The links persist in the project's Gradle settings (linkedProjects); unlinking leaves files and saved run configurations alone. Existing includeBuild support is unchanged, and an explicitly linked build takes precedence over a composite that also includes it. Workspace windows keep Link/Unlink disabled.

Co-authored-by: gudcks0305 <84506708+gudcks0305@users.noreply.github.com>
2026-11-05 11:23:39 +08:00

89 lines
3.1 KiB
TypeScript

/** Read-only build context shared by file and folder presentation consumers. */
import { useEffect } from "react";
import { create } from "zustand";
import { call, commands, isTauri, onIdeEvent, type BuildModel } from "./ipc";
import { useProject } from "./project";
export interface BuildPresentation {
modules: ReadonlyMap<string, "maven" | "gradle">;
spring: boolean;
}
const EMPTY: BuildPresentation = { modules: new Map(), spring: false };
export const useBuildPresentation = create<BuildPresentation>(() => EMPTY);
export function buildPresentation(model: BuildModel): BuildPresentation {
const modules = new Map<string, "maven" | "gradle">();
for (const p of model.maven?.projects ?? []) modules.set(p.dir, "maven");
for (const p of (model.gradle ?? []).flatMap((g) => g.projects)) modules.set(p.dir, "gradle");
const spring =
(model.maven?.projects ?? []).some(
(p) =>
p.dependencies.some((d) => d.groupId.startsWith("org.springframework")) ||
p.plugins.some((p) => p.groupId.startsWith("org.springframework")),
) || (model.gradle ?? []).some((g) => g.projects.some((p) => p.tasks.some((t) => t.name === "bootRun")));
return { modules, spring };
}
/** A Maven / Gradle module folder below the project root: IDEA shows its name in bold. */
export function isModuleFolder(rel: string, root: boolean, modules: BuildPresentation["modules"]): boolean {
return !root && rel !== "" && modules.has(rel);
}
export function moduleFolderIcon(rel: string, root: boolean, modules: BuildPresentation["modules"]): string | null {
const tool = modules.get(rel);
return tool ? `${tool}-${root ? "project" : "module"}` : null;
}
let users = 0;
let generation = 0;
let dispose: (() => void) | undefined;
async function load() {
const gen = ++generation;
if (!useProject.getState().project || !isTauri()) {
useBuildPresentation.setState(EMPTY);
return;
}
try {
const model = await call(commands.buildModel());
if (generation === gen) useBuildPresentation.setState(buildPresentation(model));
} catch {
/* Presentation keeps its filename fallback. */
}
}
export function useProjectPresentation(): BuildPresentation {
useEffect(() => {
if (users++ === 0) {
void load();
const project = useProject.subscribe((s, prev) => {
if (s.project === prev.project) return;
useBuildPresentation.setState(EMPTY);
void load();
});
const roots = onIdeEvent("projectRootsChanged", () => void load());
let timer: ReturnType<typeof setTimeout> | undefined;
const files = onIdeEvent("fsChanged", (e) => {
if (
![...e.created, ...e.modified, ...e.removed].some((p) =>
/(?:^|\/)(pom\.xml|(?:build|settings)\.gradle(?:\.kts)?)$/.test(p),
)
)
return;
clearTimeout(timer);
timer = setTimeout(() => void load(), 100);
});
dispose = () => {
project();
roots();
files();
clearTimeout(timer);
generation++;
};
}
return () => {
if (--users === 0) dispose?.();
};
}, []);
return useBuildPresentation();
}