Skip to content

Commit 8c41225

Browse files
committed
POC for avoiding waiting on vscode-microprofile
Work in progress. Instead of directly depending on vscode-microprofile, send the user a notification with a button to install the extension. The user can reject the installation. They will be free to use: * qute syntax highlight * properties file highlight * project generator but no new language features will appear, and they will be prompted to install vscode-microprofile when attempting to run commands that require it. Since vscode-quarkus doesn't wait on vscode-microprofile starting anymore, it can start the project generator without starting vscode-microprofile or even vscode-java. This allows for it to work in rootless mode. Closes redhat-developer#323 Signed-off-by: David Thompson <davthomp@redhat.com>
1 parent 68dc8f8 commit 8c41225

File tree

8 files changed

+191
-31
lines changed

8 files changed

+191
-31
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
],
5151
"main": "./dist/extension",
5252
"extensionDependencies": [
53-
"redhat.vscode-microprofile",
5453
"redhat.java",
5554
"vscjava.vscode-java-debug",
5655
"redhat.vscode-commons"

src/commands/registerCommands.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { commands, ExtensionContext, window } from "vscode";
22
import { VSCodeCommands } from "../definitions/constants";
33
import { ProjectLabelInfo } from "../definitions/ProjectLabelInfo";
4+
import { installMPExtForFeature, isToolsForMicroProfileInstalled, microProfileToolsStarted } from "../requirements/toolsForMicroProfile";
45
import { requestStandardMode } from "../utils/requestStandardMode";
56
import { sendCommandFailedTelemetry, sendCommandSucceededTelemetry } from "../utils/telemetryUtils";
67
import { WelcomeWebview } from "../webviews/WelcomeWebview";
78
import { addExtensionsWizard } from "../wizards/addExtensions/addExtensionsWizard";
89
import { startDebugging } from "../wizards/debugging/startDebugging";
9-
import { generateProjectWizard } from "../wizards/generateProject/generationWizard";
1010
import { deployToOpenShift } from "../wizards/deployToOpenShift/deployToOpenShift";
11+
import { generateProjectWizard } from "../wizards/generateProject/generationWizard";
1112

1213
const NOT_A_QUARKUS_PROJECT = new Error('No Quarkus projects were detected in this folder');
1314
const STANDARD_MODE_REQUEST_FAILED = new Error('Error occurred while requesting standard mode from the Java language server');
@@ -89,6 +90,15 @@ async function registerCommandWithTelemetry(context: ExtensionContext, commandNa
8990
*/
9091
function withStandardMode(commandAction: () => Promise<any>, commandDescription: string): () => Promise<void> {
9192
return async () => {
93+
if (!isToolsForMicroProfileInstalled()) {
94+
await installMPExtForFeature(commandDescription);
95+
// You need to reload the window after installing Tools for MicroProfile
96+
// before any of the features are available.
97+
// Return early instead of attempting to run the command.
98+
return;
99+
} else {
100+
await microProfileToolsStarted();
101+
}
92102
let isStandardMode = false;
93103
try {
94104
isStandardMode = await requestStandardMode(commandDescription);

src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import quarkusProjectListener from './QuarkusProjectListener';
2525
import { terminalCommandRunner } from './terminal/terminalCommandRunner';
2626
import { WelcomeWebview } from './webviews/WelcomeWebview';
2727
import { createTerminateDebugListener } from './wizards/debugging/terminateProcess';
28+
import { installMPExtOnStartup } from './requirements/toolsForMicroProfile';
2829

2930
export async function activate(context: ExtensionContext) {
3031

@@ -33,8 +34,11 @@ export async function activate(context: ExtensionContext) {
3334

3435
QuarkusContext.setContext(context);
3536
displayWelcomePageIfNeeded(context);
37+
3638
commands.executeCommand('setContext', 'quarkusProjectExistsOrLightWeight', true);
3739

40+
installMPExtOnStartup();
41+
3842
context.subscriptions.push(createTerminateDebugListener());
3943
quarkusProjectListener.getQuarkusProjectListener().then((disposableListener: Disposable) => {
4044
context.subscriptions.push(disposableListener);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright 2021 Red Hat, Inc. and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { extensions, window } from "vscode";
17+
import { QuarkusContext } from "../QuarkusContext";
18+
import { installExtension, isExtensionInstalled } from "../utils/extensionInstallationUtils";
19+
20+
const TOOLS_FOR_MICRO_PROFILE_EXT = 'redhat.vscode-microprofile';
21+
22+
const STARTUP_INSTALL_MEMO = 'mpExtInstallOnStartup.isIgnored';
23+
24+
/**
25+
* Returns true if Tools for MicroProfile is installed, and false otherwise
26+
*
27+
* @returns true if Tools for MicroProfile is installed, and false otherwise
28+
*/
29+
export function isToolsForMicroProfileInstalled(): boolean {
30+
return isExtensionInstalled(TOOLS_FOR_MICRO_PROFILE_EXT);
31+
}
32+
33+
/**
34+
* Prompts the user to install Tools for MicroProfile if they don't have it installed
35+
*
36+
* Allows the user to silence this prompt in the future by clicking on a button.
37+
* Warns the user that only some functionality is available if they choose not to install vscode-microprofile.
38+
*
39+
* @returns when the user has installed Tools for MicroProfile,
40+
* has chosen not to install Tools for MicroProfile,
41+
* or its detected that they've silenced this popup
42+
*/
43+
export async function installMPExtOnStartup(): Promise<void> {
44+
if (isExtensionInstalled(TOOLS_FOR_MICRO_PROFILE_EXT)) {
45+
return;
46+
}
47+
const installOnStartupIsIgnored = QuarkusContext.getExtensionContext().globalState.get(STARTUP_INSTALL_MEMO, false);
48+
if (installOnStartupIsIgnored) {
49+
return;
50+
}
51+
const YES = 'Install';
52+
const NO = 'Don\'t install';
53+
const NOT_AGAIN = 'Don\'t ask me again';
54+
const result = await window.showWarningMessage('vscode-quarkus depends on Tools for MicroProfile for many of its features, '
55+
+ 'but can provide some functionality without it. '
56+
+ 'Install Tools for MicroProfile now? '
57+
+ 'You will need to reload the window after the installation.', YES, NO, NOT_AGAIN);
58+
if (result === YES) {
59+
try {
60+
await installExtension(TOOLS_FOR_MICRO_PROFILE_EXT);
61+
} catch (e) {
62+
window.showErrorMessage(e);
63+
}
64+
} else if (result === NOT_AGAIN) {
65+
QuarkusContext.getExtensionContext().globalState.update(STARTUP_INSTALL_MEMO, true);
66+
limitedFunctionalityWarning();
67+
} else {
68+
limitedFunctionalityWarning();
69+
}
70+
}
71+
72+
/**
73+
* Prompts the user to install Tools for MicroProfile in order to use a feature
74+
*
75+
* TODO: this workflow is a mess
76+
*
77+
* @param feature the feature that requires Tools for MicroProfile in order to run
78+
* @returns when the user has attempted to install
79+
*/
80+
export async function installMPExtForFeature(feature: string) {
81+
if (isExtensionInstalled(TOOLS_FOR_MICRO_PROFILE_EXT)) {
82+
await microProfileToolsStarted();
83+
}
84+
const YES = 'Install';
85+
const NO = `Cancel ${feature}`;
86+
const result = await window.showWarningMessage(`${feature} requires Tools for MicroProfile. Install it now? `
87+
+ 'You will need to reload the window after the installation.',
88+
YES, NO);
89+
if (result === YES) {
90+
try {
91+
await installExtension(TOOLS_FOR_MICRO_PROFILE_EXT);
92+
} catch (e) {
93+
window.showErrorMessage(e);
94+
}
95+
} else {
96+
throw new Error(`${feature} requires Tools for MicroProfile, so it can't be run.`);
97+
}
98+
}
99+
100+
export async function microProfileToolsStarted(): Promise<void> {
101+
await extensions.getExtension(TOOLS_FOR_MICRO_PROFILE_EXT).activate();
102+
}
103+
104+
async function limitedFunctionalityWarning(): Promise<void> {
105+
await window.showInformationMessage('vscode-quarkus will run with limited functionality');
106+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2021 Red Hat, Inc. and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { commands, Disposable, extensions } from "vscode";
17+
18+
export const EXT_DOWNLOAD_TIMEOUT_ERROR = new Error('Extension installation is taking a while');
19+
20+
const DOWNLOAD_TIMEOUT = 60000;
21+
22+
/**
23+
* Installs the extension with the given id
24+
*
25+
* @param extensionId the id (`"${publisher}.${name}"`) of the extension to check
26+
* @returns when the extension is installed
27+
* @throws `EXT_DOWNLOAD_TIMEOUT_ERROR` when the extension installation takes a while,
28+
* or the extension installation fails
29+
*/
30+
export async function installExtension(extensionId: string): Promise<void> {
31+
let installListenerDisposable: Disposable;
32+
return new Promise<void>((resolve, reject) => {
33+
installListenerDisposable = extensions.onDidChange(() => {
34+
if (isExtensionInstalled(extensionId)) {
35+
resolve();
36+
}
37+
});
38+
commands.executeCommand("workbench.extensions.installExtension", extensionId)
39+
.then((_unused: any) => { }, reject);
40+
setTimeout(reject, DOWNLOAD_TIMEOUT, EXT_DOWNLOAD_TIMEOUT_ERROR);
41+
}).finally(() => {
42+
installListenerDisposable.dispose();
43+
});
44+
}
45+
46+
/**
47+
* Returns true if the extension is installed and false otherwise
48+
*
49+
* @param extensionId the id (`"${publisher}.${name}"`) of the extension to check
50+
* @returns true if the extension is installed and false otherwise
51+
*/
52+
export function isExtensionInstalled(extensionId: string): boolean {
53+
return !!extensions.getExtension(extensionId);
54+
}

src/utils/openShiftConnectorUtils.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { commands, Disposable, Extension, extensions, ProgressLocation, Uri, window } from "vscode";
16+
import { commands, Extension, extensions, ProgressLocation, Uri, window } from "vscode";
17+
import { EXT_DOWNLOAD_TIMEOUT_ERROR, installExtension, isExtensionInstalled } from "./extensionInstallationUtils";
1718

1819
export const OPENSHIFT_CONNECTOR_EXTENSION_ID = 'redhat.vscode-openshift-connector';
1920
export const OPENSHIFT_CONNECTOR = 'OpenShift Connector extension';
20-
const DOWNLOAD_TIMEOUT = 60000; // Timeout for downloading VSCode OpenShift Connector, in milliseconds
21-
22-
/**
23-
* Returns true if the OpenShift connector extension is installed, and false otherwise
24-
*
25-
* @returns true if the OpenShift connector extension is installed, and false otherwise
26-
*/
27-
export function isOpenShiftConnectorInstalled(): boolean {
28-
return !!extensions.getExtension(OPENSHIFT_CONNECTOR_EXTENSION_ID);
29-
}
3021

3122
/**
3223
* Returns the OpenShift Connector extension API
@@ -35,7 +26,7 @@ export function isOpenShiftConnectorInstalled(): boolean {
3526
* @returns the OpenShift Connector extension API
3627
*/
3728
export async function getOpenShiftConnector(): Promise<any> {
38-
if (!isOpenShiftConnectorInstalled()) {
29+
if (!isExtensionInstalled(OPENSHIFT_CONNECTOR_EXTENSION_ID)) {
3930
throw new Error(`${OPENSHIFT_CONNECTOR} is not installed`);
4031
}
4132
const openShiftConnector: Extension<any> = extensions.getExtension(OPENSHIFT_CONNECTOR_EXTENSION_ID);
@@ -52,19 +43,14 @@ export async function getOpenShiftConnector(): Promise<any> {
5243
* @throws if the user refuses to install the extension, or if the extension does not get installed within a timeout period
5344
*/
5445
async function installOpenShiftConnector(): Promise<void> {
55-
let installListenerDisposable: Disposable;
56-
return new Promise<void>((resolve, reject) => {
57-
installListenerDisposable = extensions.onDidChange(() => {
58-
if (isOpenShiftConnectorInstalled()) {
59-
resolve();
60-
}
61-
});
62-
commands.executeCommand("workbench.extensions.installExtension", OPENSHIFT_CONNECTOR_EXTENSION_ID)
63-
.then((_unused: any) => { }, reject);
64-
setTimeout(reject, DOWNLOAD_TIMEOUT, new Error(`${OPENSHIFT_CONNECTOR} installation is taking a while. Cancelling 'Deploy to OpenShift'. Please retry after the OpenShift Connector installation has finished`));
65-
}).finally(() => {
66-
installListenerDisposable.dispose();
67-
});
46+
try {
47+
installExtension(OPENSHIFT_CONNECTOR_EXTENSION_ID);
48+
} catch (e) {
49+
if (e === EXT_DOWNLOAD_TIMEOUT_ERROR) {
50+
throw new Error(`${OPENSHIFT_CONNECTOR} installation is taking a while. Cancelling 'Deploy to OpenShift'. Please retry after the OpenShift Connector installation has finished`);
51+
}
52+
throw e;
53+
}
6854
}
6955

7056
/**

src/wizards/deployToOpenShift/deployToOpenShift.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616
import { Uri, window } from "vscode";
1717
import { ProjectLabelInfo } from "../../definitions/ProjectLabelInfo";
18-
import { deployQuarkusProject, getOpenShiftConnector, installOpenShiftConnectorWithProgress, isOpenShiftConnectorInstalled, OPENSHIFT_CONNECTOR } from "../../utils/openShiftConnectorUtils";
18+
import { isExtensionInstalled } from "../../utils/extensionInstallationUtils";
19+
import { deployQuarkusProject, getOpenShiftConnector, installOpenShiftConnectorWithProgress, OPENSHIFT_CONNECTOR, OPENSHIFT_CONNECTOR_EXTENSION_ID } from "../../utils/openShiftConnectorUtils";
1920
import { getQuarkusProject } from "../getQuarkusProject";
2021

2122
/**
@@ -38,7 +39,7 @@ export async function deployToOpenShift(): Promise<void> {
3839
* @returns the OpenShift Connector extension API
3940
*/
4041
async function installOpenShiftConnectorIfNeeded(): Promise<any> {
41-
if (isOpenShiftConnectorInstalled()) {
42+
if (isExtensionInstalled(OPENSHIFT_CONNECTOR_EXTENSION_ID)) {
4243
return getOpenShiftConnector();
4344
}
4445
return askToInstallOpenShiftConnector();
@@ -57,7 +58,7 @@ async function askToInstallOpenShiftConnector(): Promise<any> {
5758
if (response === YES) {
5859
try {
5960
await installOpenShiftConnectorWithProgress();
60-
if (isOpenShiftConnectorInstalled()) {
61+
if (isExtensionInstalled(OPENSHIFT_CONNECTOR_EXTENSION_ID)) {
6162
return getOpenShiftConnector();
6263
}
6364
} catch (e) {

0 commit comments

Comments
 (0)