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:
parent
b41dabe19a
commit
b9096ce3b8
|
@ -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.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.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.
|
||||
|
||||
|
|
|
@ -59,9 +59,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.10.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.51.tgz",
|
||||
"integrity": "sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==",
|
||||
"version": "14.14.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
|
||||
"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
|
||||
},
|
||||
"@types/vscode": {
|
||||
|
@ -1135,6 +1141,11 @@
|
|||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"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": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
|
@ -5797,9 +5808,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz",
|
||||
"integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==",
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
|
||||
"integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"unc-path-regex": {
|
||||
|
@ -5973,9 +5984,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.0.3",
|
||||
|
@ -6083,6 +6094,13 @@
|
|||
"requires": {
|
||||
"uuid": "^3.4.0",
|
||||
"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": {
|
||||
|
|
12
package.json
12
package.json
|
@ -741,6 +741,11 @@
|
|||
"type": "string",
|
||||
"description": "%java.debugger.configuration.vmArgs.description%",
|
||||
"default": ""
|
||||
},
|
||||
"java.silentNotification": {
|
||||
"type": "boolean",
|
||||
"description": "%java.debugger.configuration.silentNotification%",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -756,7 +761,8 @@
|
|||
"@types/glob": "^7.1.3",
|
||||
"@types/lodash": "^4.14.137",
|
||||
"@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",
|
||||
"cross-env": "^5.2.0",
|
||||
"gulp": "^4.0.2",
|
||||
|
@ -765,13 +771,15 @@
|
|||
"shelljs": "^0.8.3",
|
||||
"ts-loader": "^5.4.5",
|
||||
"tslint": "^5.18.0",
|
||||
"typescript": "^3.5.3",
|
||||
"typescript": "^4.1.2",
|
||||
"vscode-test": "^1.2.0",
|
||||
"webpack": "^4.39.2",
|
||||
"webpack-cli": "^3.3.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"compare-versions": "^3.6.0",
|
||||
"lodash": "^4.17.19",
|
||||
"uuid": "^8.3.1",
|
||||
"vscode-extension-telemetry": "^0.1.6",
|
||||
"vscode-extension-telemetry-wrapper": "^0.8.0"
|
||||
}
|
||||
|
|
|
@ -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.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.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."
|
||||
}
|
||||
|
|
|
@ -56,5 +56,6 @@
|
|||
"java.debugger.configuration.exceptionBreakpoint.skipClasses": "当发生异常时,跳过指定的类。你可以使用内置变量,如'$JDK'和'$Libraries'来跳过一组类,或者添加一个特定的类名表达式,如java.*,*.Foo。",
|
||||
"java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "一次JDWP请求中可以请求的变量或字段的最大数量。该值越高,在展开变量视图时,请求debuggee的频率就越低。同时数量过大也会导致JDWP请求超时。",
|
||||
"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": "控制是否可以使用通知来报告进度。如果为真,则使用状态栏来报告进度。"
|
||||
}
|
28
src/build.ts
28
src/build.ts
|
@ -7,16 +7,24 @@ import { instrumentOperation, sendInfo, sendOperationError, setErrorCode } from
|
|||
import * as anchor from "./anchor";
|
||||
import * as commands from "./commands";
|
||||
import * as lsPlugin from "./languageServerPlugin";
|
||||
import { IProgressReporter } from "./progressAPI";
|
||||
import * as utility from "./utility";
|
||||
|
||||
const JAVA_DEBUG_CONFIGURATION = "java.debug.settings";
|
||||
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) => {
|
||||
let error;
|
||||
try {
|
||||
await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false);
|
||||
await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false, progressReporter.getCancellationToken());
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -27,14 +35,14 @@ export async function buildWorkspace(): Promise<boolean> {
|
|||
};
|
||||
})();
|
||||
|
||||
if (buildResult.error) {
|
||||
return handleBuildFailure(buildResult.operationId, buildResult.error);
|
||||
if (progressReporter.isCancelled() || buildResult.error === CompileWorkspaceStatus.CANCELLED) {
|
||||
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 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));
|
||||
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()) {
|
||||
vscode.commands.executeCommand("workbench.actions.view.problems");
|
||||
}
|
||||
|
||||
const ans = onBuildFailureProceed ? "Proceed" : (await vscode.window.showErrorMessage("Build failed, do you want to continue?",
|
||||
"Proceed", "Fix...", "Cancel"));
|
||||
progressReporter.hide(true);
|
||||
const ans = await vscode.window.showErrorMessage("Build failed, do you want to continue?", "Proceed", "Fix...", "Cancel");
|
||||
sendInfo(operationId, {
|
||||
operationName: "build",
|
||||
choiceForBuildError: ans || "esc",
|
||||
|
|
|
@ -16,6 +16,8 @@ import { addMoreHelpfulVMArgs, detectLaunchCommandStyle, validateRuntime } from
|
|||
import { logger, Type } from "./logger";
|
||||
import { mainClassPicker } from "./mainClassPicker";
|
||||
import { resolveJavaProcess } from "./processPicker";
|
||||
import { IProgressReporter } from "./progressAPI";
|
||||
import { progressProvider } from "./progressImpl";
|
||||
import * as utility from "./utility";
|
||||
|
||||
const platformNameMappings: {[key: string]: string} = {
|
||||
|
@ -42,10 +44,10 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
}
|
||||
|
||||
// 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[]> {
|
||||
const provideDebugConfigurationsHandler = instrumentOperation("provideDebugConfigurations", (_operationId: string) => {
|
||||
return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder);
|
||||
return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder, token);
|
||||
});
|
||||
return provideDebugConfigurationsHandler();
|
||||
}
|
||||
|
@ -68,13 +70,13 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
public resolveDebugConfigurationWithSubstitutedVariables(
|
||||
folder: vscode.WorkspaceFolder | undefined,
|
||||
config: vscode.DebugConfiguration,
|
||||
_token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
|
||||
token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
|
||||
const resolveDebugConfigurationHandler = instrumentOperation("resolveDebugConfiguration", (_operationId: string) => {
|
||||
try {
|
||||
// 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.
|
||||
this.mergePlatformProperties(config, folder);
|
||||
return this.resolveAndValidateDebugConfiguration(folder, config);
|
||||
return this.resolveAndValidateDebugConfiguration(folder, config, token);
|
||||
} catch (ex) {
|
||||
utility.showErrorMessage({
|
||||
type: Type.EXCEPTION,
|
||||
|
@ -86,44 +88,56 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
return resolveDebugConfigurationHandler();
|
||||
}
|
||||
|
||||
private provideDebugConfigurationsAsync(folder: vscode.WorkspaceFolder | undefined, _token?: vscode.CancellationToken) {
|
||||
return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, (p) => {
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
p.report({ message: "Auto generating configuration..." });
|
||||
const defaultLaunchConfig = {
|
||||
type: "java",
|
||||
name: "Debug (Launch) - Current File",
|
||||
request: "launch",
|
||||
// tslint:disable-next-line
|
||||
mainClass: "${file}",
|
||||
};
|
||||
try {
|
||||
const isOnStandardMode = await utility.waitForStandardMode();
|
||||
if (!isOnStandardMode) {
|
||||
resolve([defaultLaunchConfig]);
|
||||
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);
|
||||
private provideDebugConfigurationsAsync(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken) {
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
const progressReporter = progressProvider.createProgressReporter("Create launch.json", vscode.ProgressLocation.Window);
|
||||
progressReporter.observe(token);
|
||||
const defaultLaunchConfig = {
|
||||
type: "java",
|
||||
name: "Debug (Launch) - Current File",
|
||||
request: "launch",
|
||||
// tslint:disable-next-line
|
||||
mainClass: "${file}",
|
||||
};
|
||||
try {
|
||||
const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
|
||||
if (!isOnStandardMode) {
|
||||
resolve([defaultLaunchConfig]);
|
||||
return ;
|
||||
}
|
||||
});
|
||||
|
||||
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 {
|
||||
const isOnStandardMode = await utility.waitForStandardMode();
|
||||
if (!isOnStandardMode) {
|
||||
const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
|
||||
if (!isOnStandardMode || progressReporter.isCancelled()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -191,21 +218,34 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
}
|
||||
|
||||
if (needsBuildWorkspace()) {
|
||||
const proceed = await buildWorkspace();
|
||||
progressReporter.report("Compiling...");
|
||||
const proceed = await buildWorkspace(progressReporter);
|
||||
if (!proceed) {
|
||||
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.
|
||||
// Exit the debug session.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
progressReporter.report("Resolving launch configuration...");
|
||||
config.mainClass = mainClassOption.mainClass;
|
||||
config.projectName = mainClassOption.projectName;
|
||||
|
||||
if (progressReporter.isCancelled()) {
|
||||
return undefined;
|
||||
}
|
||||
if (_.isEmpty(config.classPaths) && _.isEmpty(config.modulePaths)) {
|
||||
const result = <any[]>(await lsPlugin.resolveClasspath(config.mainClass, config.projectName));
|
||||
config.modulePaths = result[0];
|
||||
|
@ -229,6 +269,9 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
config.vmArgs = this.concatArgs(config.vmArgs);
|
||||
}
|
||||
|
||||
if (progressReporter.isCancelled()) {
|
||||
return undefined;
|
||||
}
|
||||
// Populate the class filters to the debug configuration.
|
||||
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;
|
||||
} catch (ex) {
|
||||
if (ex instanceof utility.JavaExtensionNotEnabledError) {
|
||||
|
@ -306,6 +354,8 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
|
||||
utility.showErrorMessageWithTroubleshooting(utility.convertErrorToMessage(ex));
|
||||
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;
|
||||
}
|
||||
|
||||
private async resolveLaunchConfig(folder: vscode.Uri | undefined,
|
||||
config: vscode.DebugConfiguration): Promise<lsPlugin.IMainClassOption | undefined> {
|
||||
private async resolveAndValidateMainClass(folder: vscode.Uri | undefined, config: vscode.DebugConfiguration,
|
||||
progressReporter: IProgressReporter): Promise<lsPlugin.IMainClassOption | undefined> {
|
||||
if (!config.mainClass || this.isFile(config.mainClass)) {
|
||||
const currentFile = config.mainClass || _.get(vscode.window.activeTextEditor, "document.uri.fsPath");
|
||||
if (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.");
|
||||
}
|
||||
}
|
||||
|
@ -347,13 +402,15 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
const hintMessage = currentFile ?
|
||||
`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.";
|
||||
return this.promptMainClass(folder, hintMessage);
|
||||
return this.promptMainClass(folder, progressReporter, hintMessage);
|
||||
}
|
||||
|
||||
const containsExternalClasspaths = !_.isEmpty(config.classPaths) || !_.isEmpty(config.modulePaths);
|
||||
const validationResponse = await lsPlugin.validateLaunchConfig(config.mainClass, config.projectName, containsExternalClasspaths, folder);
|
||||
if (!validationResponse.mainClass.isValid || !validationResponse.projectName.isValid) {
|
||||
return this.fixMainClass(folder, config, validationResponse);
|
||||
if (progressReporter.isCancelled()) {
|
||||
return undefined;
|
||||
} else if (!validationResponse.mainClass.isValid || !validationResponse.projectName.isValid) {
|
||||
return this.fixMainClass(folder, config, validationResponse, progressReporter);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -372,7 +429,8 @@ export class JavaDebugConfigurationProvider implements 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[] = [];
|
||||
if (!validationResponse.mainClass.isValid) {
|
||||
errors.push(String(validationResponse.mainClass.message));
|
||||
|
@ -383,6 +441,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
|
|||
}
|
||||
|
||||
if (validationResponse.proposals && validationResponse.proposals.length) {
|
||||
progressReporter.hide(true);
|
||||
const answer = await utility.showErrorMessageWithTroubleshooting({
|
||||
message: errors.join(os.EOL),
|
||||
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);
|
||||
if (res.length === 0) {
|
||||
if (progressReporter.isCancelled()) {
|
||||
return undefined;
|
||||
} else if (res.length === 0) {
|
||||
const workspaceFolder = folder ? vscode.workspace.getWorkspaceFolder(folder) : undefined;
|
||||
throw new utility.UserError({
|
||||
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>");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-w
|
|||
import { JAVA_LANGID } from "./constants";
|
||||
import { initializeHoverProvider } from "./hoverProvider";
|
||||
import { IMainMethod, isOnClasspath, resolveMainMethod } from "./languageServerPlugin";
|
||||
import { IProgressReporter } from "./progressAPI";
|
||||
import { getJavaExtensionAPI, isJavaExtEnabled, ServerMode } from "./utility";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 workspaceUri: vscode.Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined;
|
||||
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);
|
||||
debugConfig.projectName = projectName;
|
||||
debugConfig.noDebug = noDebug;
|
||||
debugConfig.__progressId = progressReporter?.getId();
|
||||
|
||||
return vscode.debug.startDebugging(workspaceFolder, debugConfig);
|
||||
}
|
||||
|
|
222
src/extension.ts
222
src/extension.ts
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as compareVersions from "compare-versions";
|
||||
import * as _ from "lodash";
|
||||
import * as path from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
@ -18,18 +19,20 @@ import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServ
|
|||
import { logger, Type } from "./logger";
|
||||
import { mainClassPicker } from "./mainClassPicker";
|
||||
import { pickJavaProcess } from "./processPicker";
|
||||
import { IProgressReporter } from "./progressAPI";
|
||||
import { progressProvider } from "./progressImpl";
|
||||
import { JavaTerminalLinkProvder } from "./terminalLinkProvider";
|
||||
import { initializeThreadOperations } from "./threadOperations";
|
||||
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"), {
|
||||
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
|
||||
logger.initialize(context, true);
|
||||
|
||||
|
@ -72,6 +75,10 @@ function initializeExtension(_operationId: string, context: vscode.ExtensionCont
|
|||
initializeHotCodeReplace(context);
|
||||
initializeCodeLensProvider(context);
|
||||
initializeThreadOperations(context);
|
||||
|
||||
return {
|
||||
progressProvider,
|
||||
};
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const alreadyActivated: boolean = utility.isJavaExtActivated();
|
||||
const progressReporter = progressProvider.createProgressReporter(noDebug ? "Run" : "Debug");
|
||||
try {
|
||||
// Wait for Java Language Support extension being on Standard mode.
|
||||
const isOnStandardMode = await utility.waitForStandardMode();
|
||||
const isOnStandardMode = await utility.waitForStandardMode(progressReporter);
|
||||
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) {
|
||||
progressReporter.done();
|
||||
if (ex instanceof utility.OperationCancelledError) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ex instanceof utility.JavaExtensionNotEnabledError) {
|
||||
utility.guideToInstallJavaExtension();
|
||||
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));
|
||||
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");
|
||||
}
|
||||
|
||||
async function launchTesting(uri: vscode.Uri, noDebug: boolean): Promise<void> {
|
||||
noDebug ? vscode.commands.executeCommand("java.test.editor.run", uri) : vscode.commands.executeCommand("java.test.editor.debug", uri);
|
||||
function launchTesting(uri: vscode.Uri, noDebug: boolean, progressReporter: IProgressReporter) {
|
||||
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,
|
||||
autoPick: boolean = true): Promise<void> {
|
||||
function getTestExtensionVersion(): string {
|
||||
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) {
|
||||
vscode.window.showErrorMessage(
|
||||
"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);
|
||||
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) {
|
||||
|
@ -329,35 +348,56 @@ async function runJavaProject(node: any, noDebug: boolean) {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const mainClassesOptions: IMainClassOption[] = await utility.searchMainMethods(vscode.Uri.parse(node.uri));
|
||||
if (!mainClassesOptions || !mainClassesOptions.length) {
|
||||
vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' `
|
||||
+ "because it does not contain any main class.");
|
||||
return;
|
||||
}
|
||||
const progressReporter = progressProvider.createProgressReporter(noDebug ? "Run" : "Debug");
|
||||
try {
|
||||
progressReporter.report("Resolving main class...");
|
||||
const mainClassesOptions: IMainClassOption[] = await utility.searchMainMethods(vscode.Uri.parse(node.uri));
|
||||
if (progressReporter.isCancelled()) {
|
||||
throw new utility.OperationCancelledError("");
|
||||
}
|
||||
|
||||
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions,
|
||||
"Select the main class to run.");
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
if (!mainClassesOptions || !mainClassesOptions.length) {
|
||||
vscode.window.showErrorMessage(`Failed to ${noDebug ? "run" : "debug"} this project '${node._nodeData.displayName || node.name}' `
|
||||
+ "because it does not contain any main class.");
|
||||
throw new utility.OperationCancelledError("");
|
||||
}
|
||||
|
||||
const projectName: string | undefined = pick.projectName;
|
||||
const mainClass: string = pick.mainClass;
|
||||
const filePath: string | undefined = pick.filePath;
|
||||
const workspaceFolder: vscode.WorkspaceFolder | undefined = filePath ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) : undefined;
|
||||
const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspaceFolder);
|
||||
const existingConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations;
|
||||
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;
|
||||
vscode.debug.startDebugging(workspaceFolder, debugConfig);
|
||||
if (!mainClassPicker.isAutoPicked(mainClassesOptions)) {
|
||||
progressReporter.hide(true);
|
||||
}
|
||||
const pick = await mainClassPicker.showQuickPickWithRecentlyUsed(mainClassesOptions,
|
||||
"Select the main class to run.");
|
||||
if (!pick || progressReporter.isCancelled()) {
|
||||
throw new utility.OperationCancelledError("");
|
||||
}
|
||||
|
||||
progressReporter.report("Launching main class...");
|
||||
const projectName: string | undefined = pick.projectName;
|
||||
const mainClass: string = pick.mainClass;
|
||||
const filePath: string | undefined = pick.filePath;
|
||||
const workspaceFolder: vscode.WorkspaceFolder | undefined =
|
||||
filePath ? vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)) : undefined;
|
||||
const launchConfigurations: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("launch", workspaceFolder);
|
||||
const existingConfigs: vscode.DebugConfiguration[] = launchConfigurations.configurations;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,6 +153,22 @@ class MainClassPicker {
|
|||
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 {
|
||||
return this.cache[this.getKey(mainClassOption)] || 0;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
proc = spawn(wmic, [ 'process', 'get', 'CommandLine,CreationDate,ParentProcessId,ProcessId' ]);
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stdout.on('data', lines(line => {
|
||||
proc.stdout?.setEncoding('utf8');
|
||||
proc.stdout?.on('data', lines(line => {
|
||||
let matches = CMD_PAT.exec(line.trim());
|
||||
if (matches && matches.length === 5) {
|
||||
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
|
||||
|
||||
proc = spawn('/bin/ps', [ '-x', '-o', `pid,ppid,comm=${'a'.repeat(256)},command` ]);
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stdout.on('data', lines(line => {
|
||||
proc.stdout?.setEncoding('utf8');
|
||||
proc.stdout?.on('data', lines(line => {
|
||||
|
||||
const pid = Number(line.substr(0, 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
|
||||
|
||||
proc = spawn('/bin/ps', [ '-ax', '-o', 'pid,ppid,comm:20,command' ]);
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stdout.on('data', lines(line => {
|
||||
proc.stdout?.setEncoding('utf8');
|
||||
proc.stdout?.on('data', lines(line => {
|
||||
|
||||
const pid = Number(line.substr(0, 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);
|
||||
});
|
||||
|
||||
proc.stderr.setEncoding('utf8');
|
||||
proc.stderr.on('data', data => {
|
||||
proc.stderr?.setEncoding('utf8');
|
||||
proc.stderr?.on('data', data => {
|
||||
const e = data.toString();
|
||||
if (e.indexOf('screen size is bogus') >= 0) {
|
||||
// ignore this error silently; see https://github.com/microsoft/vscode/issues/75932
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
|
@ -6,6 +6,7 @@ import * as vscode from "vscode";
|
|||
import { sendError, sendInfo, setUserError } from "vscode-extension-telemetry-wrapper";
|
||||
import { IMainClassOption, resolveMainClass } from "./languageServerPlugin";
|
||||
import { logger, Type } from "./logger";
|
||||
import { IProgressReporter } from "./progressAPI";
|
||||
|
||||
const TROUBLESHOOTING_LINK = "https://github.com/Microsoft/vscode-java-debug/blob/master/Troubleshooting.md";
|
||||
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 {
|
||||
type?: Type;
|
||||
message: string;
|
||||
|
@ -174,13 +181,19 @@ export async function getJavaHome(): Promise<string> {
|
|||
return "";
|
||||
}
|
||||
|
||||
export function getJavaExtensionAPI(): Thenable<any> {
|
||||
export function getJavaExtensionAPI(progressReporter?: IProgressReporter): Thenable<any> {
|
||||
const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID);
|
||||
if (!extension) {
|
||||
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 {
|
||||
|
@ -212,33 +225,46 @@ export enum ServerMode {
|
|||
* Wait for Java Language Support extension being on Standard mode,
|
||||
* and return true if the final status is on Standard mode.
|
||||
*/
|
||||
export async function waitForStandardMode(): Promise<boolean> {
|
||||
const api = await getJavaExtensionAPI();
|
||||
export async function waitForStandardMode(progressReporter: IProgressReporter): Promise<boolean> {
|
||||
if (await isImportingProjects()) {
|
||||
progressReporter.report("Importing projects...");
|
||||
}
|
||||
|
||||
const api = await getJavaExtensionAPI(progressReporter);
|
||||
if (!api) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (api && api.serverMode === ServerMode.LIGHTWEIGHT) {
|
||||
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");
|
||||
if (answer === "Yes") {
|
||||
return vscode.window.withProgress<boolean>({ location: vscode.ProgressLocation.Window }, async (progress) => {
|
||||
if (api.serverMode === ServerMode.STANDARD) {
|
||||
return true;
|
||||
}
|
||||
if (api.serverMode === ServerMode.STANDARD) {
|
||||
return true;
|
||||
}
|
||||
|
||||
progress.report({ message: "Switching to Standard mode..." });
|
||||
return new Promise<boolean>((resolve) => {
|
||||
api.onDidServerModeChange((mode: string) => {
|
||||
if (mode === ServerMode.STANDARD) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.executeCommand("java.server.mode.switch", ServerMode.STANDARD, true);
|
||||
progressReporter?.report("Importing projects...");
|
||||
return new Promise<boolean>((resolve) => {
|
||||
progressReporter.getCancellationToken().onCancellationRequested(() => {
|
||||
resolve(false);
|
||||
});
|
||||
api.onDidServerModeChange((mode: string) => {
|
||||
if (mode === ServerMode.STANDARD) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.executeCommand("java.server.mode.switch", ServerMode.STANDARD, true);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (api && api.serverMode === ServerMode.HYBRID) {
|
||||
progressReporter.report("Importing projects...");
|
||||
return new Promise<boolean>((resolve) => {
|
||||
progressReporter.getCancellationToken().onCancellationRequested(() => {
|
||||
resolve(false);
|
||||
});
|
||||
api.onDidServerModeChange((mode: string) => {
|
||||
if (mode === ServerMode.STANDARD) {
|
||||
resolve(true);
|
||||
|
@ -251,6 +277,10 @@ export async function waitForStandardMode(): Promise<boolean> {
|
|||
}
|
||||
|
||||
export async function searchMainMethods(uri?: vscode.Uri): Promise<IMainClassOption[]> {
|
||||
return resolveMainClass(uri);
|
||||
}
|
||||
|
||||
export async function searchMainMethodsWithProgress(uri?: vscode.Uri): Promise<IMainClassOption[]> {
|
||||
try {
|
||||
return await vscode.window.withProgress<IMainClassOption[]>(
|
||||
{ location: vscode.ProgressLocation.Window },
|
||||
|
@ -263,3 +293,24 @@ export async function searchMainMethods(uri?: vscode.Uri): Promise<IMainClassOpt
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"no-duplicate-variable": true,
|
||||
"max-classes-per-file": false,
|
||||
"no-implicit-dependencies": [
|
||||
false, // Turned off due to: https://github.com/microsoft/vscode/issues/78019
|
||||
false,
|
||||
"dev"
|
||||
],
|
||||
"no-empty": false,
|
||||
|
|
Loading…
Reference in New Issue