Файли
openidea/src/features/welcome/session.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

137 рядки
4.5 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* "Reopen last project on startup" (IDEA: Settings › Appearance & Behavior ›
* System Settings). The project open when the app last quit is remembered;
* closing a project explicitly forgets it, so the next start shows the welcome screen.
*/
import { getCurrentWindow } from "@tauri-apps/api/window";
import { create } from "zustand";
import { commands, isTauri, type Settings } from "@/platform/ipc";
import { openProject, startupLaunchHandled, useProject } from "@/platform/project";
import { notifications } from "@/workbench/contributions";
const SESSION_KEY = "openidea.session.openProject";
const PREF_KEY = "openidea.welcome.reopenLastProject";
/** Per-window (sessionStorage): the project this window shows, restored when the webview reloads. */
const WINDOW_KEY = "openidea.window.project";
function windowProject(value?: string | null): string | null {
try {
if (value === undefined) return sessionStorage.getItem(WINDOW_KEY);
if (value === null) sessionStorage.removeItem(WINDOW_KEY);
else sessionStorage.setItem(WINDOW_KEY, value);
} catch {
/* storage unavailable */
}
return null;
}
function read(key: string): string | null {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function write(key: string, value: string | null) {
try {
if (value === null) localStorage.removeItem(key);
else localStorage.setItem(key, value);
} catch {
/* storage unavailable */
}
}
export const useSession = create<{ reopenPref: boolean; reopening: string | null }>(() => ({
reopenPref: read(PREF_KEY) !== "false",
reopening: null,
}));
export function setReopenPreference(on: boolean) {
write(PREF_KEY, on ? "true" : "false");
useSession.setState({ reopenPref: on });
}
/** `general.reopenLastProject` from settings when present, else the local preference. */
export function reopenEnabled(settings: Settings | null | undefined, pref: boolean): boolean {
const general = (settings as { general?: { reopenLastProject?: unknown } } | null | undefined)?.general;
return typeof general?.reopenLastProject === "boolean" ? general.reopenLastProject : pref;
}
/** Keeps the remembered project in sync: set while a project is open, cleared on explicit close. */
export function trackSession(): () => void {
const root = useProject.getState().project?.root;
if (root) {
write(SESSION_KEY, root);
windowProject(root);
}
return useProject.subscribe((s, prev) => {
const now = s.project?.root ?? null;
const before = prev.project?.root ?? null;
if (now === before) return;
if (now) write(SESSION_KEY, now);
else if (before && !useSession.getState().reopening) write(SESSION_KEY, null);
if (now || !useSession.getState().reopening) windowProject(now);
});
}
export function lastSessionProject(): string | null {
return read(SESSION_KEY);
}
let attempted = false;
/** On startup: reopens the project that was open at exit. Robust against a missing folder. */
export async function reopenLastProject(): Promise<void> {
if (attempted || !isTauri()) return;
attempted = true;
if (useProject.getState().project) return;
// A reloaded webview gets back its own project, whatever window it is.
const own = windowProject();
if (own) {
await reopen(own);
return;
}
// Secondary windows open the project they were created for (features/app/windows.ts).
if (getCurrentWindow().label !== "main") return;
if (useProject.getState().project) return;
const path = lastSessionProject();
if (!path) return;
let settings: Settings | null = null;
try {
settings = await commands.settingsGet();
} catch {
/* use the local preference */
}
if (!reopenEnabled(settings, useSession.getState().reopenPref)) return;
// A project passed on the command line wins over the last session's.
await startupLaunchHandled();
if (useProject.getState().project) return;
await reopen(path);
}
async function reopen(path: string) {
const name = path.slice(path.lastIndexOf("/") + 1) || path;
useSession.setState({ reopening: path });
try {
await openProject(path);
} catch (e) {
write(SESSION_KEY, null);
windowProject(null);
notifications().notify({
level: "warning",
title: `Cannot reopen ${name}`,
body: e instanceof Error ? e.message : String(e),
key: "welcome.reopen",
});
} finally {
useSession.setState({ reopening: null });
}
}
/** Tests only. */
export function _resetSessionForTests() {
attempted = false;
useSession.setState({ reopening: null, reopenPref: read(PREF_KEY) !== "false" });
}