Skip to content

Commit 2b32667

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 2b32667

File tree

7 files changed

+197
-30
lines changed

7 files changed

+197
-30
lines changed

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 { installMPExtForCommand, 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 installMPExtForCommand(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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ProjectLabelInfo } from './definitions/ProjectLabelInfo';
2222
import { PropertiesLanguageMismatch, QuarkusConfig } from './QuarkusConfig';
2323
import { QuarkusContext } from './QuarkusContext';
2424
import quarkusProjectListener from './QuarkusProjectListener';
25+
import { installMPExtOnStartup } from './requirements/toolsForMicroProfile';
2526
import { terminalCommandRunner } from './terminal/terminalCommandRunner';
2627
import { WelcomeWebview } from './webviews/WelcomeWebview';
2728
import { createTerminateDebugListener } from './wizards/debugging/terminateProcess';
@@ -35,6 +36,8 @@ export async function activate(context: ExtensionContext) {
3536
displayWelcomePageIfNeeded(context);
3637
commands.executeCommand('setContext', 'quarkusProjectExistsOrLightWeight', true);
3738

39+
installMPExtOnStartup();
40+
3841
context.subscriptions.push(createTerminateDebugListener());
3942
quarkusProjectListener.getQuarkusProjectListener().then((disposableListener: Disposable) => {
4043
context.subscriptions.push(disposableListener);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
* Prompts the user to install Tools for MicroProfile if they don't have it installed
26+
*
27+
* Allows the user to silence this prompt in the future by clicking on a button.
28+
* Warns the user that only some functionality is available if they choose not to install vscode-microprofile.
29+
*
30+
* @returns when the user has installed Tools for MicroProfile,
31+
* or the user has chosen not to install Tools for MicroProfile,
32+
* or its detected that they've silenced this popup
33+
*/
34+
export async function installMPExtOnStartup(): Promise<void> {
35+
if (isExtensionInstalled(TOOLS_FOR_MICRO_PROFILE_EXT)) {
36+
return;
37+
}
38+
const installOnStartupIsIgnored = QuarkusContext.getExtensionContext().globalState.get(STARTUP_INSTALL_MEMO, false);
39+
if (installOnStartupIsIgnored) {
40+
return;
41+
}
42+
const YES = 'Install';
43+
const NO = 'Don\'t install';
44+
const NOT_AGAIN = 'Don\'t ask me again';
45+
const result = await window.showWarningMessage('vscode-quarkus depends on Tools for MicroProfile for many of its features, '
46+
+ 'but can provide some functionality without it. '
47+
+ 'Install Tools for MicroProfile now? '
48+
+ 'You will need to reload the window after the installation.', YES, NO, NOT_AGAIN);
49+
if (result === YES) {
50+
try {
51+
await installExtension(TOOLS_FOR_MICRO_PROFILE_EXT);
52+
} catch (e) {
53+
window.showErrorMessage(e);
54+
}
55+
} else if (result === NOT_AGAIN) {
56+
QuarkusContext.getExtensionContext().globalState.update(STARTUP_INSTALL_MEMO, true);
57+
limitedFunctionalityWarning();
58+
} else {
59+
limitedFunctionalityWarning();
60+
}
61+
}
62+
63+
/**
64+
* Installs Tools for MicroProfile with the user's permission, in order to use a given command
65+
*
66+
* @param commandDescription description of the command that requires Tools for MicroProfile in order to be used
67+
* @returns when the user refuses to install,
68+
* or when the install succeeds,
69+
* or when the install fails
70+
*/
71+
export async function installMPExtForCommand(commandDescription: string) {
72+
const YES = 'Install';
73+
const NO = `Cancel ${commandDescription}`;
74+
const result = await window.showWarningMessage(`${commandDescription} requires Tools for MicroProfile. Install it now? `
75+
+ 'You will need to reload the window after the installation.',
76+
YES, NO);
77+
if (result === YES) {
78+
try {
79+
await installExtension(TOOLS_FOR_MICRO_PROFILE_EXT);
80+
} catch (e) {
81+
window.showErrorMessage(e);
82+
}
83+
} else {
84+
window.showErrorMessage(`${commandDescription} requires Tools for MicroProfile, so it can't be run.`);
85+
}
86+
}
87+
88+
/**
89+
* Returns true if Tools for MicroProfile is installed, and false otherwise
90+
*
91+
* @returns true if Tools for MicroProfile is installed, and false otherwise
92+
*/
93+
export function isToolsForMicroProfileInstalled(): boolean {
94+
return isExtensionInstalled(TOOLS_FOR_MICRO_PROFILE_EXT);
95+
}
96+
97+
/**
98+
* Returns when Tools for MicroProfile has started
99+
*
100+
* @returns when Tools for MicroProfile has started
101+
*/
102+
export async function microProfileToolsStarted(): Promise<void> {
103+
await extensions.getExtension(TOOLS_FOR_MICRO_PROFILE_EXT).activate();
104+
}
105+
106+
async function limitedFunctionalityWarning(): Promise<void> {
107+
await window.showInformationMessage('vscode-quarkus will run with limited functionality');
108+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
/**
19+
* Error that is throw when the extension download times out
20+
*/
21+
export const EXT_DOWNLOAD_TIMEOUT_ERROR = new Error('Extension installation is taking a while');
22+
23+
const DOWNLOAD_TIMEOUT = 60000;
24+
25+
/**
26+
* Installs the extension with the given id
27+
*
28+
* This function will timeout with an error if the installation takes a while.
29+
* However, the extension installation will not be cancelled.
30+
*
31+
* @param extensionId the id (`"${publisher}.${name}"`) of the extension to check
32+
* @returns when the extension is installed
33+
* @throws `EXT_DOWNLOAD_TIMEOUT_ERROR` when the extension installation takes a while,
34+
* or a different error when the extension installation fails.
35+
*/
36+
export async function installExtension(extensionId: string): Promise<void> {
37+
let installListenerDisposable: Disposable;
38+
return new Promise<void>((resolve, reject) => {
39+
installListenerDisposable = extensions.onDidChange(() => {
40+
if (isExtensionInstalled(extensionId)) {
41+
resolve();
42+
}
43+
});
44+
commands.executeCommand("workbench.extensions.installExtension", extensionId)
45+
.then((_unused: any) => { }, reject);
46+
setTimeout(reject, DOWNLOAD_TIMEOUT, EXT_DOWNLOAD_TIMEOUT_ERROR);
47+
}).finally(() => {
48+
installListenerDisposable.dispose();
49+
});
50+
}
51+
52+
/**
53+
* Returns true if the extension is installed and false otherwise
54+
*
55+
* @param extensionId the id (`"${publisher}.${name}"`) of the extension to check
56+
* @returns `true` if the extension is installed and `false` otherwise
57+
*/
58+
export function isExtensionInstalled(extensionId: string): boolean {
59+
return !!extensions.getExtension(extensionId);
60+
}

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)