Files
openidea/src/platform/project.ts
T
l0ng-ai 63a03d1c08 fix: Status refresh loop, double project open at startup, jdtls Java requirement (#5)
* fix: Stop the status refresh loop and the double project open at startup

Every status rewrote .git/index, even with nothing to persist: gix's
`write_changes` writes an empty change set too. The watcher reports
.git/index writes as VCS changes, so each status triggered the next one,
together with an ahead/behind walk. On a repository with a long history
(freqtrade, 33k commits behind upstream) that kept a core busy for as long
as the project was open. Write back only when `has_changes()`.

A project passed on the command line was opened twice: the launch and the
"reopen last project" restore ran concurrently, and the restore checked
for an open project before the launch had finished. The second open found
the index cache locked and indexed in memory only; with a different last
project, the window ended up on whichever finished last. The restore now
waits for the startup launch.

* fix(lsp): Run jdtls on the Java version its bundles need

jdtls's launcher checks for Java 21, and so did JDTLS_MIN_JAVA, but the
Eclipse platform a release ships moves on: 1.63 bundles
org.eclipse.core.filesystem with JavaSE-25, so on a JDK 21 the server died
resolving its bundles ("jdtls is not ready (Crashed)") and the error told
users to install the 21 they already had.

Read the requirement from the installed jdtls instead: the highest osgi.ee
capability / Bundle-RequiredExecutionEnvironment among its plugins'
manifests (cached per install), never below 21. The runtime choice, the
rescan while no JDK fits and the "needs JDK N" message use it.

Fixes #4
2026-10-23 15:20:36 +08:00

87 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** The project open in this window. */
import { create } from "zustand";
import { call, commands, type ProjectInfo, type Settings } from "./ipc";
import { isVirtualUri, virtualContainer, virtualName, virtualParent } from "./virtualDocs";
interface ProjectState {
project: ProjectInfo | null;
/** Effective settings (project-layered when a project is open). */
settings: Settings | null;
opening: boolean;
}
export const useProject = create<ProjectState>(() => ({ project: null, settings: null, opening: false }));
let leaveGuard: (() => Promise<boolean>) | null = null;
/** Installs the check run before this window leaves its project (unsaved changes). */
export function provideLeaveProjectGuard(guard: () => Promise<boolean>): () => void {
leaveGuard = guard;
return () => {
if (leaveGuard === guard) leaveGuard = null;
};
}
/** False when the user chose to stay (another project in place, Close Project). */
export function confirmLeaveProject(): Promise<boolean> {
return useProject.getState().project && leaveGuard ? leaveGuard() : Promise.resolve(true);
}
let startupLaunch: Promise<void> | null = null;
/** Installs this window's startup launch (the project it was started or created for, features/app/windows.ts). */
export function provideStartupLaunch(launch: Promise<void>) {
startupLaunch = launch;
}
/** Settles once the startup launch has opened its project (if any), so a restore doesn't race it. */
export function startupLaunchHandled(): Promise<void> {
return (startupLaunch ?? Promise.resolve()).catch(() => {});
}
export async function openProject(path: string): Promise<ProjectInfo> {
useProject.setState({ opening: true });
try {
const project = await call(commands.openProject(path));
useProject.setState({ project, settings: project.settings });
return project;
} finally {
useProject.setState({ opening: false });
}
}
export async function closeProject() {
await commands.closeProject();
useProject.setState({ project: null });
}
export async function loadSettings() {
useProject.setState({ settings: await commands.settingsGet() });
}
/** Joins and normalises POSIX-style paths returned by the backend. */
/** Path relative to `root`; a virtual document (library class) shows where it comes from. */
export function relativePath(root: string, path: string): string {
if (isVirtualUri(path)) return [virtualContainer(path), virtualName(path)].filter(Boolean).join(" › ");
return path.startsWith(root + "/") ? path.slice(root.length + 1) : path;
}
/** Project-relative directory of `path`; "" for files directly in `root`. */
export function relativeDir(root: string, path: string): string {
if (isVirtualUri(path)) return virtualContainer(path);
const dir = dirname(path);
return dir === root ? "" : relativePath(root, dir);
}
export function basename(path: string): string {
if (isVirtualUri(path)) return virtualName(path);
const i = path.lastIndexOf("/");
return i < 0 ? path : path.slice(i + 1);
}
export function dirname(path: string): string {
if (isVirtualUri(path)) return virtualParent(path);
const i = path.lastIndexOf("/");
return i <= 0 ? "/" : path.slice(0, i);
}