Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 66 additions & 24 deletions src/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const platformNameMappings: { [key: string]: string } = {
};
const platformName = platformNameMappings[process.platform];

export let lastUsedLaunchConfig: vscode.DebugConfiguration | undefined;

export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private isUserSettingsDirty: boolean = true;
constructor() {
Expand Down Expand Up @@ -75,6 +77,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
config.type = "java";
config.name = "Java Debug";
config.request = "launch";
config.__origin = "internal";
}

return config;
Expand Down Expand Up @@ -200,6 +203,15 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration

private async resolveAndValidateDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration,
token?: vscode.CancellationToken) {
let configCopy: vscode.DebugConfiguration | undefined;
const isConfigFromInternal = config.__origin === "internal" /** in-memory configuration from debugger */
|| config.__configurationTarget /** configuration from launch.json */;
if (config.request === "launch" && isConfigFromInternal) {
configCopy = _.cloneDeep(config);
delete configCopy.__progressId;
delete configCopy.noDebug;
}

let progressReporter = progressProvider.getProgressReporter(config.__progressId);
if (!progressReporter && config.__progressId) {
return undefined;
Expand Down Expand Up @@ -231,35 +243,26 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
}

if (config.request === "launch") {
this.mergeEnvFile(config);

// If the user doesn't specify 'vmArgs' in launch.json, use the global setting to get the default vmArgs.
if (config.vmArgs === undefined) {
const debugSettings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug.settings");
config.vmArgs = debugSettings.vmArgs;
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;
}
// If the user doesn't specify 'console' in launch.json, use the global setting to get the launch console.
if (!config.console) {
const debugSettings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug.settings");
config.console = debugSettings.console;

config.mainClass = mainClassOption.mainClass;
config.projectName = mainClassOption.projectName;
if (config.__workspaceFolder && config.__workspaceFolder !== folder) {
folder = config.__workspaceFolder;
}
// If the console is integratedTerminal, don't auto switch the focus to DEBUG CONSOLE.
if (config.console === "integratedTerminal" && !config.internalConsoleOptions) {
config.internalConsoleOptions = "neverOpen";
// Update the job name if the main class is changed during the resolving of configuration provider.
if (configCopy && configCopy.mainClass !== config.mainClass) {
config.name = config.mainClass.substr(config.mainClass.lastIndexOf(".") + 1);
progressReporter.setJobName(utility.launchJobName(config.name, config.noDebug));
}


if (progressReporter.isCancelled()) {
return undefined;
}

progressReporter.report("Resolving main class...");
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;
}

if (needsBuildWorkspace()) {
progressReporter.report("Compiling...");
const proceed = await buildWorkspace({
Expand All @@ -272,9 +275,26 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
}
}

if (progressReporter.isCancelled()) {
return undefined;
}

progressReporter.report("Resolving launch configuration...");
config.mainClass = mainClassOption.mainClass;
config.projectName = mainClassOption.projectName;
this.mergeEnvFile(config);
// If the user doesn't specify 'vmArgs' in launch.json, use the global setting to get the default vmArgs.
if (config.vmArgs === undefined) {
const debugSettings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug.settings");
config.vmArgs = debugSettings.vmArgs;
}
// If the user doesn't specify 'console' in launch.json, use the global setting to get the launch console.
if (!config.console) {
const debugSettings: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.debug.settings");
config.console = debugSettings.console;
}
// If the console is integratedTerminal, don't auto switch the focus to DEBUG CONSOLE.
if (config.console === "integratedTerminal" && !config.internalConsoleOptions) {
config.internalConsoleOptions = "neverOpen";
}

if (progressReporter.isCancelled()) {
return undefined;
Expand Down Expand Up @@ -407,6 +427,14 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
utility.showErrorMessageWithTroubleshooting(utility.convertErrorToMessage(ex));
return undefined;
} finally {
if (configCopy && config.mainClass) {
configCopy.name = config.name;
configCopy.mainClass = config.mainClass;
configCopy.projectName = config.projectName;
configCopy.__workspaceFolder = folder;
lastUsedLaunchConfig = configCopy;
}

progressReporter.done();
}
}
Expand Down Expand Up @@ -521,6 +549,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
progressReporter: IProgressReporter): Promise<lsPlugin.IMainClassOption | undefined> {
// Validate it if the mainClass is already set in launch configuration.
if (config.mainClass && !this.isFilePath(config.mainClass)) {
progressReporter.report("Resolving main class...");
const containsExternalClasspaths = !_.isEmpty(config.classPaths) || !_.isEmpty(config.modulePaths);
const validationResponse = await lsPlugin.validateLaunchConfig(config.mainClass, config.projectName, containsExternalClasspaths, folder);
if (progressReporter.isCancelled()) {
Expand All @@ -541,6 +570,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
private async resolveMainClass(config: vscode.DebugConfiguration, progressReporter: IProgressReporter):
Promise<lsPlugin.IMainClassOption | undefined> {
if (config.projectName) {
progressReporter.report("Resolving main class...");
if (this.isFilePath(config.mainClass)) {
const mainEntries = await lsPlugin.resolveMainMethod(vscode.Uri.file(config.mainClass));
if (progressReporter.isCancelled()) {
Expand Down Expand Up @@ -570,6 +600,18 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration
}
}

// If current file is not executable, run previously used launch config.
if (lastUsedLaunchConfig) {
Object.assign(config, lastUsedLaunchConfig);
progressReporter.setJobName(utility.launchJobName(config.name, config.noDebug));
progressReporter.report("Resolving main class...");
return {
mainClass: config.mainClass,
projectName: config.projectName,
};
}

progressReporter.report("Resolving main class...");
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.";
Expand Down
1 change: 1 addition & 0 deletions src/debugCodeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export async function startDebugging(mainClass: string, projectName: string, uri
debugConfig.projectName = projectName;
debugConfig.noDebug = noDebug;
debugConfig.__progressId = progressReporter?.getId();
debugConfig.__origin = "internal";

return vscode.debug.startDebugging(workspaceFolder, debugConfig);
}
Expand Down
56 changes: 40 additions & 16 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as vscode from "vscode";
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation,
instrumentOperationAsVsCodeCommand, setUserError } from "vscode-extension-telemetry-wrapper";
import * as commands from "./commands";
import { JavaDebugConfigurationProvider } from "./configurationProvider";
import { JavaDebugConfigurationProvider, lastUsedLaunchConfig } from "./configurationProvider";
import { HCR_EVENT, JAVA_LANGID, USER_NOTIFICATION_EVENT } from "./constants";
import { NotificationBar } from "./customWidget";
import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvider";
Expand Down Expand Up @@ -251,14 +251,23 @@ async function runJavaFile(uri: vscode.Uri, noDebug: boolean) {
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("");
// If current file is not a main class, "Run Java" will run previously used launch config.
if (lastUsedLaunchConfig) {
progressReporter.setJobName(utility.launchJobName(lastUsedLaunchConfig.name, noDebug));
progressReporter.report("Resolving launch configuration...");
lastUsedLaunchConfig.noDebug = noDebug;
lastUsedLaunchConfig.__progressId = progressReporter.getId();
vscode.debug.startDebugging(lastUsedLaunchConfig.__workspaceFolder, lastUsedLaunchConfig);
} else {
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*/);
}

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) {
Expand Down Expand Up @@ -330,7 +339,13 @@ async function launchMain(mainMethods: IMainClassOption[], uri: vscode.Uri, noDe
throw new utility.OperationCancelledError("");
}

progressReporter.setJobName(utility.launchJobNameByMainClass(pick.mainClass, noDebug));
const existConfig: vscode.DebugConfiguration | undefined = findLaunchConfiguration(
pick.mainClass, pick.projectName, uri.fsPath);
if (existConfig) {
progressReporter.setJobName(utility.launchJobName(existConfig.name, noDebug));
} else {
progressReporter.setJobName(utility.launchJobNameByMainClass(pick.mainClass, noDebug));
}
progressReporter.report("Launching main class...");
startDebugging(pick.mainClass, pick.projectName || "", uri, noDebug, progressReporter);
}
Expand Down Expand Up @@ -367,18 +382,12 @@ async function runJavaProject(node: any, noDebug: boolean) {
throw new utility.OperationCancelledError("");
}

progressReporter.setJobName(utility.launchJobNameByMainClass(pick.mainClass, noDebug));
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 existConfig: vscode.DebugConfiguration | undefined = findLaunchConfiguration(mainClass, projectName, filePath);
const debugConfig = existConfig || {
type: "java",
name: `${mainClass.substr(mainClass.lastIndexOf(".") + 1)}`,
Expand All @@ -388,6 +397,9 @@ async function runJavaProject(node: any, noDebug: boolean) {
};
debugConfig.noDebug = noDebug;
debugConfig.__progressId = progressReporter.getId();
debugConfig.__origin = "internal";
progressReporter.setJobName(utility.launchJobName(debugConfig.name, noDebug));
progressReporter.report("Launching main class...");
vscode.debug.startDebugging(workspaceFolder, debugConfig);
} catch (ex) {
progressReporter.done();
Expand All @@ -398,3 +410,15 @@ async function runJavaProject(node: any, noDebug: boolean) {
throw ex;
}
}

function findLaunchConfiguration(mainClass: string, projectName: string | undefined, filePath?: string): vscode.DebugConfiguration | undefined {
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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user has two debug configurations, whose mainClass and projectName are the same but other options like args, vmArgs are different?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first one will be used. This might be a rare case, since no complaint received yet.

return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName);
});

return existConfig;
}