Skip to content

Commit a4a65fe

Browse files
committed
startup op parallelizes reads and ls start
1 parent 1bce2aa commit a4a65fe

7 files changed

Lines changed: 651 additions & 399 deletions

File tree

src/definitions/constants.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,69 @@
11
/*
22
* IBM Confidential
3-
* Copyright IBM Corp. 2020, 2025
3+
* Copyright IBM Corp. 2020, 2026
44
*/
55
import { localize } from "../util/i18nUtil";
6-
export const LIBERTY_MAVEN_PROJECT = "libertyMavenProject";
7-
export const LIBERTY_GRADLE_PROJECT = "libertyGradleProject";
8-
export const TEST_REPORT_STRING = "Test Summary";
96

10-
export const LIBERTY_DASHBOARD_WORKSPACE_STORAGE_KEY = "liberty.dashboard.data";
7+
// ---------------------------------------------------------------------------
8+
// New contextValue scheme: libertyProject:{tool}[:{container|aggregator}]
9+
// Use the helpers below (isMaven, isGradle, isContainer, isAggregator) instead
10+
// of direct string comparisons throughout the codebase.
11+
// ---------------------------------------------------------------------------
12+
export const LIBERTY_PROJECT_MAVEN = "libertyProject:maven";
13+
export const LIBERTY_PROJECT_GRADLE = "libertyProject:gradle";
14+
export const LIBERTY_PROJECT_MAVEN_CONTAINER = "libertyProject:maven:container";
15+
export const LIBERTY_PROJECT_GRADLE_CONTAINER = "libertyProject:gradle:container";
16+
export const LIBERTY_PROJECT_MAVEN_AGGREGATOR = "libertyProject:maven:aggregator";
17+
export const LIBERTY_PROJECT_GRADLE_AGGREGATOR = "libertyProject:gradle:aggregator";
18+
19+
/** Returns true for any libertyProject contextValue (maven or gradle, any variant). */
20+
export function isLibertyProject(contextValue: string): boolean {
21+
return /^libertyProject/.test(contextValue);
22+
}
23+
/** Returns true for maven projects (leaf, container, or aggregator). */
24+
export function isMaven(contextValue: string): boolean {
25+
return /^libertyProject:maven/.test(contextValue);
26+
}
27+
/** Returns true for gradle projects (leaf, container, or aggregator). */
28+
export function isGradle(contextValue: string): boolean {
29+
return /^libertyProject:gradle/.test(contextValue);
30+
}
31+
/** Returns true for container-capable projects (maven or gradle). */
32+
export function isContainer(contextValue: string): boolean {
33+
return /^libertyProject.*:container/.test(contextValue);
34+
}
35+
/** Returns true for aggregator (parent POM / parent Gradle) projects. */
36+
export function isAggregator(contextValue: string): boolean {
37+
return /^libertyProject.*:aggregator/.test(contextValue);
38+
}
1139

12-
// Liberty dev mode in containers
40+
// ---------------------------------------------------------------------------
41+
// Deprecated — kept temporarily for persisted storage migration only.
42+
// These are the old contextValue strings that may exist in workspaceState for
43+
// users upgrading. createLibertyProject maps these to new values on load.
44+
// Remove after one release cycle.
45+
// ---------------------------------------------------------------------------
46+
/** @deprecated Use LIBERTY_PROJECT_MAVEN */
47+
export const LIBERTY_MAVEN_PROJECT = "libertyMavenProject";
48+
/** @deprecated Use LIBERTY_PROJECT_GRADLE */
49+
export const LIBERTY_GRADLE_PROJECT = "libertyGradleProject";
50+
/** @deprecated Use LIBERTY_PROJECT_MAVEN_CONTAINER */
1351
export const LIBERTY_MAVEN_PROJECT_CONTAINER = "libertyMavenProjectContainer";
52+
/** @deprecated Use LIBERTY_PROJECT_GRADLE_CONTAINER */
1453
export const LIBERTY_GRADLE_PROJECT_CONTAINER = "libertyGradleProjectContainer";
1554

55+
export const TEST_REPORT_STRING = "Test Summary";
56+
export const LIBERTY_DASHBOARD_WORKSPACE_STORAGE_KEY = "liberty.dashboard.data";
1657
export const LIBERTY_MAVEN_PLUGIN_CONTAINER_VERSION = "3.3.0";
1758
export const LIBERTY_GRADLE_PLUGIN_CONTAINER_VERSION = "3.1.0";
1859
export const LIBERTY_SERVER_ENV_PORT_REGEX = /^WLP_DEBUG_ADDRESS=([\d]+)$/;
60+
61+
// ---------------------------------------------------------------------------
62+
// Deprecated — COMMAND_AND_PROJECT_TYPE_MAP replaced by regex helpers above.
63+
// filterProjects in helperUtil.ts now uses isMaven/isGradle/isContainer.
64+
// Remove after migration is complete.
65+
// ---------------------------------------------------------------------------
66+
/** @deprecated Use isMaven/isGradle/isContainer helpers in filterProjects */
1967
export const COMMAND_AND_PROJECT_TYPE_MAP: { [command: string]: string[] } = {
2068
"liberty.dev.start": [LIBERTY_MAVEN_PROJECT, LIBERTY_GRADLE_PROJECT, LIBERTY_MAVEN_PROJECT_CONTAINER, LIBERTY_GRADLE_PROJECT_CONTAINER],
2169
"liberty.dev.stop":[LIBERTY_MAVEN_PROJECT, LIBERTY_GRADLE_PROJECT, LIBERTY_MAVEN_PROJECT_CONTAINER, LIBERTY_GRADLE_PROJECT_CONTAINER],
@@ -27,6 +75,7 @@ export const COMMAND_AND_PROJECT_TYPE_MAP: { [command: string]: string[] } = {
2775
"gradle":[ LIBERTY_GRADLE_PROJECT, LIBERTY_GRADLE_PROJECT_CONTAINER],
2876
"liberty.dev.debug": [LIBERTY_MAVEN_PROJECT, LIBERTY_GRADLE_PROJECT, LIBERTY_MAVEN_PROJECT_CONTAINER, LIBERTY_GRADLE_PROJECT_CONTAINER],
2977
};
78+
3079
export const EXCLUDED_DIR_PATTERN = "**/{bin,classes,target,build}/**";
3180
export const COMMAND_TITLES = new Map();
3281
export const UNTITLED_WORKSPACE="Untitled (Workspace)";

src/extension.ts

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,66 +32,91 @@ export type JavaExtensionAPI = any;
3232
const SUPPORTED_LANGUAGE_IDS = ["java-properties", "properties", "plaintext"];
3333
export async function activate(context: vscode.ExtensionContext): Promise<void> {
3434

35-
/**
36-
* Waits for the java language server to launch in standard mode
37-
* Before activating Tools for MicroProfile.
38-
* If java ls was started in lightweight mode, It will prompt user to switch
39-
*/
40-
const api: JavaExtensionAPI = await getJavaExtensionAPI();
41-
35+
// Track 1: Register tree view, file watcher, and all dev commands immediately.
36+
// These have no dependency on the Java API or either language server — they only
37+
// need the VS Code workspace API and the filesystem, both available at activate() time.
38+
//
39+
// NOTE: handleWorkspaceSaveInProgress() is called here (inside registerCommands) rather
40+
// than after the Liberty LS starts. It only touches ProjectProvider and DashboardData —
41+
// no LS dependency — so this is safe. If that ever changes, move it back into the
42+
// Liberty LS .then() callback below.
43+
registerCommands(context);
44+
45+
// Track 2: Language servers — boot in background but still awaited by activate() so
46+
// VS Code's extension host tracks the LS lifecycle correctly. The tree view above is
47+
// already registered and visible while this runs.
48+
//
49+
// IMPORTANT: activate() must return a promise that resolves only after languageClient.start()
50+
// completes. If we fire-and-forget here (drop the await / return), VS Code loses track of
51+
// the LS process lifecycle and surfaces "server connection failed" errors.
4252
const item = window.createStatusBarItem(StatusBarAlignment.Right, Number.MIN_VALUE);
43-
// item.name = "Liberty Language Server";
4453
item.text = localize("liberty.ls");
4554
item.tooltip = localize("liberty.ls.starting");
4655
toggleItem(window.activeTextEditor, item);
4756

57+
let api: JavaExtensionAPI;
58+
try {
59+
api = await getJavaExtensionAPI();
60+
} catch (error: any) {
61+
// Java extension not installed or failed to activate.
62+
// Tree view and dev commands remain functional; language features (LCLS, Jakarta) unavailable.
63+
console.warn("Java extension unavailable, language servers will not start:", error.message);
64+
return;
65+
}
66+
67+
// Waits for the java language server to launch in standard mode
68+
// before activating Tools for MicroProfile.
69+
// If java ls was started in lightweight mode, it will prompt user to switch.
4870
resolveLclsRequirements(api).then().catch((error => {
4971
window.showErrorMessage(error.message, error.label).then((selection) => {
5072
if (error.label && error.label === selection && error.openUrl) {
5173
commands.executeCommand('vscode.open', error.openUrl);
5274
}
5375
});
54-
}))
76+
}));
5577

56-
resolveRequirements(api).then(requirements => {
78+
let requirements: RequirementsData;
79+
try {
80+
requirements = await resolveRequirements(api);
81+
} catch (error: any) {
82+
window.showErrorMessage(error.message, error.label).then((selection) => {
83+
if (error.label && error.label === selection && error.openUrl) {
84+
commands.executeCommand('vscode.open', error.openUrl);
85+
}
86+
});
87+
return;
88+
}
89+
90+
// Start both language servers in parallel and await both — activate() must
91+
// not return until the LS processes are started. If activate() returns early,
92+
// VS Code's extension host loses the LS lifecycle handle and surfaces
93+
// "server connection failed" on reload/restart.
94+
await Promise.all([
5795
startLangServer(context, requirements, true).then(() => {
58-
console.log("Liberty client ready, registering commands");
59-
96+
console.log("Liberty client ready");
6097
item.text = localize("liberty.ls.thumbs.up");
6198
item.tooltip = localize("liberty.ls.started");
6299
toggleItem(window.activeTextEditor, item);
63-
handleWorkspaceSaveInProgress(context);
64-
registerCommands(context);
65100
}, (error: any) => {
66101
console.log("Liberty client was not ready. Did not initialize");
67102
console.log(error);
68-
69103
item.text = localize("liberty.ls.thumbs.down");
70104
item.tooltip = localize("liberty.ls.failedstart");
71-
});
105+
}),
72106

73107
startLangServer(context, requirements, false).then(() => {
74108
console.log("LSP4Jakarta is ready, binding requests...");
75-
76-
// Delegate requests from Jakarta LS to the Jakarta JDT core
77109
bindRequest(lsp4jakartaLS.FILEINFO_REQUEST);
78110
bindRequest(lsp4jakartaLS.JAVA_COMPLETION_REQUEST);
79111
bindRequest(lsp4jakartaLS.JAVA_CODEACTION_REQUEST);
80-
bindRequest(lsp4jakartaLS.JAVA_CODEACTION_RESOLVE_REQUEST);
112+
bindRequest(lsp4jakartaLS.JAVA_CODEACTION_RESOLVE_REQUEST);
81113
bindRequest(lsp4jakartaLS.JAVA_DIAGNOSTICS_REQUEST);
82-
bindRequest(lsp4jakartaLS.JAVA_PROJECT_LABELS_REQUEST);
83-
114+
bindRequest(lsp4jakartaLS.JAVA_PROJECT_LABELS_REQUEST);
84115
item.text = localize("jakarta.ls.thumbs.up");
85116
item.tooltip = localize("jakarta.ls.started");
86117
toggleItem(window.activeTextEditor, item);
87-
});
88-
}).catch((error) => {
89-
window.showErrorMessage(error.message, error.label).then((selection) => {
90-
if (error.label && error.label === selection && error.openUrl) {
91-
commands.executeCommand('vscode.open', error.openUrl);
92-
}
93-
});
94-
})
118+
}),
119+
]);
95120
}
96121

97122
function bindRequest(request: string) {
@@ -109,6 +134,8 @@ function registerCommands(context: ExtensionContext) {
109134
vscode.window.registerTreeDataProvider("liberty-dev", projectProvider);
110135
}
111136

137+
handleWorkspaceSaveInProgress(context);
138+
112139
context.subscriptions.push(
113140
vscode.commands.registerCommand("liberty.explorer.refresh", (async () => {
114141
projectProvider.refresh();

src/liberty/devCommands.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { localize } from "../util/i18nUtil";
1111
import { QuickPickItem } from "vscode";
1212
import { LibertyProject, ProjectProvider } from "./libertyProject";
1313
import { getReport, filterProjects } from "../util/helperUtil";
14-
import { COMMAND_TITLES, LIBERTY_MAVEN_PROJECT, LIBERTY_GRADLE_PROJECT, LIBERTY_MAVEN_PROJECT_CONTAINER, LIBERTY_GRADLE_PROJECT_CONTAINER, LIBERTY_SERVER_ENV_PORT_REGEX } from "../definitions/constants";
14+
import { COMMAND_TITLES, LIBERTY_SERVER_ENV_PORT_REGEX, isMaven, isGradle, isContainer } from "../definitions/constants";
1515
import { getGradleTestReport } from "../util/gradleUtil";
1616
import { DashboardData } from "./dashboard";
1717
import { ProjectStartCmdParam } from "./projectStartCmdParam";
@@ -116,7 +116,7 @@ export async function startDevMode(libProject?: LibertyProject | undefined): Pro
116116
if (terminal !== undefined) {
117117
terminal.show();
118118
targetProject.setTerminal(terminal);
119-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT || targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
119+
if (isMaven(targetProject.getContextValue())) {
120120
// For multi-module projects, use parent aggregator path
121121
const pomPath = targetProject.parent ? targetProject.parent.getPath() : targetProject.getPath();
122122
// Use module selector (-pl) if:
@@ -127,7 +127,7 @@ export async function startDevMode(libProject?: LibertyProject | undefined): Pro
127127
: undefined;
128128
const cmd = await getCommandForMaven(pomPath, "io.openliberty.tools:liberty-maven-plugin:dev", targetProject.getTerminalType(), undefined, artifactId);
129129
terminal.sendText(cmd); // start dev mode on current project
130-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT || targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
130+
} else if (isGradle(targetProject.getContextValue())) {
131131
const cmd = await getCommandForGradle(targetProject.getPath(), "libertyDev", targetProject.getTerminalType());
132132
terminal.sendText(cmd); // start dev mode on current project
133133
}
@@ -290,10 +290,10 @@ export async function attachDebugger(libProject?: LibertyProject | undefined): P
290290

291291
const EXCLUDED_DIR_PATTERN = "**/{bin,classes}/**";
292292
let pathPrefix = "";
293-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT || targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
293+
if (isMaven(targetProject.getContextValue())) {
294294
pathPrefix = "target";
295295

296-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT || targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
296+
} else if (isGradle(targetProject.getContextValue())) {
297297
pathPrefix = "build";
298298
}
299299
let paths: string[] = [];
@@ -424,9 +424,9 @@ export async function customDevMode(libProject?: LibertyProject | undefined, par
424424

425425
let placeHolderStr = "";
426426
let promptString = localize("specify.custom.parms.maven");
427-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT || targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
427+
if (isMaven(targetProject.getContextValue())) {
428428
placeHolderStr = "e.g. -DhotTests=true";
429-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT || targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
429+
} else if (isGradle(targetProject.getContextValue())) {
430430
placeHolderStr = "e.g. --hotTests";
431431
promptString = localize("specify.custom.parms.gradle");
432432
}
@@ -460,7 +460,7 @@ export async function customDevMode(libProject?: LibertyProject | undefined, par
460460
await helperUtil.saveStorageData(projectProvider.getContext(), dashboardData);
461461
}
462462

463-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT || targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
463+
if (isMaven(targetProject.getContextValue())) {
464464
// For multi-module projects, use parent aggregator path
465465
const pomPath = targetProject.parent ? targetProject.parent.getPath() : targetProject.getPath();
466466
// Use module selector (-pl) if:
@@ -471,7 +471,7 @@ export async function customDevMode(libProject?: LibertyProject | undefined, par
471471
: undefined;
472472
const cmd = await getCommandForMaven(pomPath, "io.openliberty.tools:liberty-maven-plugin:dev", targetProject.getTerminalType(), customCommand, artifactId);
473473
terminal.sendText(cmd);
474-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT || targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
474+
} else if (isGradle(targetProject.getContextValue())) {
475475
const cmd = await getCommandForGradle(targetProject.getPath(), "libertyDev", targetProject.getTerminalType(), customCommand);
476476
terminal.sendText(cmd);
477477
}
@@ -514,7 +514,7 @@ export async function startContainerDevMode(libProject?: LibertyProject | undefi
514514
if (terminal !== undefined) {
515515
terminal.show();
516516
targetProject.setTerminal(terminal);
517-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
517+
if (isContainer(targetProject.getContextValue()) && isMaven(targetProject.getContextValue())) {
518518
// For multi-module projects, use parent aggregator path
519519
const pomPath = targetProject.parent ? targetProject.parent.getPath() : targetProject.getPath();
520520
// Use module selector (-pl) if:
@@ -525,7 +525,7 @@ export async function startContainerDevMode(libProject?: LibertyProject | undefi
525525
: undefined;
526526
const cmd = await getCommandForMaven(pomPath, "io.openliberty.tools:liberty-maven-plugin:devc", targetProject.getTerminalType(), undefined, artifactId);
527527
terminal.sendText(cmd);
528-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
528+
} else if (isContainer(targetProject.getContextValue()) && isGradle(targetProject.getContextValue())) {
529529
const cmd = await getCommandForGradle(targetProject.getPath(), "libertyDevc", targetProject.getTerminalType());
530530
terminal.sendText(cmd);
531531
}
@@ -590,7 +590,7 @@ export async function openReport(reportType: string, libProject?: LibertyProject
590590
reportTypeLabel = "test";
591591
}
592592
let showErrorMessage: boolean = true;
593-
if (targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT || targetProject.getContextValue() === LIBERTY_MAVEN_PROJECT_CONTAINER) {
593+
if (isMaven(targetProject.getContextValue())) {
594594
report = getReportFile(path, "reports", reportType + ".html");
595595
// show the error message only if both "reports" and "site" dirs do not contain the test reports
596596
// set to false since this will be the first location checked
@@ -602,7 +602,7 @@ export async function openReport(reportType: string, libProject?: LibertyProject
602602
showErrorMessage = true;
603603
await checkReportAndDisplay(report, reportType, reportTypeLabel, targetProject, showErrorMessage);
604604
}
605-
} else if (targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT || targetProject.getContextValue() === LIBERTY_GRADLE_PROJECT_CONTAINER) {
605+
} else if (isGradle(targetProject.getContextValue())) {
606606
report = await getGradleTestReport(targetProject.path, path);
607607
await checkReportAndDisplay(report, reportType, reportTypeLabel, targetProject, showErrorMessage);
608608
}
@@ -687,7 +687,9 @@ Method adds a project which is selected by the user from the list to the liberty
687687
export async function addProjectsToTheDashBoard(projectProvider: ProjectProvider, selection: string): Promise<void> {
688688
const result = await projectProvider.addUserSelectedPath(selection, projectProvider.getProjects());
689689
const message = localize(`add.project.manually.message.${result}`, selection);
690-
(result !== 0) ? console.error(message) : console.info(message); projectProvider.fireChangeEvent();
690+
(result !== 0) ? console.error(message) : console.info(message);
691691
vscode.window.showInformationMessage(message);
692+
// refresh() re-runs full discovery (including hierarchy) and shows the status bar indicator
693+
await projectProvider.refresh();
692694
return Promise.resolve();
693695
}

0 commit comments

Comments
 (0)