Use a progress reporter to hint the current build status when run or debug a program (#919)

* Use a progress reporter to hint the current build status when run or debug a program

* Align build and configuration error messages with progress reporter

* refactor the progress api based on feedback

* Hide progress reporter only when necessary

* Refactor the progressReporter api interfaces per comments

* Simplify report method

* Refine the progress message

* Add overload api for createProgressReporter
This commit is contained in:
Jinbo Wang 2020-12-15 14:56:46 +08:00 committed by GitHub
parent b41dabe19a
commit b9096ce3b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 638 additions and 196 deletions

View File

@ -121,6 +121,7 @@ Please also check the documentation of [Language Support for Java by Red Hat](ht
- `java.debug.settings.jdwp.limitOfVariablesPerJdwpRequest`: The maximum number of variables or fields that can be requested in one JDWP request. The higher the value, the less frequently debuggee will be requested when expanding the variable view. Also a large number can cause JDWP request timeout. Defaults to 100. - `java.debug.settings.jdwp.limitOfVariablesPerJdwpRequest`: The maximum number of variables or fields that can be requested in one JDWP request. The higher the value, the less frequently debuggee will be requested when expanding the variable view. Also a large number can cause JDWP request timeout. Defaults to 100.
- `java.debug.settings.jdwp.requestTimeout`: The timeout (ms) of JDWP request when the debugger communicates with the target JVM. Defaults to 3000. - `java.debug.settings.jdwp.requestTimeout`: The timeout (ms) of JDWP request when the debugger communicates with the target JVM. Defaults to 3000.
- `java.debug.settings.vmArgs`: The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json. - `java.debug.settings.vmArgs`: The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json.
- `java.silentNotification`: Controls whether notifications can be used to report progress. If true, use status bar to report progress instead. Defaults to `false`.
Pro Tip: The documentation [Configuration.md](https://github.com/microsoft/vscode-java-debug/blob/master/Configuration.md) provides lots of samples to demonstrate how to use these debug configurations, recommend to take a look. Pro Tip: The documentation [Configuration.md](https://github.com/microsoft/vscode-java-debug/blob/master/Configuration.md) provides lots of samples to demonstrate how to use these debug configurations, recommend to take a look.

36
package-lock.json generated
View File

@ -59,9 +59,15 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "8.10.51", "version": "14.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.51.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
"integrity": "sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==", "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==",
"dev": true
},
"@types/uuid": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
"dev": true "dev": true
}, },
"@types/vscode": { "@types/vscode": {
@ -1135,6 +1141,11 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true "dev": true
}, },
"compare-versions": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
"integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA=="
},
"component-emitter": { "component-emitter": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@ -5797,9 +5808,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.5.3", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==",
"dev": true "dev": true
}, },
"unc-path-regex": { "unc-path-regex": {
@ -5973,9 +5984,9 @@
"dev": true "dev": true
}, },
"uuid": { "uuid": {
"version": "3.4.0", "version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.0.3", "version": "2.0.3",
@ -6083,6 +6094,13 @@
"requires": { "requires": {
"uuid": "^3.4.0", "uuid": "^3.4.0",
"vscode-extension-telemetry": "^0.1.6" "vscode-extension-telemetry": "^0.1.6"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
}
} }
}, },
"vscode-test": { "vscode-test": {

View File

@ -741,6 +741,11 @@
"type": "string", "type": "string",
"description": "%java.debugger.configuration.vmArgs.description%", "description": "%java.debugger.configuration.vmArgs.description%",
"default": "" "default": ""
},
"java.silentNotification": {
"type": "boolean",
"description": "%java.debugger.configuration.silentNotification%",
"default": false
} }
} }
} }
@ -756,7 +761,8 @@
"@types/glob": "^7.1.3", "@types/glob": "^7.1.3",
"@types/lodash": "^4.14.137", "@types/lodash": "^4.14.137",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"@types/node": "^8.10.51", "@types/node": "^14.14.10",
"@types/uuid": "^8.3.0",
"@types/vscode": "1.49.0", "@types/vscode": "1.49.0",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
@ -765,13 +771,15 @@
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"ts-loader": "^5.4.5", "ts-loader": "^5.4.5",
"tslint": "^5.18.0", "tslint": "^5.18.0",
"typescript": "^3.5.3", "typescript": "^4.1.2",
"vscode-test": "^1.2.0", "vscode-test": "^1.2.0",
"webpack": "^4.39.2", "webpack": "^4.39.2",
"webpack-cli": "^3.3.7" "webpack-cli": "^3.3.7"
}, },
"dependencies": { "dependencies": {
"compare-versions": "^3.6.0",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"uuid": "^8.3.1",
"vscode-extension-telemetry": "^0.1.6", "vscode-extension-telemetry": "^0.1.6",
"vscode-extension-telemetry-wrapper": "^0.8.0" "vscode-extension-telemetry-wrapper": "^0.8.0"
} }

View File

@ -58,5 +58,6 @@
"java.debugger.configuration.exceptionBreakpoint.skipClasses": "Skip the specified classes when breaking on exception. You could use the built-in variables such as '$JDK' and '$Libraries' to skip a group of classes, or add a specific class name expression, e.g. java.*, *.Foo", "java.debugger.configuration.exceptionBreakpoint.skipClasses": "Skip the specified classes when breaking on exception. You could use the built-in variables such as '$JDK' and '$Libraries' to skip a group of classes, or add a specific class name expression, e.g. java.*, *.Foo",
"java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "The maximum number of variables or fields that can be requested in one JDWP request. The higher the value, the less frequently debuggee will be requested when expanding the variable view. Also a large number can cause JDWP request timeout.", "java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "The maximum number of variables or fields that can be requested in one JDWP request. The higher the value, the less frequently debuggee will be requested when expanding the variable view. Also a large number can cause JDWP request timeout.",
"java.debugger.configuration.jdwp.requestTimeout.description": "The timeout (ms) of JDWP request when the debugger communicates with the target JVM.", "java.debugger.configuration.jdwp.requestTimeout.description": "The timeout (ms) of JDWP request when the debugger communicates with the target JVM.",
"java.debugger.configuration.vmArgs.description": "The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json." "java.debugger.configuration.vmArgs.description": "The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json.",
"java.debugger.configuration.silentNotification": "Controls whether notifications can be used to report progress. If true, use status bar to report progress instead."
} }

View File

@ -56,5 +56,6 @@
"java.debugger.configuration.exceptionBreakpoint.skipClasses": "当发生异常时,跳过指定的类。你可以使用内置变量,如'$JDK'和'$Libraries'来跳过一组类或者添加一个特定的类名表达式如java.**.Foo。", "java.debugger.configuration.exceptionBreakpoint.skipClasses": "当发生异常时,跳过指定的类。你可以使用内置变量,如'$JDK'和'$Libraries'来跳过一组类或者添加一个特定的类名表达式如java.**.Foo。",
"java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "一次JDWP请求中可以请求的变量或字段的最大数量。该值越高在展开变量视图时请求debuggee的频率就越低。同时数量过大也会导致JDWP请求超时。", "java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "一次JDWP请求中可以请求的变量或字段的最大数量。该值越高在展开变量视图时请求debuggee的频率就越低。同时数量过大也会导致JDWP请求超时。",
"java.debugger.configuration.jdwp.requestTimeout.description": "调试器与目标JVM通信时JDWP请求的超时时间ms。", "java.debugger.configuration.jdwp.requestTimeout.description": "调试器与目标JVM通信时JDWP请求的超时时间ms。",
"java.debugger.configuration.vmArgs.description": "启动Java程序的默认VM参数。例如使用'-Xmx1G -ea'将堆大小增加到1GB并启用断言。如果要为特定的调试会话定制VM参数请修改launch.json中的'vmArgs'配置。" "java.debugger.configuration.vmArgs.description": "启动Java程序的默认VM参数。例如使用'-Xmx1G -ea'将堆大小增加到1GB并启用断言。如果要为特定的调试会话定制VM参数请修改launch.json中的'vmArgs'配置。",
"java.debugger.configuration.silentNotification": "控制是否可以使用通知来报告进度。如果为真,则使用状态栏来报告进度。"
} }

View File

@ -7,16 +7,24 @@ import { instrumentOperation, sendInfo, sendOperationError, setErrorCode } from
import * as anchor from "./anchor"; import * as anchor from "./anchor";
import * as commands from "./commands"; import * as commands from "./commands";
import * as lsPlugin from "./languageServerPlugin"; import * as lsPlugin from "./languageServerPlugin";
import { IProgressReporter } from "./progressAPI";
import * as utility from "./utility"; import * as utility from "./utility";
const JAVA_DEBUG_CONFIGURATION = "java.debug.settings"; const JAVA_DEBUG_CONFIGURATION = "java.debug.settings";
const ON_BUILD_FAILURE_PROCEED = "onBuildFailureProceed"; const ON_BUILD_FAILURE_PROCEED = "onBuildFailureProceed";
export async function buildWorkspace(): Promise<boolean> { enum CompileWorkspaceStatus {
FAILED = 0,
SUCCEED = 1,
WITHERROR = 2,
CANCELLED = 3,
}
export async function buildWorkspace(progressReporter: IProgressReporter): Promise<boolean> {
const buildResult = await instrumentOperation("build", async (operationId: string) => { const buildResult = await instrumentOperation("build", async (operationId: string) => {
let error; let error;
try { try {
await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false); await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false, progressReporter.getCancellationToken());
} catch (err) { } catch (err) {
error = err; error = err;
} }
@ -27,14 +35,14 @@ export async function buildWorkspace(): Promise<boolean> {
}; };
})(); })();
if (buildResult.error) { if (progressReporter.isCancelled() || buildResult.error === CompileWorkspaceStatus.CANCELLED) {
return handleBuildFailure(buildResult.operationId, buildResult.error); return false;
} else {
return handleBuildFailure(buildResult.operationId, buildResult.error, progressReporter);
} }
return true;
} }
async function handleBuildFailure(operationId: string, err: any): Promise<boolean> { async function handleBuildFailure(operationId: string, err: any, progressReporter: IProgressReporter): Promise<boolean> {
const configuration = vscode.workspace.getConfiguration(JAVA_DEBUG_CONFIGURATION); const configuration = vscode.workspace.getConfiguration(JAVA_DEBUG_CONFIGURATION);
const onBuildFailureProceed = configuration.get<boolean>(ON_BUILD_FAILURE_PROCEED); const onBuildFailureProceed = configuration.get<boolean>(ON_BUILD_FAILURE_PROCEED);
@ -48,13 +56,13 @@ async function handleBuildFailure(operationId: string, err: any): Promise<boolea
}); });
setErrorCode(error, Number(err)); setErrorCode(error, Number(err));
sendOperationError(operationId, "build", error); sendOperationError(operationId, "build", error);
if (err === lsPlugin.CompileWorkspaceStatus.WITHERROR || err === lsPlugin.CompileWorkspaceStatus.FAILED) { if (!onBuildFailureProceed && (err === lsPlugin.CompileWorkspaceStatus.WITHERROR || err === lsPlugin.CompileWorkspaceStatus.FAILED)) {
if (checkErrorsReportedByJavaExtension()) { if (checkErrorsReportedByJavaExtension()) {
vscode.commands.executeCommand("workbench.actions.view.problems"); vscode.commands.executeCommand("workbench.actions.view.problems");
} }
const ans = onBuildFailureProceed ? "Proceed" : (await vscode.window.showErrorMessage("Build failed, do you want to continue?", progressReporter.hide(true);
"Proceed", "Fix...", "Cancel")); const ans = await vscode.window.showErrorMessage("Build failed, do you want to continue?", "Proceed", "Fix...", "Cancel");
sendInfo(operationId, { sendInfo(operationId, {
operationName: "build", operationName: "build",
choiceForBuildError: ans || "esc", choiceForBuildError: ans || "esc",

View File

@ -16,6 +16,8 @@ import { addMoreHelpfulVMArgs, detectLaunchCommandStyle, validateRuntime } from
import { logger, Type } from "./logger"; import { logger, Type } from "./logger";
import { mainClassPicker } from "./mainClassPicker"; import { mainClassPicker } from "./mainClassPicker";
import { resolveJavaProcess } from "./processPicker"; import { resolveJavaProcess } from "./processPicker";
import { IProgressReporter } from "./progressAPI";
import { progressProvider } from "./progressImpl";
import * as utility from "./utility"; import * as utility from "./utility";
const platformNameMappings: {[key: string]: string} = { const platformNameMappings: {[key: string]: string} = {
@ -42,10 +44,10 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
// Returns an initial debug configurations based on contextual information. // Returns an initial debug configurations based on contextual information.
public provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken): public provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken):
vscode.ProviderResult<vscode.DebugConfiguration[]> { vscode.ProviderResult<vscode.DebugConfiguration[]> {
const provideDebugConfigurationsHandler = instrumentOperation("provideDebugConfigurations", (_operationId: string) => { const provideDebugConfigurationsHandler = instrumentOperation("provideDebugConfigurations", (_operationId: string) => {
return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder); return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder, token);
}); });
return provideDebugConfigurationsHandler(); return provideDebugConfigurationsHandler();
} }
@ -68,13 +70,13 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
public resolveDebugConfigurationWithSubstitutedVariables( public resolveDebugConfigurationWithSubstitutedVariables(
folder: vscode.WorkspaceFolder | undefined, folder: vscode.WorkspaceFolder | undefined,
config: vscode.DebugConfiguration, config: vscode.DebugConfiguration,
_token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> { token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
const resolveDebugConfigurationHandler = instrumentOperation("resolveDebugConfiguration", (_operationId: string) => { const resolveDebugConfigurationHandler = instrumentOperation("resolveDebugConfiguration", (_operationId: string) => {
try { try {
// See https://github.com/microsoft/vscode-java-debug/issues/778 // See https://github.com/microsoft/vscode-java-debug/issues/778
// Merge the platform specific properties to the global config to simplify the subsequent resolving logic. // Merge the platform specific properties to the global config to simplify the subsequent resolving logic.
this.mergePlatformProperties(config, folder); this.mergePlatformProperties(config, folder);
return this.resolveAndValidateDebugConfiguration(folder, config); return this.resolveAndValidateDebugConfiguration(folder, config, token);
} catch (ex) { } catch (ex) {
utility.showErrorMessage({ utility.showErrorMessage({
type: Type.EXCEPTION, type: Type.EXCEPTION,
@ -86,44 +88,56 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
return resolveDebugConfigurationHandler(); return resolveDebugConfigurationHandler();
} }
private provideDebugConfigurationsAsync(folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken) { private provideDebugConfigurationsAsync(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken) {
return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, (p) => { return new Promise(async (resolve, _reject) => {
return new Promise(async (resolve, _reject) => { const progressReporter = progressProvider.createProgressReporter("Create launch.json", vscode.ProgressLocation.Window);
p.report({ message: "Auto generating configuration..." }); progressReporter.observe(token);
const defaultLaunchConfig = { const defaultLaunchConfig = {
type: "java", type: "java",
name: "Debug (Launch) - Current File", name: "Debug (Launch) - Current File",
request: "launch", request: "launch",
// tslint:disable-next-line // tslint:disable-next-line
mainClass: "${file}", mainClass: "${file}",
}; };
try { try {
const isOnStandardMode = await utility.waitForStandardMode(); const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
if (!isOnStandardMode) { if (!isOnStandardMode) {
resolve([defaultLaunchConfig]); resolve([defaultLaunchConfig]);
return ; return ;
}
const mainClasses = await lsPlugin.resolveMainClass(folder ? folder.uri : undefined);
let cache: {[key: string]: any};
cache = {};
const launchConfigs = mainClasses.map((item) => {
return {
...defaultLaunchConfig,
name: this.constructLaunchConfigName(item.mainClass, cache, item.projectName),
mainClass: item.mainClass,
projectName: item.projectName,
};
});
resolve([defaultLaunchConfig, ...launchConfigs]);
} catch (ex) {
if (ex instanceof utility.JavaExtensionNotEnabledError) {
utility.guideToInstallJavaExtension();
}
p.report({ message: `failed to generate configuration. ${ex}` });
resolve(defaultLaunchConfig);
} }
});
if (progressReporter.isCancelled()) {
resolve([defaultLaunchConfig]);
return;
}
progressReporter.report("Generating Java configuration...");
const mainClasses = await lsPlugin.resolveMainClass(folder ? folder.uri : undefined);
const cache = {};
const launchConfigs = mainClasses.map((item) => {
return {
...defaultLaunchConfig,
name: this.constructLaunchConfigName(item.mainClass, cache, item.projectName),
mainClass: item.mainClass,
projectName: item.projectName,
};
});
if (progressReporter.isCancelled()) {
resolve([defaultLaunchConfig]);
return;
}
resolve([defaultLaunchConfig, ...launchConfigs]);
} catch (ex) {
if (ex instanceof utility.JavaExtensionNotEnabledError) {
utility.guideToInstallJavaExtension();
} else {
// tslint:disable-next-line
console.error(ex);
}
resolve([defaultLaunchConfig]);
} finally {
progressReporter.done();
}
}); });
} }
@ -155,10 +169,23 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
} }
private async resolveAndValidateDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) { private async resolveAndValidateDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration,
token?: vscode.CancellationToken) {
let progressReporter = progressProvider.getProgressReporter(config.__progressId);
if (!progressReporter && config.__progressId) {
return undefined;
} else if (!progressReporter) {
progressReporter = progressProvider.createProgressReporter(config.noDebug ? "Run" : "Debug");
}
progressReporter.observe(token);
if (progressReporter.isCancelled()) {
return undefined;
}
try { try {
const isOnStandardMode = await utility.waitForStandardMode(); const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
if (!isOnStandardMode) { if (!isOnStandardMode || progressReporter.isCancelled()) {
return undefined; return undefined;
} }
@ -191,21 +218,34 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
if (needsBuildWorkspace()) { if (needsBuildWorkspace()) {
const proceed = await buildWorkspace(); progressReporter.report("Compiling...");
const proceed = await buildWorkspace(progressReporter);
if (!proceed) { if (!proceed) {
return undefined; return undefined;
} }
} }
const mainClassOption = await this.resolveLaunchConfig(folder ? folder.uri : undefined, config); if (progressReporter.isCancelled()) {
return undefined;
}
if (!config.mainClass) {
progressReporter.report("Resolving main class...");
} else {
progressReporter.report("Resolving launch configuration...");
}
const mainClassOption = await this.resolveAndValidateMainClass(folder && folder.uri, config, progressReporter);
if (!mainClassOption || !mainClassOption.mainClass) { // Exit silently if the user cancels the prompt fix by ESC. if (!mainClassOption || !mainClassOption.mainClass) { // Exit silently if the user cancels the prompt fix by ESC.
// Exit the debug session. // Exit the debug session.
return undefined; return undefined;
} }
progressReporter.report("Resolving launch configuration...");
config.mainClass = mainClassOption.mainClass; config.mainClass = mainClassOption.mainClass;
config.projectName = mainClassOption.projectName; config.projectName = mainClassOption.projectName;
if (progressReporter.isCancelled()) {
return undefined;
}
if (_.isEmpty(config.classPaths) && _.isEmpty(config.modulePaths)) { if (_.isEmpty(config.classPaths) && _.isEmpty(config.modulePaths)) {
const result = <any[]>(await lsPlugin.resolveClasspath(config.mainClass, config.projectName)); const result = <any[]>(await lsPlugin.resolveClasspath(config.mainClass, config.projectName));
config.modulePaths = result[0]; config.modulePaths = result[0];
@ -229,6 +269,9 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
config.vmArgs = this.concatArgs(config.vmArgs); config.vmArgs = this.concatArgs(config.vmArgs);
} }
if (progressReporter.isCancelled()) {
return undefined;
}
// Populate the class filters to the debug configuration. // Populate the class filters to the debug configuration.
await populateStepFilters(config); await populateStepFilters(config);
@ -293,6 +336,11 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
}); });
} }
if (token?.isCancellationRequested || progressReporter.isCancelled()) {
return undefined;
}
delete config.__progressId;
return config; return config;
} catch (ex) { } catch (ex) {
if (ex instanceof utility.JavaExtensionNotEnabledError) { if (ex instanceof utility.JavaExtensionNotEnabledError) {
@ -306,6 +354,8 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
utility.showErrorMessageWithTroubleshooting(utility.convertErrorToMessage(ex)); utility.showErrorMessageWithTroubleshooting(utility.convertErrorToMessage(ex));
return undefined; return undefined;
} finally {
progressReporter.done();
} }
} }
@ -333,13 +383,18 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
return Object.keys(config).filter((key: string) => key !== "noDebug").length === 0; return Object.keys(config).filter((key: string) => key !== "noDebug").length === 0;
} }
private async resolveLaunchConfig(folder: vscode.Uri | undefined, private async resolveAndValidateMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration,
config: vscode.DebugConfiguration): Promise<lsPlugin.IMainClassOption | undefined> { progressReporter: IProgressReporter): Promise<lsPlugin.IMainClassOption | undefined> {
if (!config.mainClass || this.isFile(config.mainClass)) { if (!config.mainClass || this.isFile(config.mainClass)) {
const currentFile = config.mainClass || _.get(vscode.window.activeTextEditor, "document.uri.fsPath"); const currentFile = config.mainClass || _.get(vscode.window.activeTextEditor, "document.uri.fsPath");
if (currentFile) { if (currentFile) {
const mainEntries = await lsPlugin.resolveMainMethod(vscode.Uri.file(currentFile)); const mainEntries = await lsPlugin.resolveMainMethod(vscode.Uri.file(currentFile));
if (mainEntries.length) { if (progressReporter.isCancelled()) {
return undefined;
} else if (mainEntries.length) {
if (!mainClassPicker.isAutoPicked(mainEntries)) {
progressReporter.hide(true);
}
return mainClassPicker.showQuickPick(mainEntries, "Please select a main class you want to run."); return mainClassPicker.showQuickPick(mainEntries, "Please select a main class you want to run.");
} }
} }
@ -347,13 +402,15 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
const hintMessage = currentFile ? const hintMessage = currentFile ?
`The file '${path.basename(currentFile)}' is not executable, please select a main class you want to run.` : `The file '${path.basename(currentFile)}' is not executable, please select a main class you want to run.` :
"Please select a main class you want to run."; "Please select a main class you want to run.";
return this.promptMainClass(folder, hintMessage); return this.promptMainClass(folder, progressReporter, hintMessage);
} }
const containsExternalClasspaths = !_.isEmpty(config.classPaths) || !_.isEmpty(config.modulePaths); const containsExternalClasspaths = !_.isEmpty(config.classPaths) || !_.isEmpty(config.modulePaths);
const validationResponse = await lsPlugin.validateLaunchConfig(config.mainClass, config.projectName, containsExternalClasspaths, folder); const validationResponse = await lsPlugin.validateLaunchConfig(config.mainClass, config.projectName, containsExternalClasspaths, folder);
if (!validationResponse.mainClass.isValid || !validationResponse.projectName.isValid) { if (progressReporter.isCancelled()) {
return this.fixMainClass(folder, config, validationResponse); return undefined;
} else if (!validationResponse.mainClass.isValid || !validationResponse.projectName.isValid) {
return this.fixMainClass(folder, config, validationResponse, progressReporter);
} }
return { return {
@ -372,7 +429,8 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
private async fixMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration, private async fixMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration,
validationResponse: lsPlugin.ILaunchValidationResponse): Promise<lsPlugin.IMainClassOption | undefined> { validationResponse: lsPlugin.ILaunchValidationResponse, progressReporter: IProgressReporter):
Promise<lsPlugin.IMainClassOption | undefined> {
const errors: string[] = []; const errors: string[] = [];
if (!validationResponse.mainClass.isValid) { if (!validationResponse.mainClass.isValid) {
errors.push(String(validationResponse.mainClass.message)); errors.push(String(validationResponse.mainClass.message));
@ -383,6 +441,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
if (validationResponse.proposals && validationResponse.proposals.length) { if (validationResponse.proposals && validationResponse.proposals.length) {
progressReporter.hide(true);
const answer = await utility.showErrorMessageWithTroubleshooting({ const answer = await utility.showErrorMessageWithTroubleshooting({
message: errors.join(os.EOL), message: errors.join(os.EOL),
type: Type.USAGEERROR, type: Type.USAGEERROR,
@ -438,9 +497,12 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
} }
} }
private async promptMainClass(folder: vscode.Uri | undefined, hintMessage?: string): Promise<lsPlugin.IMainClassOption | undefined> { private async promptMainClass(folder: vscode.Uri | undefined, progressReporter: IProgressReporter, hintMessage?: string):
Promise<lsPlugin.IMainClassOption | undefined> {
const res = await lsPlugin.resolveMainClass(folder); const res = await lsPlugin.resolveMainClass(folder);
if (res.length === 0) { if (progressReporter.isCancelled()) {
return undefined;
} else if (res.length === 0) {
const workspaceFolder = folder ? vscode.workspace.getWorkspaceFolder(folder) : undefined; const workspaceFolder = folder ? vscode.workspace.getWorkspaceFolder(folder) : undefined;
throw new utility.UserError({ throw new utility.UserError({
message: `Cannot find a class with the main method${ workspaceFolder ? " in the folder '" + workspaceFolder.name + "'" : ""}.`, message: `Cannot find a class with the main method${ workspaceFolder ? " in the folder '" + workspaceFolder.name + "'" : ""}.`,
@ -449,6 +511,9 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
}); });
} }
if (!mainClassPicker.isAutoPicked(res)) {
progressReporter.hide(true);
}
return mainClassPicker.showQuickPickWithRecentlyUsed(res, hintMessage || "Select main class<project name>"); return mainClassPicker.showQuickPickWithRecentlyUsed(res, hintMessage || "Select main class<project name>");
} }
} }

View File

@ -9,6 +9,7 @@ import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-w
import { JAVA_LANGID } from "./constants"; import { JAVA_LANGID } from "./constants";
import { initializeHoverProvider } from "./hoverProvider"; import { initializeHoverProvider } from "./hoverProvider";
import { IMainMethod, isOnClasspath, resolveMainMethod } from "./languageServerPlugin"; import { IMainMethod, isOnClasspath, resolveMainMethod } from "./languageServerPlugin";
import { IProgressReporter } from "./progressAPI";
import { getJavaExtensionAPI, isJavaExtEnabled, ServerMode } from "./utility"; import { getJavaExtensionAPI, isJavaExtEnabled, ServerMode } from "./utility";
const JAVA_RUN_CODELENS_COMMAND = "java.debug.runCodeLens"; const JAVA_RUN_CODELENS_COMMAND = "java.debug.runCodeLens";
@ -184,7 +185,8 @@ async function launchJsonExists(workspace?: vscode.Uri): Promise<boolean> {
return !!results.find((launchJson) => vscode.workspace.getWorkspaceFolder(launchJson) === workspaceFolder); return !!results.find((launchJson) => vscode.workspace.getWorkspaceFolder(launchJson) === workspaceFolder);
} }
export async function startDebugging(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean): Promise<boolean> { export async function startDebugging(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean,
progressReporter?: IProgressReporter): Promise<boolean> {
const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(uri); const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(uri);
const workspaceUri: vscode.Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; const workspaceUri: vscode.Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined;
const onClasspath = await isOnClasspath(uri.toString()); const onClasspath = await isOnClasspath(uri.toString());
@ -195,6 +197,7 @@ export async function startDebugging(mainClass: string, projectName: string, uri
const debugConfig: vscode.DebugConfiguration = await constructDebugConfig(mainClass, projectName, workspaceUri); const debugConfig: vscode.DebugConfiguration = await constructDebugConfig(mainClass, projectName, workspaceUri);
debugConfig.projectName = projectName; debugConfig.projectName = projectName;
debugConfig.noDebug = noDebug; debugConfig.noDebug = noDebug;
debugConfig.__progressId = progressReporter?.getId();
return vscode.debug.startDebugging(workspaceFolder, debugConfig); return vscode.debug.startDebugging(workspaceFolder, debugConfig);
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
import * as compareVersions from "compare-versions";
import * as _ from "lodash"; import * as _ from "lodash";
import * as path from "path"; import * as path from "path";
import * as vscode from "vscode"; import * as vscode from "vscode";
@ -18,18 +19,20 @@ import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServ
import { logger, Type } from "./logger"; import { logger, Type } from "./logger";
import { mainClassPicker } from "./mainClassPicker"; import { mainClassPicker } from "./mainClassPicker";
import { pickJavaProcess } from "./processPicker"; import { pickJavaProcess } from "./processPicker";
import { IProgressReporter } from "./progressAPI";
import { progressProvider } from "./progressImpl";
import { JavaTerminalLinkProvder } from "./terminalLinkProvider"; import { JavaTerminalLinkProvder } from "./terminalLinkProvider";
import { initializeThreadOperations } from "./threadOperations"; import { initializeThreadOperations } from "./threadOperations";
import * as utility from "./utility"; import * as utility from "./utility";
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext): Promise<any> {
await initializeFromJsonFile(context.asAbsolutePath("./package.json"), { await initializeFromJsonFile(context.asAbsolutePath("./package.json"), {
firstParty: true, firstParty: true,
}); });
await instrumentOperation("activation", initializeExtension)(context); return instrumentOperation("activation", initializeExtension)(context);
} }
function initializeExtension(_operationId: string, context: vscode.ExtensionContext) { function initializeExtension(_operationId: string, context: vscode.ExtensionContext): any {
// Deprecated // Deprecated
logger.initialize(context, true); logger.initialize(context, true);
@ -72,6 +75,10 @@ function initializeExtension(_operationId: string, context: vscode.ExtensionCont
initializeHotCodeReplace(context); initializeHotCodeReplace(context);
initializeCodeLensProvider(context); initializeCodeLensProvider(context);
initializeThreadOperations(context); initializeThreadOperations(context);
return {
progressProvider,
};
} }
// this method is called when your extension is deactivated // this method is called when your extension is deactivated
@ -225,69 +232,67 @@ async function applyHCR(hcrStatusBar: NotificationBar) {
} }
async function runJavaFile(uri: vscode.Uri, noDebug: boolean) { async function runJavaFile(uri: vscode.Uri, noDebug: boolean) {
const alreadyActivated: boolean = utility.isJavaExtActivated(); const progressReporter = progressProvider.createProgressReporter(noDebug ? "Run" : "Debug");
try { try {
// Wait for Java Language Support extension being on Standard mode. // Wait for Java Language Support extension being on Standard mode.
const isOnStandardMode = await utility.waitForStandardMode(); const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
if (!isOnStandardMode) { if (!isOnStandardMode) {
return; throw new utility.OperationCancelledError("");
}
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (!uri && activeEditor && _.endsWith(path.basename(activeEditor.document.fileName), ".java")) {
uri = activeEditor.document.uri;
}
if (!uri) {
vscode.window.showErrorMessage(`${noDebug ? "Run" : "Debug"} failed. Please open a Java file with main method first.`);
throw new utility.OperationCancelledError("");
}
const mainMethods: IMainMethod[] = await resolveMainMethod(uri);
const hasMainMethods: boolean = mainMethods.length > 0;
const canRunTests: boolean = await canDelegateToJavaTestRunner(uri);
const defaultPlaceHolder: string = "Select the main class to run";
if (!hasMainMethods && !canRunTests) {
progressReporter.report("Resolving main class...");
const mainClasses: IMainClassOption[] = await utility.searchMainMethods();
if (progressReporter.isCancelled()) {
throw new utility.OperationCancelledError("");
}
const placeHolder: string = `The file '${path.basename(uri.fsPath)}' is not executable, please select a main class you want to run.`;
await launchMain(mainClasses, uri, noDebug, progressReporter, placeHolder, false /*autoPick*/);
} else if (hasMainMethods && !canRunTests) {
await launchMain(mainMethods, uri, noDebug, progressReporter, defaultPlaceHolder);
} else if (!hasMainMethods && canRunTests) {
launchTesting(uri, noDebug, progressReporter);
} else {
const launchMainChoice: string = "main() method";
const launchTestChoice: string = "unit tests";
const choice: string | undefined = await vscode.window.showQuickPick(
[launchMainChoice, launchTestChoice],
{ placeHolder: "Please select which kind of task you would like to launch" },
);
if (choice === launchMainChoice) {
await launchMain(mainMethods, uri, noDebug, progressReporter, defaultPlaceHolder);
} else if (choice === launchTestChoice) {
launchTesting(uri, noDebug, progressReporter);
}
} }
} catch (ex) { } catch (ex) {
progressReporter.done();
if (ex instanceof utility.OperationCancelledError) {
return;
}
if (ex instanceof utility.JavaExtensionNotEnabledError) { if (ex instanceof utility.JavaExtensionNotEnabledError) {
utility.guideToInstallJavaExtension(); utility.guideToInstallJavaExtension();
return; return;
} }
if (alreadyActivated) {
vscode.window.showErrorMessage(String((ex && ex.message) || ex));
return;
}
throw ex;
}
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (!uri && activeEditor && _.endsWith(path.basename(activeEditor.document.fileName), ".java")) {
uri = activeEditor.document.uri;
}
if (!uri) {
vscode.window.showErrorMessage(`${noDebug ? "Run" : "Debug"} failed. Please open a Java file with main method first.`);
return;
}
let mainMethods: IMainMethod[] = [];
try {
mainMethods = await resolveMainMethod(uri);
} catch (ex) {
vscode.window.showErrorMessage(String((ex && ex.message) || ex)); vscode.window.showErrorMessage(String((ex && ex.message) || ex));
throw ex;
}
const hasMainMethods: boolean = mainMethods.length > 0;
const canRunTests: boolean = await canDelegateToJavaTestRunner(uri);
const defaultPlaceHolder: string = "Select the main class to run";
if (!hasMainMethods && !canRunTests) {
const mainClasses: IMainClassOption[] = await utility.searchMainMethods();
const placeHolder: string = `The file '${path.basename(uri.fsPath)}' is not executable, please select a main class you want to run.`;
await launchMain(mainClasses, uri, noDebug, placeHolder, false /*autoPick*/);
} else if (hasMainMethods && !canRunTests) {
await launchMain(mainMethods, uri, noDebug, defaultPlaceHolder);
} else if (!hasMainMethods && canRunTests) {
await launchTesting(uri, noDebug);
} else {
const launchMainChoice: string = "main() method";
const launchTestChoice: string = "unit tests";
const choice: string | undefined = await vscode.window.showQuickPick(
[launchMainChoice, launchTestChoice],
{ placeHolder: "Please select which kind of task you would like to launch" },
);
if (choice === launchMainChoice) {
await launchMain(mainMethods, uri, noDebug, defaultPlaceHolder);
} else if (choice === launchTestChoice) {
await launchTesting(uri, noDebug);
}
} }
} }
@ -300,24 +305,38 @@ async function canDelegateToJavaTestRunner(uri: vscode.Uri): Promise<boolean> {
return (await vscode.commands.getCommands()).includes("java.test.editor.run"); return (await vscode.commands.getCommands()).includes("java.test.editor.run");
} }
async function launchTesting(uri: vscode.Uri, noDebug: boolean): Promise<void> { function launchTesting(uri: vscode.Uri, noDebug: boolean, progressReporter: IProgressReporter) {
noDebug ? vscode.commands.executeCommand("java.test.editor.run", uri) : vscode.commands.executeCommand("java.test.editor.debug", uri); const command: string = noDebug ? "java.test.editor.run" : "java.test.editor.debug";
vscode.commands.executeCommand(command, uri, progressReporter);
if (compareVersions(getTestExtensionVersion(), "0.26.1") <= 0) {
throw new utility.OperationCancelledError("");
}
} }
async function launchMain(mainMethods: IMainClassOption[], uri: vscode.Uri, noDebug: boolean, placeHolder: string, function getTestExtensionVersion(): string {
autoPick: boolean = true): Promise<void> { const extension: vscode.Extension<any> | undefined = vscode.extensions.getExtension("vscjava.vscode-java-test");
return extension?.packageJSON.version || "0.0.0";
}
async function launchMain(mainMethods: IMainClassOption[], uri: vscode.Uri, noDebug: boolean, progressReporter: IProgressReporter,
placeHolder: string, autoPick: boolean = true): Promise<void> {
if (!mainMethods || !mainMethods.length) { if (!mainMethods || !mainMethods.length) {
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
"Error: Main method not found in the file, please define the main method as: public static void main(String[] args)"); "Error: Main method not found in the file, please define the main method as: public static void main(String[] args)");
return; throw new utility.OperationCancelledError("");
}
if (!mainClassPicker.isAutoPicked(mainMethods, autoPick)) {
progressReporter.hide(true);
} }
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainMethods, placeHolder, autoPick); const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainMethods, placeHolder, autoPick);
if (!pick) { if (!pick) {
return; throw new utility.OperationCancelledError("");
} }
await startDebugging(pick.mainClass, pick.projectName || "", uri, noDebug); progressReporter.report("Launching main class...");
startDebugging(pick.mainClass, pick.projectName || "", uri, noDebug, progressReporter);
} }
async function runJavaProject(node: any, noDebug: boolean) { async function runJavaProject(node: any, noDebug: boolean) {
@ -329,35 +348,56 @@ async function runJavaProject(node: any, noDebug: boolean) {
throw error; throw error;
} }
const mainClassesOptions: IMainClassOption[] = await utility.searchMainMethods(vscode.Uri.parse(node.uri)); const progressReporter = progressProvider.createProgressReporter(noDebug ? "Run" : "Debug");
if (!mainClassesOptions || !mainClassesOptions.length) { try {
vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' ` progressReporter.report("Resolving main class...");
+ "because it does not contain any main class."); const mainClassesOptions: IMainClassOption[] = await utility.searchMainMethods(vscode.Uri.parse(node.uri));
return; if (progressReporter.isCancelled()) {
} throw new utility.OperationCancelledError("");
}
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions, if (!mainClassesOptions || !mainClassesOptions.length) {
"Select the main class to run."); vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' `
if (!pick) { + "because it does not contain any main class.");
return; throw new utility.OperationCancelledError("");
} }
const projectName: string | undefined = pick.projectName; if (!mainClassPicker.isAutoPicked(mainClassesOptions)) {
const mainClass: string = pick.mainClass; progressReporter.hide(true);
const filePath: string | undefined = pick.filePath; }
const workspaceFolder: vscode.WorkspaceFolder | undefined = filePath ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) : undefined; const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions,
const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspaceFolder); "Select the main class to run.");
const existingConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations; if (!pick || progressReporter.isCancelled()) {
const existConfig: vscode.DebugConfiguration | undefined = _.find(existingConfigs, (config) => { throw new utility.OperationCancelledError("");
return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName); }
});
const debugConfig = existConfig || { progressReporter.report("Launching main class...");
type: "java", const projectName: string | undefined = pick.projectName;
name: `Launch - ${mainClass.substr(mainClass.lastIndexOf(".") + 1)}`, const mainClass: string = pick.mainClass;
request: "launch", const filePath: string | undefined = pick.filePath;
mainClass, const workspaceFolder: vscode.WorkspaceFolder | undefined =
projectName, filePath ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) : undefined;
}; const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspaceFolder);
debugConfig.noDebug = noDebug; const existingConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations;
vscode.debug.startDebugging(workspaceFolder, debugConfig); const existConfig: vscode.DebugConfiguration | undefined = _.find(existingConfigs, (config) => {
return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName);
});
const debugConfig = existConfig || {
type: "java",
name: `Launch - ${mainClass.substr(mainClass.lastIndexOf(".") + 1)}`,
request: "launch",
mainClass,
projectName,
};
debugConfig.noDebug = noDebug;
debugConfig.__progressId = progressReporter.getId();
vscode.debug.startDebugging(workspaceFolder, debugConfig);
} catch (ex) {
progressReporter.done();
if (ex instanceof utility.OperationCancelledError) {
return;
}
throw ex;
}
} }

View File

@ -153,6 +153,22 @@ class MainClassPicker {
return undefined; return undefined;
} }
/**
* Checks whether the item options can be picked automatically without popping up the QuickPick box.
* @param options the item options to pick
* @param autoPick pick it automatically if only one option is available
*/
public isAutoPicked(options: IMainClassOption[], autoPick?: boolean) {
const shouldAutoPick: boolean = (autoPick === undefined ? true : !!autoPick);
if (!options || !options.length) {
return true;
} else if (shouldAutoPick && options.length === 1) {
return true;
}
return false;
}
private getMRUTimestamp(mainClassOption: IMainClassOption): number { private getMRUTimestamp(mainClassOption: IMainClassOption): number {
return this.cache[this.getKey(mainClassOption)] || 0; return this.cache[this.getKey(mainClassOption)] || 0;
} }

View File

@ -77,8 +77,8 @@ export function getProcesses(one: (pid: number, ppid: number, command: string, a
const wmic = join(process.env['WINDIR'] || 'C:\\Windows', 'System32', 'wbem', 'WMIC.exe'); const wmic = join(process.env['WINDIR'] || 'C:\\Windows', 'System32', 'wbem', 'WMIC.exe');
proc = spawn(wmic, [ 'process', 'get', 'CommandLine,CreationDate,ParentProcessId,ProcessId' ]); proc = spawn(wmic, [ 'process', 'get', 'CommandLine,CreationDate,ParentProcessId,ProcessId' ]);
proc.stdout.setEncoding('utf8'); proc.stdout?.setEncoding('utf8');
proc.stdout.on('data', lines(line => { proc.stdout?.on('data', lines(line => {
let matches = CMD_PAT.exec(line.trim()); let matches = CMD_PAT.exec(line.trim());
if (matches && matches.length === 5) { if (matches && matches.length === 5) {
const pid = Number(matches[4]); const pid = Number(matches[4]);
@ -110,8 +110,8 @@ export function getProcesses(one: (pid: number, ppid: number, command: string, a
} else if (process.platform === 'darwin') { // OS X } else if (process.platform === 'darwin') { // OS X
proc = spawn('/bin/ps', [ '-x', '-o', `pid,ppid,comm=${'a'.repeat(256)},command` ]); proc = spawn('/bin/ps', [ '-x', '-o', `pid,ppid,comm=${'a'.repeat(256)},command` ]);
proc.stdout.setEncoding('utf8'); proc.stdout?.setEncoding('utf8');
proc.stdout.on('data', lines(line => { proc.stdout?.on('data', lines(line => {
const pid = Number(line.substr(0, 5)); const pid = Number(line.substr(0, 5));
const ppid = Number(line.substr(6, 5)); const ppid = Number(line.substr(6, 5));
@ -126,8 +126,8 @@ export function getProcesses(one: (pid: number, ppid: number, command: string, a
} else { // linux } else { // linux
proc = spawn('/bin/ps', [ '-ax', '-o', 'pid,ppid,comm:20,command' ]); proc = spawn('/bin/ps', [ '-ax', '-o', 'pid,ppid,comm:20,command' ]);
proc.stdout.setEncoding('utf8'); proc.stdout?.setEncoding('utf8');
proc.stdout.on('data', lines(line => { proc.stdout?.on('data', lines(line => {
const pid = Number(line.substr(0, 5)); const pid = Number(line.substr(0, 5));
const ppid = Number(line.substr(6, 5)); const ppid = Number(line.substr(6, 5));
@ -157,8 +157,8 @@ export function getProcesses(one: (pid: number, ppid: number, command: string, a
reject(err); reject(err);
}); });
proc.stderr.setEncoding('utf8'); proc.stderr?.setEncoding('utf8');
proc.stderr.on('data', data => { proc.stderr?.on('data', data => {
const e = data.toString(); const e = data.toString();
if (e.indexOf('screen size is bogus') >= 0) { if (e.indexOf('screen size is bogus') >= 0) {
// ignore this error silently; see https://github.com/microsoft/vscode/issues/75932 // ignore this error silently; see https://github.com/microsoft/vscode/issues/75932

71
src/progressAPI.ts Normal file
View File

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationToken, ProgressLocation } from "vscode";
export interface IProgressReporter {
/**
* Returns the id of the progress reporter.
*/
getId(): string;
/**
* Reports a progress message update.
* @param message the message to update
* @param increment use `increment` to report discrete progress. Each call with a `increment`
* value will be summed up and reflected as overall progress until 100% is reached.
* Note that currently only `ProgressLocation.Notification` is capable of showing
* discrete progress.
*/
report(message: string, increment?: number): void;
/**
* Shows the progress reporter.
*/
show(): void;
/**
* Hides the progress reporter.
* @param onlyNotifications only hide the progress reporter when it's shown as notification.
*/
hide(onlyNotifications?: boolean): void;
/**
* Returns whether the progress reporter has been cancelled or completed.
*/
isCancelled(): boolean;
/**
* Notifies the work is done that is either the task is completed or the user has cancelled it.
*/
done(): void;
/**
* The CancellationToken to monitor if the progress reporter has been cancelled by the user.
*/
getCancellationToken(): CancellationToken;
/**
* Disposes the progress reporter if the observed token has been cancelled.
* @param token the cancellation token to observe
*/
observe(token?: CancellationToken): void;
}
export interface IProgressProvider {
/**
* Creates a progress reporter.
* @param jobName the job name
* @param progressLocation The location at which progress should show, defaults to `ProgressLocation.Notification`.
* @param cancellable Controls if a cancel button should show to allow the user to cancel the progress reporter,
* defaults to true. Note that currently only `ProgressLocation.Notification` is supporting
* to show a cancel button.
*/
createProgressReporter(jobName: string, progressLocation?: ProgressLocation | { viewId: string }, cancellable?: boolean): IProgressReporter;
/**
* Returns the progress reporter with the progress id.
* @param progressId the progress id
*/
getProgressReporter(progressId: string): IProgressReporter | undefined;
}

159
src/progressImpl.ts Normal file
View File

@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { v4 } from "uuid";
import { CancellationToken, CancellationTokenSource, Disposable, EventEmitter, ProgressLocation,
StatusBarAlignment, StatusBarItem, window, workspace } from "vscode";
import { IProgressProvider, IProgressReporter } from "./progressAPI";
const STATUS_COMMAND: string = "java.show.server.task.status";
class ProgressReporter implements IProgressReporter {
private _id: string = v4();
private _jobName: string;
private _progressLocation: ProgressLocation | { viewId: string };
private _cancellable: boolean = false;
private _message: string;
private _increment: number | undefined;
private _isShown: boolean;
private _tokenSource = new CancellationTokenSource();
private _statusBarItem: StatusBarItem | undefined;
private _cancelProgressEventEmitter: EventEmitter<void>;
private _progressEventEmitter: EventEmitter<void>;
private _disposables: Disposable[] = [];
constructor(jobName: string, progressLocation: ProgressLocation | { viewId: string }, cancellable: boolean) {
this._jobName = jobName;
this._progressLocation = progressLocation || ProgressLocation.Notification;
this._cancellable = cancellable;
const config = workspace.getConfiguration("java");
if (config.silentNotification && this._progressLocation === ProgressLocation.Notification) {
this._progressLocation = ProgressLocation.Window;
}
if (this._progressLocation === ProgressLocation.Window) {
this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 1);
this._statusBarItem.text = `$(sync~spin) ${this._jobName}...`;
this._statusBarItem.command = {
title: "Check Java Build Status",
command: STATUS_COMMAND,
arguments: [],
};
this._statusBarItem.tooltip = "Check Java Build Status";
this._disposables.push(this._statusBarItem);
}
this._cancelProgressEventEmitter = new EventEmitter<any>();
this._progressEventEmitter = new EventEmitter<any>();
this._disposables.push(this._cancelProgressEventEmitter);
this._disposables.push(this._progressEventEmitter);
this._disposables.push(this._tokenSource);
}
public getId(): string {
return this._id;
}
public report(message: string, increment?: number): void {
if (this._statusBarItem) {
const text = message ? `${this._jobName} - ${message}` : `${this._jobName}...`;
this._statusBarItem.text = `$(sync~spin) ${text}`;
}
this._message = message;
this._increment = increment;
this._progressEventEmitter.fire();
this.show();
}
public show(): void {
if (this._statusBarItem) {
this._statusBarItem.show();
return;
}
this.showNativeProgress();
}
public hide(onlyNotifications?: boolean): void {
if (onlyNotifications && this._progressLocation === ProgressLocation.Notification) {
this._cancelProgressEventEmitter.fire();
this._isShown = false;
}
}
public isCancelled(): boolean {
return this.getCancellationToken().isCancellationRequested;
}
public done(): void {
this._tokenSource.cancel();
this._cancelProgressEventEmitter.fire();
this._statusBarItem?.hide();
this._disposables.forEach((disposable) => disposable.dispose());
(<ProgressProvider> progressProvider).remove(this);
}
public getCancellationToken(): CancellationToken {
return this._tokenSource.token;
}
public observe(token?: CancellationToken): void {
token?.onCancellationRequested(() => {
this.done();
});
}
private showNativeProgress() {
if (this._isShown) {
return;
}
this._isShown = true;
window.withProgress<boolean>({
location: this._progressLocation,
title: this._jobName ? `[${this._jobName}](command:${STATUS_COMMAND})` : undefined,
cancellable: this._cancellable,
}, (progress, token) => {
progress.report({
message: this._message,
increment: this._increment,
});
this.observe(token);
this._progressEventEmitter.event(() => {
progress.report({
message: this._message,
increment: this._increment,
});
});
return new Promise((resolve) => {
this._cancelProgressEventEmitter.event(() => {
resolve(true);
});
});
});
}
}
class ProgressProvider implements IProgressProvider {
private store: { [key: string]: IProgressReporter } = {};
public createProgressReporter(jobName: string, progressLocation?: ProgressLocation | { viewId: string },
cancellable?: boolean): IProgressReporter {
const progressReporter = new ProgressReporter(jobName, progressLocation || ProgressLocation.Notification,
cancellable === undefined ? true : !!cancellable);
this.store[progressReporter.getId()] = progressReporter;
return progressReporter;
}
public getProgressReporter(progressId: string): IProgressReporter | undefined {
return this.store[progressId];
}
public remove(progressReporter: IProgressReporter) {
delete this.store[progressReporter.getId()];
}
}
export const progressProvider: IProgressProvider = new ProgressProvider();

View File

@ -6,6 +6,7 @@ import * as vscode from "vscode";
import { sendError, sendInfo, setUserError } from "vscode-extension-telemetry-wrapper"; import { sendError, sendInfo, setUserError } from "vscode-extension-telemetry-wrapper";
import { IMainClassOption, resolveMainClass } from "./languageServerPlugin"; import { IMainClassOption, resolveMainClass } from "./languageServerPlugin";
import { logger, Type } from "./logger"; import { logger, Type } from "./logger";
import { IProgressReporter } from "./progressAPI";
const TROUBLESHOOTING_LINK = "https://github.com/Microsoft/vscode-java-debug/blob/master/Troubleshooting.md"; const TROUBLESHOOTING_LINK = "https://github.com/Microsoft/vscode-java-debug/blob/master/Troubleshooting.md";
const LEARN_MORE = "Learn More"; const LEARN_MORE = "Learn More";
@ -29,6 +30,12 @@ export class JavaExtensionNotEnabledError extends Error {
} }
} }
export class OperationCancelledError extends Error {
constructor(message: string) {
super(message);
}
}
interface ILoggingMessage { interface ILoggingMessage {
type?: Type; type?: Type;
message: string; message: string;
@ -174,13 +181,19 @@ export async function getJavaHome(): Promise<string> {
return ""; return "";
} }
export function getJavaExtensionAPI(): Thenable<any> { export function getJavaExtensionAPI(progressReporter?: IProgressReporter): Thenable<any> {
const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID); const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID);
if (!extension) { if (!extension) {
throw new JavaExtensionNotEnabledError("VS Code Java Extension is not enabled."); throw new JavaExtensionNotEnabledError("VS Code Java Extension is not enabled.");
} }
return extension.activate(); return new Promise<any>(async (resolve) => {
progressReporter?.getCancellationToken().onCancellationRequested(() => {
resolve(undefined);
});
resolve(await extension.activate());
});
} }
export function getJavaExtension(): vscode.Extension<any> | undefined { export function getJavaExtension(): vscode.Extension<any> | undefined {
@ -212,33 +225,46 @@ export enum ServerMode {
* Wait for Java Language Support extension being on Standard mode, * Wait for Java Language Support extension being on Standard mode,
* and return true if the final status is on Standard mode. * and return true if the final status is on Standard mode.
*/ */
export async function waitForStandardMode(): Promise<boolean> { export async function waitForStandardMode(progressReporter: IProgressReporter): Promise<boolean> {
const api = await getJavaExtensionAPI(); if (await isImportingProjects()) {
progressReporter.report("Importing projects...");
}
const api = await getJavaExtensionAPI(progressReporter);
if (!api) {
return false;
}
if (api && api.serverMode === ServerMode.LIGHTWEIGHT) { if (api && api.serverMode === ServerMode.LIGHTWEIGHT) {
const answer = await vscode.window.showInformationMessage("Run/Debug feature requires Java language server to run in Standard mode. " const answer = await vscode.window.showInformationMessage("Run/Debug feature requires Java language server to run in Standard mode. "
+ "Do you want to switch it to Standard mode now?", "Yes", "Cancel"); + "Do you want to switch it to Standard mode now?", "Yes", "Cancel");
if (answer === "Yes") { if (answer === "Yes") {
return vscode.window.withProgress<boolean>({ location: vscode.ProgressLocation.Window }, async (progress) => { if (api.serverMode === ServerMode.STANDARD) {
if (api.serverMode === ServerMode.STANDARD) { return true;
return true; }
}
progress.report({ message: "Switching to Standard mode..." }); progressReporter?.report("Importing projects...");
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
api.onDidServerModeChange((mode: string) => { progressReporter.getCancellationToken().onCancellationRequested(() => {
if (mode === ServerMode.STANDARD) { resolve(false);
resolve(true);
}
});
vscode.commands.executeCommand("java.server.mode.switch", ServerMode.STANDARD, true);
}); });
api.onDidServerModeChange((mode: string) => {
if (mode === ServerMode.STANDARD) {
resolve(true);
}
});
vscode.commands.executeCommand("java.server.mode.switch", ServerMode.STANDARD, true);
}); });
} }
return false; return false;
} else if (api && api.serverMode === ServerMode.HYBRID) { } else if (api && api.serverMode === ServerMode.HYBRID) {
progressReporter.report("Importing projects...");
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
progressReporter.getCancellationToken().onCancellationRequested(() => {
resolve(false);
});
api.onDidServerModeChange((mode: string) => { api.onDidServerModeChange((mode: string) => {
if (mode === ServerMode.STANDARD) { if (mode === ServerMode.STANDARD) {
resolve(true); resolve(true);
@ -251,6 +277,10 @@ export async function waitForStandardMode(): Promise<boolean> {
} }
export async function searchMainMethods(uri?: vscode.Uri): Promise<IMainClassOption[]> { export async function searchMainMethods(uri?: vscode.Uri): Promise<IMainClassOption[]> {
return resolveMainClass(uri);
}
export async function searchMainMethodsWithProgress(uri?: vscode.Uri): Promise<IMainClassOption[]> {
try { try {
return await vscode.window.withProgress<IMainClassOption[]>( return await vscode.window.withProgress<IMainClassOption[]>(
{ location: vscode.ProgressLocation.Window }, { location: vscode.ProgressLocation.Window },
@ -263,3 +293,24 @@ export async function searchMainMethods(uri?: vscode.Uri): Promise<IMainClassOpt
throw ex; throw ex;
} }
} }
async function isImportingProjects(): Promise<boolean> {
const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID);
if (!extension) {
return false;
}
const serverMode = getJavaServerMode();
if (serverMode === ServerMode.STANDARD || serverMode === ServerMode.HYBRID) {
const allCommands = await vscode.commands.getCommands();
return (!extension.isActive && allCommands.includes("java.show.server.task.status"))
|| (extension.isActive && extension.exports?.serverMode === ServerMode.HYBRID);
}
return false;
}
function getJavaServerMode(): ServerMode {
return vscode.workspace.getConfiguration().get("java.server.launchMode")
|| ServerMode.HYBRID;
}

View File

@ -9,7 +9,7 @@
"no-duplicate-variable": true, "no-duplicate-variable": true,
"max-classes-per-file": false, "max-classes-per-file": false,
"no-implicit-dependencies": [ "no-implicit-dependencies": [
false, // Turned off due to: https://github.com/microsoft/vscode/issues/78019 false,
"dev" "dev"
], ],
"no-empty": false, "no-empty": false,