fix issue 187:If only 1 main class detected, proceed and start it dir… (#262)

* fix issue 187:If only 1 main class detected, proceed and start it directly
This commit is contained in:
Andy Xu(devdiv) 2018-03-27 10:54:44 +08:00 committed by GitHub
parent 00da939a67
commit ab595f02b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 280 additions and 264 deletions

View File

@ -1,264 +1,280 @@
// 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 vscode from "vscode"; import * as vscode from "vscode";
import TelemetryReporter from "vscode-extension-telemetry"; import TelemetryReporter from "vscode-extension-telemetry";
import * as commands from "./commands"; import * as commands from "./commands";
export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider { export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private isUserSettingsDirty: boolean = true; private isUserSettingsDirty: boolean = true;
constructor(private _reporter: TelemetryReporter) { constructor(private _reporter: TelemetryReporter) {
vscode.workspace.onDidChangeConfiguration((event) => { vscode.workspace.onDidChangeConfiguration((event) => {
if (vscode.debug.activeDebugSession) { if (vscode.debug.activeDebugSession) {
this.isUserSettingsDirty = false; this.isUserSettingsDirty = false;
return updateDebugSettings(); return updateDebugSettings();
} else { } else {
this.isUserSettingsDirty = true; this.isUserSettingsDirty = true;
} }
}); });
} }
// 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[]> {
return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder); return <Thenable<vscode.DebugConfiguration[]>>this.provideDebugConfigurationsAsync(folder);
} }
// Try to add all missing attributes to the debug configuration being launched. // Try to add all missing attributes to the debug configuration being launched.
public resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): public resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken):
vscode.ProviderResult<vscode.DebugConfiguration> { vscode.ProviderResult<vscode.DebugConfiguration> {
return this.heuristicallyResolveDebugConfiguration(folder, config); return this.heuristicallyResolveDebugConfiguration(folder, config);
} }
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 vscode.window.withProgress({location: vscode.ProgressLocation.Window}, (p) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
p.report({message: "Auto generating configuration..."}); p.report({message: "Auto generating configuration..."});
resolveMainClass(folder ? folder.uri : undefined).then((res: any[]) => { resolveMainClass(folder ? folder.uri : undefined).then((res: IMainClassOption []) => {
let cache; let cache;
cache = {}; cache = {};
const launchConfigs = res.map((item) => { const launchConfigs = res.map((item) => {
return { return {
type: "java", type: "java",
name: this.constructLaunchConfigName(item.mainClass, item.projectName, cache), name: this.constructLaunchConfigName(item.mainClass, item.projectName, cache),
request: "launch", request: "launch",
// tslint:disable-next-line // tslint:disable-next-line
cwd: "${workspaceFolder}", cwd: "${workspaceFolder}",
console: "internalConsole", console: "internalConsole",
stopOnEntry: false, stopOnEntry: false,
mainClass: item.mainClass, mainClass: item.mainClass,
projectName: item.projectName, projectName: item.projectName,
args: "", args: "",
}; };
}); });
resolve([...launchConfigs, { resolve([...launchConfigs, {
type: "java", type: "java",
name: "Debug (Attach)", name: "Debug (Attach)",
request: "attach", request: "attach",
hostName: "localhost", hostName: "localhost",
port: 0, port: 0,
}]); }]);
}, (ex) => { }, (ex) => {
p.report({message: `failed to generate configuration. ${ex}`}); p.report({message: `failed to generate configuration. ${ex}`});
reject(ex); reject(ex);
}); });
}); });
}); });
} }
private constructLaunchConfigName(mainClass: string, projectName: string, cache: {}) { private constructLaunchConfigName(mainClass: string, projectName: string, cache: {}) {
const prefix = "Debug (Launch)-"; const prefix = "Debug (Launch)-";
let name = prefix + mainClass.substr(mainClass.lastIndexOf(".") + 1); let name = prefix + mainClass.substr(mainClass.lastIndexOf(".") + 1);
if (projectName !== undefined) { if (projectName !== undefined) {
name += `<${projectName}>`; name += `<${projectName}>`;
} }
if (cache[name] === undefined) { if (cache[name] === undefined) {
cache[name] = 0; cache[name] = 0;
return name; return name;
} else { } else {
cache[name] += 1; cache[name] += 1;
return `${name}(${cache[name]})`; return `${name}(${cache[name]})`;
} }
} }
private async heuristicallyResolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) { private async heuristicallyResolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) {
try { try {
if (this.isUserSettingsDirty) { if (this.isUserSettingsDirty) {
this.isUserSettingsDirty = false; this.isUserSettingsDirty = false;
await updateDebugSettings(); await updateDebugSettings();
} }
if (Object.keys(config).length === 0) { // No launch.json in current workspace. if (Object.keys(config).length === 0) { // No launch.json in current workspace.
// check whether it is opened as a folder // check whether it is opened as a folder
if (folder !== undefined) { if (folder !== undefined) {
// for opened with folder, return directly. // for opened with folder, return directly.
return config; return config;
} }
// only rebuild for single file case before the build error issue is resolved. // only rebuild for single file case before the build error issue is resolved.
try { try {
const buildResult = await vscode.commands.executeCommand(commands.JAVA_BUILD_WORKSPACE, false); const buildResult = await vscode.commands.executeCommand(commands.JAVA_BUILD_WORKSPACE, false);
console.log(buildResult); console.log(buildResult);
} catch (err) { } catch (err) {
const ans = await vscode.window.showErrorMessage("Build failed, do you want to continue?", "Proceed", "Abort"); const ans = await vscode.window.showErrorMessage("Build failed, do you want to continue?", "Proceed", "Abort");
if (ans !== "Proceed") { if (ans !== "Proceed") {
return undefined; return undefined;
} }
} }
// Generate config in memory for single file // Generate config in memory for single file
config.type = "java"; config.type = "java";
config.name = "Java Debug"; config.name = "Java Debug";
config.request = "launch"; config.request = "launch";
} }
if (config.request === "launch") { if (config.request === "launch") {
if (!config.mainClass) { if (!config.mainClass) {
const res = <any[]>(await resolveMainClass(folder ? folder.uri : undefined)); const userSelection = await chooseMainClass(folder);
if (res.length === 0) { if (!userSelection || !userSelection.mainClass) {
vscode.window.showErrorMessage( // the error is handled inside chooseMainClass
"Cannot resolve main class automatically, please specify the mainClass " + return;
"(e.g. [mymodule/]com.xyz.MainClass) in the launch.json."); }
return; config.mainClass = userSelection.mainClass;
} config.projectName = userSelection.projectName;
const pickItems = res.map((item) => { }
let name = item.mainClass; if (this.isEmptyArray(config.classPaths) && this.isEmptyArray(config.modulePaths)) {
let details = `main class: ${item.mainClass}`; const result = <any[]>(await resolveClasspath(config.mainClass, config.projectName));
if (item.projectName !== undefined) { config.modulePaths = result[0];
name += `<${item.projectName}>`; config.classPaths = result[1];
details += ` | project name: ${item.projectName}`; }
} if (this.isEmptyArray(config.classPaths) && this.isEmptyArray(config.modulePaths)) {
return { const hintMessage = "Cannot resolve the modulepaths/classpaths automatically, please specify the value in the launch.json.";
description: details, vscode.window.showErrorMessage(hintMessage);
label: name, this.log("usageError", hintMessage);
item, return undefined;
}; }
}).sort ((a, b): number => { } else if (config.request === "attach") {
return a.label > b.label ? 1 : -1; if (!config.hostName || !config.port) {
}); vscode.window.showErrorMessage("Please specify the host name and the port of the remote debuggee in the launch.json.");
const selection = await vscode.window.showQuickPick(pickItems, { placeHolder: "Select main class<project name>" }); this.log("usageError", "Please specify the host name and the port of the remote debuggee in the launch.json.");
if (selection) { return undefined;
config.mainClass = selection.item.mainClass; }
config.projectName = selection.item.projectName; } else {
} else { const ans = await vscode.window.showErrorMessage(
vscode.window.showErrorMessage("Please specify the mainClass (e.g. [mymodule/]com.xyz.MainClass) in the launch.json."); // tslint:disable-next-line:max-line-length
this.log("usageError", "Please specify the mainClass (e.g. [mymodule/]com.xyz.MainClass) in the launch.json."); "Request type \"" + config.request + "\" is not supported. Only \"launch\" and \"attach\" are supported.", "Open launch.json");
return undefined; if (ans === "Open launch.json") {
} await vscode.commands.executeCommand(commands.VSCODE_ADD_DEBUGCONFIGURATION);
} }
if (this.isEmptyArray(config.classPaths) && this.isEmptyArray(config.modulePaths)) { this.log("usageError", "Illegal request type in launch.json");
const result = <any[]>(await resolveClasspath(config.mainClass, config.projectName)); return undefined;
config.modulePaths = result[0]; }
config.classPaths = result[1]; const debugServerPort = await startDebugSession();
} if (debugServerPort) {
if (this.isEmptyArray(config.classPaths) && this.isEmptyArray(config.modulePaths)) { config.debugServer = debugServerPort;
const hintMessage = "Cannot resolve the modulepaths/classpaths automatically, please specify the value in the launch.json."; return config;
vscode.window.showErrorMessage(hintMessage); } else {
this.log("usageError", hintMessage); this.log("exception", "Failed to start debug server.");
return undefined; // Information for diagnostic:
} console.log("Cannot find a port for debugging session");
} else if (config.request === "attach") { return undefined;
if (!config.hostName || !config.port) { }
vscode.window.showErrorMessage("Please specify the host name and the port of the remote debuggee in the launch.json."); } catch (ex) {
this.log("usageError", "Please specify the host name and the port of the remote debuggee in the launch.json."); const errorMessage = (ex && ex.message) || ex;
return undefined; vscode.window.showErrorMessage(String(errorMessage));
} if (this._reporter) {
} else { const exception = (ex && ex.data && ex.data.cause)
const ans = await vscode.window.showErrorMessage( || { stackTrace: [], detailMessage: String((ex && ex.message) || ex || "Unknown exception") };
// tslint:disable-next-line:max-line-length const properties = {
"Request type \"" + config.request + "\" is not supported. Only \"launch\" and \"attach\" are supported.", "Open launch.json"); message: "",
if (ans === "Open launch.json") { stackTrace: "",
await vscode.commands.executeCommand(commands.VSCODE_ADD_DEBUGCONFIGURATION); };
} if (exception && typeof exception === "object") {
this.log("usageError", "Illegal request type in launch.json"); properties.message = exception.detailMessage;
return undefined; properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace))
} || String(exception.stackTrace);
const debugServerPort = await startDebugSession(); } else {
if (debugServerPort) { properties.message = String(exception);
config.debugServer = debugServerPort; }
return config; this._reporter.sendTelemetryEvent("exception", properties);
} else { }
this.log("exception", "Failed to start debug server."); return undefined;
// Information for diagnostic: }
console.log("Cannot find a port for debugging session"); }
return undefined;
} private log(type: string, message: string) {
} catch (ex) { if (this._reporter) {
const errorMessage = (ex && ex.message) || ex; this._reporter.sendTelemetryEvent(type, { message });
vscode.window.showErrorMessage(String(errorMessage)); }
if (this._reporter) { }
const exception = (ex && ex.data && ex.data.cause)
|| { stackTrace: [], detailMessage: String((ex && ex.message) || ex || "Unknown exception") }; private isEmptyArray(configItems: any): boolean {
const properties = { return !Array.isArray(configItems) || !configItems.length;
message: "", }
stackTrace: "", }
};
if (exception && typeof exception === "object") { function startDebugSession() {
properties.message = exception.detailMessage; return commands.executeJavaLanguageServerCommand(commands.JAVA_START_DEBUGSESSION);
properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace)) }
|| String(exception.stackTrace);
} else { function resolveClasspath(mainClass, projectName) {
properties.message = String(exception); return commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_CLASSPATH, mainClass, projectName);
} }
this._reporter.sendTelemetryEvent("exception", properties);
} function resolveMainClass(workspaceUri: vscode.Uri): Promise<IMainClassOption[]> {
return undefined; if (workspaceUri) {
} return <Promise<IMainClassOption[]>>commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINCLASS, workspaceUri.toString());
} }
return <Promise<IMainClassOption[]>>commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINCLASS);
private log(type: string, message: string) { }
if (this._reporter) {
this._reporter.sendTelemetryEvent(type, { message }); async function updateDebugSettings() {
} const debugSettingsRoot: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug");
} if (!debugSettingsRoot) {
return;
private isEmptyArray(configItems: any): boolean { }
return !Array.isArray(configItems) || !configItems.length; const logLevel = convertLogLevel(debugSettingsRoot.logLevel || "");
} if (debugSettingsRoot.settings && Object.keys(debugSettingsRoot.settings).length) {
} try {
console.log("settings:", await commands.executeJavaLanguageServerCommand(commands.JAVA_UPDATE_DEBUG_SETTINGS, JSON.stringify(
function startDebugSession() { { ...debugSettingsRoot.settings, logLevel })));
return commands.executeJavaLanguageServerCommand(commands.JAVA_START_DEBUGSESSION); } catch (err) {
} // log a warning message and continue, since update settings failure should not block debug session
console.log("Cannot update debug settings.", err)
function resolveClasspath(mainClass, projectName) { }
return commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_CLASSPATH, mainClass, projectName); }
} }
function resolveMainClass(workspaceUri: vscode.Uri) { function convertLogLevel(commonLogLevel: string) {
if (workspaceUri) { // convert common log level to java log level
return commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINCLASS, workspaceUri.toString()); switch (commonLogLevel.toLowerCase()) {
} case "verbose":
return commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINCLASS); return "FINE";
} case "warn":
return "WARNING";
async function updateDebugSettings() { case "error":
const debugSettingsRoot: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug"); return "SEVERE";
if (!debugSettingsRoot) { case "info":
return; return "INFO";
} default:
const logLevel = convertLogLevel(debugSettingsRoot.logLevel || ""); return "FINE";
if (debugSettingsRoot.settings && Object.keys(debugSettingsRoot.settings).length) { }
try { }
console.log("settings:", await commands.executeJavaLanguageServerCommand(commands.JAVA_UPDATE_DEBUG_SETTINGS, JSON.stringify(
{ ...debugSettingsRoot.settings, logLevel }))); interface IMainClassOption {
} catch (err) { readonly projectName?: string;
// log a warning message and continue, since update settings failure should not block debug session readonly mainClass: string;
console.log("Cannot update debug settings.", err) }
}
} async function chooseMainClass(folder: vscode.WorkspaceFolder | undefined): Promise<IMainClassOption> {
} const res = await resolveMainClass(folder ? folder.uri : undefined);
const pickItems = res.map((item) => {
function convertLogLevel(commonLogLevel: string) { let name = item.mainClass;
// convert common log level to java log level let details = `main class: ${item.mainClass}`;
switch (commonLogLevel.toLowerCase()) { if (item.projectName !== undefined) {
case "verbose": name += `<${item.projectName}>`;
return "FINE"; details += ` | project name: ${item.projectName}`;
case "warn": }
return "WARNING"; return {
case "error": description: details,
return "SEVERE"; label: name,
case "info": item,
return "INFO"; };
default: }).sort ((a, b): number => {
return "FINE"; return a.label > b.label ? 1 : -1;
} });
} if (pickItems.length === 0) {
vscode.window.showErrorMessage(
"Cannot resolve main class automatically, please specify the mainClass " +
"(e.g. [mymodule/]com.xyz.MainClass) in the launch.json.");
return;
}
const selection = pickItems.length > 1 ?
await vscode.window.showQuickPick(pickItems, { placeHolder: "Select main class<project name>" })
: pickItems[0];
if (selection && selection.item) {
return selection.item;
} else {
vscode.window.showErrorMessage("Please specify the mainClass (e.g. [mymodule/]com.xyz.MainClass) in the launch.json.");
this.log("usageError", "Please specify the mainClass (e.g. [mymodule/]com.xyz.MainClass) in the launch.json.");
return undefined;
}
}