Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export const classpathConfigurationViewSlice = createSlice({
},
loadClasspath: (state, action) => {
const activeProjectIndex = action.payload.activeProjectIndex;
if (action.payload.vmInstalls !== undefined) {
state.data.vmInstalls = action.payload.vmInstalls;
}
state.data.output[activeProjectIndex] = action.payload.output;
state.data.effective.output[activeProjectIndex] = action.payload.output;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "@vscode-elements/elements/dist/vscode-divider/index.js";
import "@vscode-elements/elements/dist/vscode-single-select/index.js";
import "@vscode-elements/elements/dist/vscode-option/index.js";
import "@vscode-elements/elements/dist/vscode-button/index.js";
import type { VscodeSingleSelect } from "@vscode-elements/elements/dist/vscode-single-select/index.js";

import { useCallback, useEffect, useRef } from "react";
import { ClasspathRequest, CommonRequest } from "../../../vscode/utils";
Expand All @@ -22,10 +23,11 @@ const JdkRuntime = (): JSX.Element => {
}, [activeProjectIndex]);

const vmInstalls: VmInstall[] = useSelector((state: any) => state.classpathConfig.data.vmInstalls);
const activeVmInstallPath: string = useSelector((state: any) => state.classpathConfig.data.activeVmInstallPath[activeProjectIndex]);
const activeVmInstallPath: string = useSelector((state: any) => state.classpathConfig.data.activeVmInstallPath[activeProjectIndex]) ?? "";
const hasActiveVmInstall = vmInstalls.some(install => install.path === activeVmInstallPath);

const dispatch = useDispatch();
const selectRef = useRef<HTMLElement>(null);
const selectRef = useRef<VscodeSingleSelect>(null);

const handleSelectJdk = useCallback((path: string) => {
if (path === "add-new-jdk") {
Expand All @@ -44,21 +46,21 @@ const JdkRuntime = (): JSX.Element => {
useEffect(() => {
const el = selectRef.current;
if (!el) return;
const handleChange = (e: Event) => {
const selectedValue = (e.target as any).value;
if (selectedValue) {
const handleChange = () => {
const selectedValue = el.value;
if (vmInstalls.some(install => install.path === selectedValue)) {
handleSelectJdk(selectedValue);
}
};
el.addEventListener("change", handleChange);
return () => el.removeEventListener("change", handleChange);
}, [handleSelectJdk]);
}, [handleSelectJdk, vmInstalls]);

// Sync the value property on the select element when activeVmInstallPath changes
useEffect(() => {
const el = selectRef.current;
if (el && activeVmInstallPath) {
(el as any).value = activeVmInstallPath;
if (el) {
el.value = activeVmInstallPath;
}
}, [activeVmInstallPath, vmInstalls]);

Expand Down Expand Up @@ -89,6 +91,11 @@ const JdkRuntime = (): JSX.Element => {
id="jdk-dropdown"
className="setting-section-dropdown"
>
{!hasActiveVmInstall && (
<vscode-option value={activeVmInstallPath} selected disabled description={activeVmInstallPath}>
{activeVmInstallPath ? "Current project JDK (not listed)" : "Project JDK not available"}
</vscode-option>
)}
{vmInstalls.map((vmInstall) => (
<vscode-option
key={vmInstall.path}
Expand All @@ -104,7 +111,14 @@ const JdkRuntime = (): JSX.Element => {
<div className="jdk-runtime-path">
{activeVmInstallPath}
</div>
{!hasActiveVmInstall && activeVmInstallPath && (
<p>The current project JDK is not in the available runtime list. Refresh JDKs or locate its installation.</p>
)}
<div className="jdk-runtime-actions">
<vscode-button secondary onClick={() => ClasspathRequest.onWillListVmInstalls()}>
<span className="codicon codicon-refresh mr-1"></span>
Refresh JDKs
</vscode-button>
<vscode-button secondary onClick={() => ClasspathRequest.onWillAddNewJdk()}>
<span className="codicon codicon-folder-opened mr-1"></span>
Find a local JDK...
Expand All @@ -121,7 +135,7 @@ const JdkRuntime = (): JSX.Element => {
interface OnDidChangeJdkEvent {
data: {
command: string;
vmInstalls: VmInstall[];
vmInstalls?: VmInstall[];
activeVmInstallPath: string;
};
}
Expand Down
58 changes: 36 additions & 22 deletions src/project-settings/handlers/ClasspathRequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export class ClasspathRequestHandler implements vscode.Disposable {
private webview: vscode.Webview;
private currentProjectRoot: vscode.Uri | undefined;
private disposables: vscode.Disposable[] = [];
private classpathRequestId = 0;
private vmInstallsRequestId = 0;

constructor(webview: vscode.Webview) {
this.webview = webview;
Expand Down Expand Up @@ -69,17 +71,15 @@ export class ClasspathRequestHandler implements vscode.Disposable {
}

private listVmInstalls = instrumentOperation("projectSettings.classpath.listVmInstalls", async (operationId: string) => {
let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS();
vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => {
return vmA.name.localeCompare(vmB.name);
const vmInstalls = await this.getVmInstallsFromLS();
if (vmInstalls === undefined) {
return;
}
this.webview.postMessage({
command: "classpath.onDidListVmInstalls",
vmInstalls,
});

if (vmInstalls.length > 0) {
this.webview.postMessage({
command: "classpath.onDidListVmInstalls",
vmInstalls,
});
} else {
if (vmInstalls.length === 0) {
sendInfo(operationId, {
vmNumber: vmInstalls.length,
});
Expand All @@ -93,14 +93,24 @@ export class ClasspathRequestHandler implements vscode.Disposable {
return;
}

const requestId = ++this.classpathRequestId;
const classpath = await this.getProjectClasspathFromLS(uri);
if (requestId !== this.classpathRequestId || !this.currentProjectRoot || path.relative(uri.fsPath, this.currentProjectRoot.fsPath)) {
return;
}
// Resolving the project's VM can register a JDK after the initial inventory query.
const vmInstalls = await this.getVmInstallsFromLS();
if (requestId !== this.classpathRequestId || !this.currentProjectRoot || path.relative(uri.fsPath, this.currentProjectRoot.fsPath)) {
return;
}
if (classpath) {
this.webview.postMessage({
command: "classpath.onDidLoadProjectClasspath",
projectType: classpath.projectType,
sources: classpath.sourcePaths,
output: classpath.defaultOutputPath,
activeVmInstallPath: classpath.jdkPath,
vmInstalls,
libraries: classpath.libraries
});
}
Expand Down Expand Up @@ -310,10 +320,7 @@ export class ClasspathRequestHandler implements vscode.Disposable {

if (result.success) {
const activeVmInstallPath = result.message;
let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS();
vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => {
return vmA.name.localeCompare(vmB.name);
});
const vmInstalls = await this.getVmInstallsFromLS();
this.webview.postMessage({
command: "classpath.onDidChangeJdk",
activeVmInstallPath,
Expand Down Expand Up @@ -345,10 +352,7 @@ export class ClasspathRequestHandler implements vscode.Disposable {

if (result.success) {
const activeVmInstallPath = result.message;
let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS();
vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => {
return vmA.name.localeCompare(vmB.name);
});
const vmInstalls = await this.getVmInstallsFromLS();
this.webview.postMessage({
command: "classpath.onDidChangeJdk",
activeVmInstallPath,
Expand Down Expand Up @@ -436,13 +440,23 @@ export class ClasspathRequestHandler implements vscode.Disposable {
});
});

private async getVmInstallsFromLS(): Promise<VmInstall[]> {
const ret: VmInstall[] = [];
private async getVmInstallsFromLS(): Promise<VmInstall[] | undefined> {
const requestId = ++this.vmInstallsRequestId;
try {
ret.push(...await vscode.commands.executeCommand<VmInstall[]>("java.execute.workspaceCommand", "java.vm.getAllInstalls") || []);
const installs = await vscode.commands.executeCommand<VmInstall[]>("java.execute.workspaceCommand", "java.vm.getAllInstalls") || [];
if (requestId !== this.vmInstallsRequestId) {
return undefined;
}
return installs.sort((a, b) => a.name.localeCompare(b.name));
} catch (error) {
if (requestId !== this.vmInstallsRequestId) {
return undefined;
}
const err = error instanceof Error ? error : new Error(String(error));
sendError(err);
void vscode.window.showErrorMessage(`Unable to load Java runtimes: ${err.message}`);
return undefined;
}
Comment thread
Copilot marked this conversation as resolved.
return ret;
}

private async getProjectClasspathFromLS(uri: vscode.Uri): Promise<ClasspathComponent> {
Expand Down
49 changes: 48 additions & 1 deletion test-plans/java-extension-pack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# Source: wiki Test-Plan.md "Java Extension Pack" scenario
# Verify: Trigger Java: Configure Classpath command → configuration page appears;
# retain the selected project JDK when refreshing the runtime list;
# add a project-local JAR to a non-unmanaged project and apply it.
#
# Prerequisites:
Expand All @@ -14,7 +15,8 @@ name: "Java Extension Pack — Classpath Configuration"
description: |
Corresponds to the Java Extension Pack scenario in the wiki Test Plan:
Trigger the Java: Configure Classpath command, verify the configuration page
appears, and cover the non-unmanaged-project Add Library flow.
appears, verify the current project JDK remains selected after refreshing
available runtimes, and cover the non-unmanaged-project Add Library flow.

setup:
extension: "redhat.java"
Expand All @@ -23,6 +25,9 @@ setup:
vscodeVersion: "stable"
workspace: "../../vscode-java/test/resources/projects/eclipse/simple-app"
timeout: 90
settings:
java.help.showReleaseNotes: false
java.help.firstView: "none"

steps:
# ── Wait for LS ready ────────────────────────────────────
Expand Down Expand Up @@ -59,6 +64,48 @@ steps:
- "Classpath"
- "Libraries"

- id: "open-project-jdk-tab"
action: "clickInWebview text=JDK Runtime"
verify: "The project JDK tab shows runtime controls and an available current JDK"
verifyWebview:
contains:
- "Refresh JDKs"
- "Find a local JDK..."
notContains:
- "Current project JDK (not listed)"
- "Project JDK not available"
timeout: 30

- id: "open-selected-project-jdk"
action: "clickInWebview #jdk-dropdown:has(vscode-option[selected]:not([disabled]))"
verify: "The dropdown has a real selected runtime option, not an unavailable-current-JDK placeholder"
verifyWebview:
contains: "Refresh JDKs"
notContains: "Current project JDK (not listed)"

- id: "close-project-jdk-dropdown"
action: "clickInWebview #jdk-dropdown"

- id: "refresh-project-jdks"
action: "clickInWebview text=Refresh JDKs"
verify: "Refreshing runtimes keeps the current project JDK available without cleaning or restarting"
verifyWebview:
contains: "Refresh JDKs"
notContains:
- "Current project JDK (not listed)"
- "Project JDK not available"
timeout: 30

- id: "verify-selected-project-jdk-after-refresh"
action: "clickInWebview #jdk-dropdown:has(vscode-option[selected]:not([disabled]))"
verify: "A real current project JDK is still selected after the inventory refresh"
verifyWebview:
contains: "Refresh JDKs"
notContains: "Current project JDK (not listed)"

- id: "close-refreshed-project-jdk-dropdown"
action: "clickInWebview #jdk-dropdown"

- id: "open-libraries-tab"
action: "clickInWebview text=Libraries"
verify: "The Libraries tab is selected and shows library management actions"
Expand Down
Loading
Loading