Skip to content
Open
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
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,21 @@
"title": "Project Import/Update",
"order": 20,
"properties": {
"java.import.mode": {
"type": "string",
"enum": [
"full",
"ondemand"
],
"enumDescriptions": [
"All projects are imported at startup.",
"Projects are imported lazily when a file is opened."
],
"default": "full",
"markdownDescription": "Controls how projects are imported into the workspace.\n- `full`: all projects are discovered and imported at startup (default). Safe and well-tested, but slow for large workspaces.\n- `ondemand`: projects are imported lazily when the user first opens a file belonging to that module. This drastically reduces startup time and memory usage for large multi-module workspaces (e.g. Quarkus with 1300+ modules).",
"scope": "window",
"order": 5
},
"java.import.projectSelection": {
"type": "string",
"enum": [
Expand Down
54 changes: 33 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,28 +758,34 @@ async function startStandardServer(
}

async function doStartStandardServer(context: ExtensionContext, requirements: requirements.RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, triggeredByCommand: boolean = false): Promise<LanguageClient | undefined> {
const selector: BuildFileSelector = new BuildFileSelector(context, []);
const importMode: ImportMode = await getImportMode(context, selector);
if (importMode === ImportMode.automatic) {
if (!await ensureNoBuildToolConflicts(context, clientOptions)) {
return undefined;
}
} else {
const buildFiles: string[] = [];
if (importMode === ImportMode.manual) {
const cache = context.workspaceState.get<string[]>(PICKED_BUILD_FILES);
if (cache === undefined || cache.length === 0 && triggeredByCommand) {
buildFiles.push(...await selector.selectBuildFiles() || []);
} else {
buildFiles.push(...cache);
// In on-demand mode, the build tool is determined by the file being opened
// (each importer tries its own build file lookup), so no upfront project
// selection or build tool conflict prompt is needed.
const javaImportMode = getJavaConfiguration().get<string>("import.mode", "full");
if (javaImportMode !== "ondemand") {
const selector: BuildFileSelector = new BuildFileSelector(context, []);
const importMode: ImportMode = await getImportMode(context, selector);
if (importMode === ImportMode.automatic) {
if (!await ensureNoBuildToolConflicts(context, clientOptions)) {
return undefined;
}
} else {
const buildFiles: string[] = [];
if (importMode === ImportMode.manual) {
const cache = context.workspaceState.get<string[]>(PICKED_BUILD_FILES);
if (cache === undefined || cache.length === 0 && triggeredByCommand) {
buildFiles.push(...await selector.selectBuildFiles() || []);
} else {
buildFiles.push(...cache);
}
}
if (buildFiles.length === 0) {
commands.executeCommand('setContext', 'java:serverMode', ServerMode.lightWeight);
serverStatusBarProvider.showNotImportedStatus();
return undefined;
}
clientOptions.initializationOptions.projectConfigurations = buildFiles;
}
if (buildFiles.length === 0) {
commands.executeCommand('setContext', 'java:serverMode', ServerMode.lightWeight);
serverStatusBarProvider.showNotImportedStatus();
return undefined;
}
clientOptions.initializationOptions.projectConfigurations = buildFiles;
}

if (apiManager.getApiInstance().serverMode === ServerMode.lightWeight) {
Expand All @@ -789,7 +795,13 @@ async function doStartStandardServer(context: ExtensionContext, requirements: re
}
await standardClient.initialize(context, requirements, clientOptions, workspacePath, jdtEventEmitter);
standardClient.start().then(async () => {
standardClient.registerLanguageClientActions(context, await fse.pathExists(path.join(workspacePath, ".metadata", ".plugins")), jdtEventEmitter);
// Register handlers immediately (no await before) so they are ready
// when the server sends synchronizeBundles and ServiceReady right after 'initialized'.
// In on-demand mode with no Java file open, zero projects are loaded at startup,
// so the server reaches synchronizeBundles so fast that an await here would cause
// the ExecuteClientCommandRequest handler to not be registered in time.
const hasImportedPromise = fse.pathExists(path.join(workspacePath, ".metadata", ".plugins"));
standardClient.registerLanguageClientActions(context, hasImportedPromise, jdtEventEmitter);
// Send auto-detected JDKs after server startup to avoid blocking initialization
standardClient.getClient().sendNotification(DidChangeConfigurationNotification.type, {
settings: {
Expand Down
3 changes: 2 additions & 1 deletion src/standardLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class StandardLanguageClient {
this.status = ClientStatus.initialized;
}

public registerLanguageClientActions(context: ExtensionContext, hasImported: boolean, jdtEventEmitter: EventEmitter<Uri>) {
public registerLanguageClientActions(context: ExtensionContext, hasImportedPromise: Promise<boolean>, jdtEventEmitter: EventEmitter<Uri>) {
activationProgressNotification.showProgress();
this.languageClient.onNotification(StatusNotification.type, async (report) => {
// Resolve serverRunning on the first status notification from the server,
Expand All @@ -142,6 +142,7 @@ export class StandardLanguageClient {
logger.error(error);
}
activationProgressNotification.hide();
const hasImported = await hasImportedPromise;
if (!hasImported) {
showImportFinishNotification(context);
}
Expand Down
Loading