feat(java): share JDT LS across builds in a project window (#2272)

* feat(java): share JDT LS across builds in a project window

* fix(java): wait for JDT restart before launch requests

* fix(host): give relay final replies ten seconds of EOF grace

relay::tests::final_responses_keep_flowing_after_input_eof flakes on
Windows CI: the target writes one byte per second for seven seconds
after input EOF, but the relay only waits five idle seconds, so a
slow first byte makes the relay return with nothing received.

Raise EOF_GRACE to ten seconds and update the companion test's
server wait and elapsed bound to match.

Refs #2272 (Windows CI failure)

---------

Co-authored-by: arookieofc <[email protected]>
Цей коміт міститься в:
yoo hyeong chanтаarookieofc authored and GitHub committed 2026-11-09 17:53:59 +08:00
1 джерело 5d3432a000
коміт a46d873f6c
16 files changed
+1034 -79

No files matched your search

+2
Переглянути файл
@@ -5271,6 +5271,8 @@ export type JavaSettings = {
* (VS Code's `java.jdt.ls.vmargs`).
*/
jdtlsVmArgs?: string,
/** Share one JDT LS across builds in an ordinary project window; other windows remain separate. */
jdtlsShared?: boolean,
/** Build Tools › Maven. */
maven?: MavenSettings,
/** Build Tools › Gradle. */
+88
Переглянути файл
@@ -514,6 +514,94 @@ describe("Settings dialog", () => {
expect(settingsSet.mock.calls).toEqual([["global", "languages.java.jdtlsVmArgs", '"-Xmx2g"']]);
});
it("stages shared JDT LS in the project and writes it on Apply", async () => {
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
const checkbox = within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" });
expect(checkbox).not.toBeChecked();
fireEvent.click(checkbox);
expect(checkbox).toBeChecked();
expect(settingsSet).not.toHaveBeenCalled();
await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
expect(settingsSet.mock.calls).toEqual([["project", "languages.java.jdtlsShared", "true"]]);
});
it("keeps the global shared JDT LS preference separate from the project's override", async () => {
global = setAt(global, "languages.java.jdtlsShared", true);
current = setAt(global, "languages.java.jdtlsShared", false);
useProject.setState({ settings: current });
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
expect(within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" })).not.toBeChecked();
fireEvent.click(within(dialog).getByRole("radio", { name: "Global" }));
const checkbox = within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" });
expect(checkbox).toBeChecked();
fireEvent.click(checkbox);
await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
expect(settingsSet.mock.calls).toEqual([["global", "languages.java.jdtlsShared", "false"]]);
});
it("discards a shared JDT LS toggle on Cancel", async () => {
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
fireEvent.click(within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" }));
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
expect(settingsSet).not.toHaveBeenCalled();
expect(current.languages?.java?.jdtlsShared).toBe(false);
});
it("disables shared JDT LS only in a native Workspace's project scope", async () => {
useProject.setState((s) => ({
project: { ...s.project!, workspace: { version: 1, name: "Workspace", projects: ["/p/member"] } },
}));
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
expect(within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" })).toBeDisabled();
expect(within(dialog).getByText(/Open a workspace member as a project/)).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("radio", { name: "Global" }));
const checkbox = within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" });
expect(checkbox).toBeEnabled();
fireEvent.click(checkbox);
await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
expect(settingsSet.mock.calls).toEqual([["global", "languages.java.jdtlsShared", "true"]]);
});
it("disables sharing for a Java language server target only in project scope", async () => {
current = setAt(current, "languageServerTargets", { java: { target: "docker:java", server: "jdtls" } });
useProject.setState({ settings: current });
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
const checkbox = within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" });
expect(checkbox).toBeDisabled();
expect(within(dialog).getByText(/Select Local machine for the Java language server/)).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("radio", { name: "Global" }));
expect(checkbox).toBeEnabled();
fireEvent.click(checkbox);
await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
expect(settingsSet.mock.calls).toEqual([["global", "languages.java.jdtlsShared", "true"]]);
});
it.each([{ java: { target: "", server: "jdtls" } }, { python: { target: "docker:python" } }])(
"keeps sharing available when Java runs locally: %j",
async (targets) => {
current = setAt(current, "languageServerTargets", targets);
useProject.setState({ settings: current });
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
expect(within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" })).toBeEnabled();
},
);
it("uses a staged project Java language server target before Apply", async () => {
const dialog = await openDialog("languages.java");
fireEvent.click(within(dialog).getByRole("radio", { name: "This project" }));
const checkbox = within(dialog).getByRole("checkbox", { name: "Share JDT LS across builds" });
expect(checkbox).toBeEnabled();
act(() => stage("languages.servers", "project", "languageServerTargets.java.target", "docker:java"));
expect(checkbox).toBeDisabled();
expect(settingsSet).not.toHaveBeenCalled();
});
it("Settings for New Projects edits the global layer on every page", async () => {
render(<SettingsDialog />);
await act(async () => {
+9
Переглянути файл
@@ -192,6 +192,15 @@ describe("languages settings", () => {
expect(folderRole(ps, "src")).toBeNull();
});
it("defaults shared JDT LS to off and accepts only boolean values", () => {
expect(DEFAULT_LANGUAGES.java.jdtlsShared).toBe(false);
expect(validateLanguages({}).java.jdtlsShared).toBe(false);
expect(validateSettings({ languages: { java: { jdtlsShared: true } } }).languages.java.jdtlsShared).toBe(true);
expect(validateLanguages({ java: { jdtlsShared: false } }).java.jdtlsShared).toBe(false);
for (const invalid of ["true", 1, null, []])
expect(validateLanguages({ java: { jdtlsShared: invalid } }).java.jdtlsShared).toBe(false);
});
it("defaults Java language level to SDK default and keeps only supported levels", () => {
expect(validateLanguages({}).java.languageLevel).toBe("");
expect(validateLanguages({ java: { languageLevel: "8" } }).java.languageLevel).toBe("8");
+21 -2
Переглянути файл
@@ -4,15 +4,16 @@
* use (applied by `ide-sdk` when language servers start and when run configurations load).
*/
import { LanguageServerTargetRows, LanguageServerTargetsPage } from "./LanguageServerTarget";
import { useProject } from "@/platform/project";
import { useRunTargets } from "@/platform/pluginRunTargets";
import { tx } from "@/platform/i18n";
import { useEffect, useId, useMemo, useState } from "react";
import { call, commands, type Sdk } from "@/platform/ipc";
import { call, commands, type LanguageServerTarget, type Sdk } from "@/platform/ipc";
import { IconButton, Input, type SelectOption, T } from "@/ui";
import { JAVA_LANGUAGE_LEVELS, PHP_LANGUAGE_LEVELS } from "@/platform/languageSettings";
import { GlobListEditor, Row, Section, Select, Toggle } from "../components/Form";
import { choosePath } from "./api";
import { useSetting } from "../components/context";
import { usePageContext, useSetting } from "../components/context";
import { SdkField, tildify, useDetected } from "./components";
import styles from "./Sdk.module.css";
@@ -387,6 +388,11 @@ export const JAVA_LEVEL_OPTIONS: SelectOption[] = JAVA_LANGUAGE_LEVELS.map((leve
);
export function JavaPage() {
const [shared, setShared] = useSetting<boolean>("languages.java.jdtlsShared");
const [serverTargets] = useSetting<Record<string, LanguageServerTarget>>("languageServerTargets", "project");
const { scope } = usePageContext();
const workspace = useProject((s) => !!s.project?.workspace);
const sharedDisabled = scope === "project" && (workspace || !!serverTargets?.java?.target);
const [vmArgs, setVmArgs] = useSetting<string>("languages.java.jdtlsVmArgs");
const [languageLevel, setLanguageLevel] = useSetting<string>("languages.java.languageLevel");
const id = useId();
@@ -417,6 +423,19 @@ export function JavaPage() {
</Row>
</Section>
<Section title="Language Server">
<Toggle
label="Share JDT LS across builds"
checked={shared}
onChange={setShared}
disabled={sharedDisabled}
hint={
sharedDisabled
? workspace
? "Shared JDT LS is available only in local ordinary project windows. Open a workspace member as a project to change this setting for it."
: "Shared JDT LS is available only in local ordinary project windows. Select Local machine for the Java language server to use it."
: "Uses one JDT LS process for linked builds in each local ordinary project window. Other project windows use separate processes."
}
/>
<SdkRow
k="languages.java.jdtlsJdk"
label="JDK"
+3
Переглянути файл
@@ -132,6 +132,9 @@ export const LANGUAGE_PAGES: PageSpec[] = [
"lombok",
"javaagent",
"eclipse jdt",
"shared jdtls",
"linked builds",
"server process",
],
component: JavaPage,
},
+3
Переглянути файл
@@ -164,6 +164,7 @@ export interface LanguagesValid {
languageLevel: (typeof JAVA_LANGUAGE_LEVELS)[number];
jdtlsJdk: string;
jdtlsVmArgs: string;
jdtlsShared: boolean;
gradle: {
linkedProjects: string[] | null;
jvm: string;
@@ -243,6 +244,7 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
languageLevel: "",
jdtlsJdk: "",
jdtlsVmArgs: "",
jdtlsShared: false,
gradle: { linkedProjects: null, jvm: "", distribution: "wrapper", home: "", userHome: "", runUsing: "gradle" },
maven: {
runnerJre: "",
@@ -401,6 +403,7 @@ export function validateLanguages(raw: unknown): LanguagesValid {
languageLevel: oneOf(java.languageLevel, JAVA_LANGUAGE_LEVELS, d.java.languageLevel),
jdtlsJdk: text(java.jdtlsJdk, d.java.jdtlsJdk),
jdtlsVmArgs: text(java.jdtlsVmArgs, d.java.jdtlsVmArgs),
jdtlsShared: bool(java.jdtlsShared, d.java.jdtlsShared),
gradle: {
linkedProjects: Array.isArray(gradle.linkedProjects)
? gradle.linkedProjects.filter((p): p is string => typeof p === "string")
+4
Переглянути файл
@@ -1,4 +1,8 @@
{
"Shared JDT LS is available only in local ordinary project windows. Select Local machine for the Java language server to use it.": "Shared JDT LS is available only in local ordinary project windows. Select Local machine for the Java language server to use it.",
"Share JDT LS across builds": "Share JDT LS across builds",
"Shared JDT LS is available only in local ordinary project windows. Open a workspace member as a project to change this setting for it.": "Shared JDT LS is available only in local ordinary project windows. Open a workspace member as a project to change this setting for it.",
"Uses one JDT LS process for linked builds in each local ordinary project window. Other project windows use separate processes.": "Uses one JDT LS process for linked builds in each local ordinary project window. Other project windows use separate processes.",
"Finder": "Finder",
"Explorer": "Explorer",
"Files": "Files",
+4
Переглянути файл
@@ -1,4 +1,8 @@
{
"Shared JDT LS is available only in local ordinary project windows. Select Local machine for the Java language server to use it.": "共享 JDT LS 仅适用于本机的普通项目窗口。请将 Java 语言服务器的运行位置设为本机后再使用。",
"Share JDT LS across builds": "在构建间共享 JDT LS",
"Shared JDT LS is available only in local ordinary project windows. Open a workspace member as a project to change this setting for it.": "共享 JDT LS 仅适用于本机的普通项目窗口。请将工作区成员作为项目打开,再修改其此项设置。",
"Uses one JDT LS process for linked builds in each local ordinary project window. Other project windows use separate processes.": "在每个本机普通项目窗口中,已关联的构建共用一个 JDT LS 进程。其他项目窗口使用各自独立的进程。",
"Finder": "访达",
"Explorer": "资源管理器",
"Reveal in Finder": "在访达中显示",