From 8681e855eba3c75871b7f90f9d1f5005c798f3d6 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 1 Oct 2026 10:38:16 +0200 Subject: [PATCH] Add java.import.mode setting for on-demand project import Signed-off-by: azerr --- package.json | 15 ++++++++++ src/extension.ts | 54 +++++++++++++++++++++-------------- src/standardLanguageClient.ts | 3 +- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index a7f33f92e..5d2301282 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/src/extension.ts b/src/extension.ts index 86b384fd4..67fa4d4fb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -758,28 +758,34 @@ async function startStandardServer( } async function doStartStandardServer(context: ExtensionContext, requirements: requirements.RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, triggeredByCommand: boolean = false): Promise { - 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(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("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(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) { @@ -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: { diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index fc73cbadc..231600ff5 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -118,7 +118,7 @@ export class StandardLanguageClient { this.status = ClientStatus.initialized; } - public registerLanguageClientActions(context: ExtensionContext, hasImported: boolean, jdtEventEmitter: EventEmitter) { + public registerLanguageClientActions(context: ExtensionContext, hasImportedPromise: Promise, jdtEventEmitter: EventEmitter) { activationProgressNotification.showProgress(); this.languageClient.onNotification(StatusNotification.type, async (report) => { // Resolve serverRunning on the first status notification from the server, @@ -142,6 +142,7 @@ export class StandardLanguageClient { logger.error(error); } activationProgressNotification.hide(); + const hasImported = await hasImportedPromise; if (!hasImported) { showImportFinishNotification(context); }