From f7d75e7097bab1b2f4f9d7211d4e96a838096c6f Mon Sep 17 00:00:00 2001
From: David Thompson <davthomp@redhat.com>
Date: Fri, 27 Jan 2023 11:27:05 -0500
Subject: [PATCH] Return the API before importing the projects

This also has the side effect that extensions that depend on vscode-java
can start up sooner.

Fixes #2900

Signed-off-by: David Thompson <davthomp@redhat.com>
---
 src/extension.ts                               | 18 ++++++++++--------
 src/standardLanguageClient.ts                  |  6 ++----
 src/syntaxLanguageClient.ts                    | 18 ++++--------------
 test/lightweight-mode-suite/publicApi.test.ts  | 10 +++++-----
 .../gotoSuperImplementation.test.ts            |  6 ++++--
 test/standard-mode-suite/publicApi.test.ts     | 11 ++++++-----
 6 files changed, 31 insertions(+), 38 deletions(-)

diff --git a/src/extension.ts b/src/extension.ts
index 5c950e73dd..e77282eb84 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -243,12 +243,16 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
 			};
 
 			apiManager.initialize(requirements, serverMode);
+			resolve(apiManager.getApiInstance());
+			// the promise is resolved
+			// no need to pass `resolve` into any code past this point,
+			// since `resolve` is a no-op from now on
 
 			if (requireSyntaxServer) {
 				if (process.env['SYNTAXLS_CLIENT_PORT']) {
-					syntaxClient.initialize(requirements, clientOptions, resolve);
+					syntaxClient.initialize(requirements, clientOptions);
 				} else {
-					syntaxClient.initialize(requirements, clientOptions, resolve, prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true));
+					syntaxClient.initialize(requirements, clientOptions, prepareExecutable(requirements, syntaxServerWorkspacePath, getJavaConfig(requirements.java_home), context, true));
 				}
 				syntaxClient.start();
 				serverStatusBarProvider.showLightWeightStatus();
@@ -347,7 +351,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
 				}
 
 				if (choice === "Yes") {
-					await startStandardServer(context, requirements, clientOptions, workspacePath, resolve);
+					await startStandardServer(context, requirements, clientOptions, workspacePath);
 				}
 			});
 
@@ -372,10 +376,8 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
 				const importOnStartup = config.get(importOnStartupSection);
 				if (importOnStartup === "disabled" ||
 					env.uiKind === UIKind.Web && env.appName.includes("Visual Studio Code")) {
-					syntaxClient.resolveApi(resolve);
 					requireStandardServer = false;
 				} else if (importOnStartup === "interactive" && await workspaceContainsBuildFiles()) {
-					syntaxClient.resolveApi(resolve);
 					requireStandardServer = await promptUserForStandardServer(config);
 				} else {
 					requireStandardServer = true;
@@ -383,7 +385,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
 			}
 
 			if (requireStandardServer) {
-				await startStandardServer(context, requirements, clientOptions, workspacePath, resolve);
+				await startStandardServer(context, requirements, clientOptions, workspacePath);
 			}
 
 			const onDidGrantWorkspaceTrust = (workspace as any).onDidGrantWorkspaceTrust;
@@ -411,7 +413,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
 	});
 }
 
-async function startStandardServer(context: ExtensionContext, requirements: requirements.RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, resolve: (value?: ExtensionAPI | PromiseLike<ExtensionAPI>) => void) {
+async function startStandardServer(context: ExtensionContext, requirements: requirements.RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string) {
 	if (standardClient.getClientStatus() !== ClientStatus.uninitialized) {
 		return;
 	}
@@ -426,7 +428,7 @@ async function startStandardServer(context: ExtensionContext, requirements: requ
 		apiManager.getApiInstance().serverMode = ServerMode.hybrid;
 		apiManager.fireDidServerModeChange(ServerMode.hybrid);
 	}
-	await standardClient.initialize(context, requirements, clientOptions, workspacePath, jdtEventEmitter, resolve);
+	await standardClient.initialize(context, requirements, clientOptions, workspacePath, jdtEventEmitter);
 	standardClient.start();
 	serverStatusBarProvider.showStandardStatus();
 }
diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts
index b2a36c5eb3..1e8f2e120c 100644
--- a/src/standardLanguageClient.ts
+++ b/src/standardLanguageClient.ts
@@ -11,7 +11,7 @@ import { apiManager } from "./apiManager";
 import * as buildPath from './buildpath';
 import { javaRefactorKinds, RefactorDocumentProvider } from "./codeActionProvider";
 import { Commands } from "./commands";
-import { ClientStatus, ExtensionAPI } from "./extension.api";
+import { ClientStatus } from "./extension.api";
 import * as fileEventHandler from './fileEventHandler';
 import { gradleCodeActionMetadata, GradleCodeActionProvider } from "./gradle/gradleCodeActionProvider";
 import { JavaInlayHintsProvider } from "./inlayHintsProvider";
@@ -56,7 +56,7 @@ export class StandardLanguageClient {
 	private languageClient: LanguageClient;
 	private status: ClientStatus = ClientStatus.uninitialized;
 
-	public async initialize(context: ExtensionContext, requirements: RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, jdtEventEmitter: EventEmitter<Uri>, resolve: (value: ExtensionAPI) => void): Promise<void> {
+	public async initialize(context: ExtensionContext, requirements: RequirementsData, clientOptions: LanguageClientOptions, workspacePath: string, jdtEventEmitter: EventEmitter<Uri>): Promise<void> {
 		if (this.status !== ClientStatus.uninitialized) {
 			return;
 		}
@@ -139,13 +139,11 @@ export class StandardLanguageClient {
 						serverStatus.updateServerStatus(ServerStatusKind.ready);
 						commands.executeCommand('setContext', 'javaLSReady', true);
 						apiManager.updateStatus(ClientStatus.started);
-						resolve(apiManager.getApiInstance());
 						break;
 					case 'Error':
 						this.status = ClientStatus.error;
 						serverStatus.updateServerStatus(ServerStatusKind.error);
 						apiManager.updateStatus(ClientStatus.error);
-						resolve(apiManager.getApiInstance());
 						break;
 					case 'ProjectStatus':
 						if (report.message === "WARNING") {
diff --git a/src/syntaxLanguageClient.ts b/src/syntaxLanguageClient.ts
index 0bc7bf4775..86ab554508 100644
--- a/src/syntaxLanguageClient.ts
+++ b/src/syntaxLanguageClient.ts
@@ -5,10 +5,11 @@ import { DidChangeConfigurationNotification, LanguageClientOptions } from "vscod
 import { LanguageClient, ServerOptions, StreamInfo } from "vscode-languageclient/node";
 import { apiManager } from "./apiManager";
 import { ClientErrorHandler } from "./clientErrorHandler";
-import { ClientStatus, ExtensionAPI } from "./extension.api";
+import { ClientStatus } from "./extension.api";
 import { logger } from "./log";
 import { OutputInfoCollector } from "./outputInfoCollector";
 import { StatusNotification } from "./protocol";
+import { RequirementsData } from "./requirements";
 import { ServerMode } from "./settings";
 import { snippetCompletionProvider } from "./snippetCompletionProvider";
 import { getJavaConfig } from "./utils";
@@ -19,7 +20,7 @@ export class SyntaxLanguageClient {
 	private languageClient: LanguageClient;
 	private status: ClientStatus = ClientStatus.uninitialized;
 
-	public initialize(requirements, clientOptions: LanguageClientOptions, resolve: (value: ExtensionAPI) => void, serverOptions?: ServerOptions) {
+	public initialize(requirements: RequirementsData, clientOptions: LanguageClientOptions, serverOptions?: ServerOptions) {
 		const newClientOptions: LanguageClientOptions = Object.assign({}, clientOptions, {
 			middleware: {
 				workspace: {
@@ -75,7 +76,7 @@ export class SyntaxLanguageClient {
 							break;
 					}
 					if (apiManager.getApiInstance().serverMode === ServerMode.lightWeight) {
-						this.resolveApiOnReady(resolve);
+						apiManager.fireDidServerModeChange(ServerMode.lightWeight);
 					}
 				});
 			});
@@ -111,15 +112,4 @@ export class SyntaxLanguageClient {
 		return this.languageClient;
 	}
 
-	public resolveApi(resolve: (value: ExtensionAPI) => void): void {
-		apiManager.getApiInstance().serverMode = ServerMode.lightWeight;
-		apiManager.fireDidServerModeChange(ServerMode.lightWeight);
-		this.resolveApiOnReady(resolve);
-	}
-
-	private resolveApiOnReady(resolve: (value: ExtensionAPI) => void): void {
-		if ([ClientStatus.started, ClientStatus.error].includes(this.status)) {
-			resolve(apiManager.getApiInstance());
-		}
-	}
 }
diff --git a/test/lightweight-mode-suite/publicApi.test.ts b/test/lightweight-mode-suite/publicApi.test.ts
index b1ee770b2a..3a67f7bba0 100644
--- a/test/lightweight-mode-suite/publicApi.test.ts
+++ b/test/lightweight-mode-suite/publicApi.test.ts
@@ -1,13 +1,13 @@
 'use strict';
 
 import * as assert from 'assert';
+import * as fse from 'fs-extra';
 import * as path from 'path';
-import { ExtensionAPI, extensionApiVersion, ClasspathResult } from '../../src/extension.api';
-import { Uri, DocumentSymbol, extensions, commands } from 'vscode';
+import { commands, DocumentSymbol, extensions, Uri } from 'vscode';
+import { Commands } from '../../src/commands';
+import { ClasspathResult, ExtensionAPI, extensionApiVersion } from '../../src/extension.api';
 import { ServerMode } from '../../src/settings';
-import * as fse from 'fs-extra';
 import { getJavaConfiguration } from '../../src/utils';
-import { Commands } from '../../src/commands';
 import { constants } from '../common';
 
 const pomPath: string = path.join(constants.projectFsPath, 'pom.xml');
@@ -31,7 +31,7 @@ suite('Public APIs - LightWeight', () => {
 
 	test('status should be correct', async function () {
 		const api: ExtensionAPI = extensions.getExtension('redhat.java').exports;
-		assert.equal(api.status, 'Started');
+		assert.equal(api.status, 'Starting');
 	});
 
 	test('registerHoverCommand should work', async function () {
diff --git a/test/standard-mode-suite/gotoSuperImplementation.test.ts b/test/standard-mode-suite/gotoSuperImplementation.test.ts
index 109e5ace15..34a1285d1c 100644
--- a/test/standard-mode-suite/gotoSuperImplementation.test.ts
+++ b/test/standard-mode-suite/gotoSuperImplementation.test.ts
@@ -2,8 +2,9 @@
 
 import * as assert from 'assert';
 import * as path from 'path';
-import { Uri, extensions, commands, TextDocument, workspace, window, Selection, Position } from 'vscode';
+import { commands, extensions, Position, Selection, TextDocument, Uri, window, workspace } from 'vscode';
 import { Commands } from '../../src/commands';
+import { ExtensionAPI } from '../../src/extension.api';
 
 const projectFsPath: string = path.join(__dirname, '..', '..', '..', 'test', 'resources', 'projects', 'maven', 'salut');
 const fileFsPath: string = path.join(projectFsPath, 'src', 'main', 'java', 'java', 'Foo3.java');
@@ -11,7 +12,8 @@ const fileFsPath: string = path.join(projectFsPath, 'src', 'main', 'java', 'java
 suite('Goto Super Implementation', () => {
 
 	suiteSetup(async function() {
-		await extensions.getExtension('redhat.java').activate();
+		const api: ExtensionAPI = await extensions.getExtension('redhat.java').activate();
+		await api.serverReady();
 	});
 
 	test('go to super implementation should work', async function () {
diff --git a/test/standard-mode-suite/publicApi.test.ts b/test/standard-mode-suite/publicApi.test.ts
index e349a81dc5..2141aa4863 100644
--- a/test/standard-mode-suite/publicApi.test.ts
+++ b/test/standard-mode-suite/publicApi.test.ts
@@ -1,15 +1,15 @@
 'use strict';
 
 import * as assert from 'assert';
+import * as fse from 'fs-extra';
 import * as path from 'path';
-import { ExtensionAPI, extensionApiVersion, ClasspathResult } from '../../src/extension.api';
-import { Uri, DocumentSymbol, extensions, commands } from 'vscode';
+import { env } from 'process';
+import { commands, DocumentSymbol, extensions, Uri } from 'vscode';
+import { Commands } from '../../src/commands';
+import { ClasspathResult, ExtensionAPI, extensionApiVersion } from '../../src/extension.api';
 import { ServerMode } from '../../src/settings';
-import * as fse from 'fs-extra';
 import { getJavaConfiguration } from '../../src/utils';
-import { Commands } from '../../src/commands';
 import { constants } from '../common';
-import { env } from 'process';
 
 const pomPath: string = path.join(constants.projectFsPath, 'pom.xml');
 const gradleTestFolder: string = path.join(constants.projectFsPath, 'testGradle');
@@ -34,6 +34,7 @@ suite('Public APIs - Standard', () => {
 
 	test('status should be correct', async function () {
 		const api: ExtensionAPI = extensions.getExtension('redhat.java').exports;
+		await api.serverReady();
 		assert.equal(api.status, 'Started');
 	});