diff --git a/src/configurationProvider.ts b/src/configurationProvider.ts index bd5a81f3..b7846033 100644 --- a/src/configurationProvider.ts +++ b/src/configurationProvider.ts @@ -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() { @@ -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; @@ -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; @@ -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({ @@ -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; @@ -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(); } } @@ -521,6 +549,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration progressReporter: IProgressReporter): Promise { // 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()) { @@ -541,6 +570,7 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration private async resolveMainClass(config: vscode.DebugConfiguration, progressReporter: IProgressReporter): Promise { 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()) { @@ -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."; diff --git a/src/debugCodeLensProvider.ts b/src/debugCodeLensProvider.ts index 18fa245b..110d2946 100644 --- a/src/debugCodeLensProvider.ts +++ b/src/debugCodeLensProvider.ts @@ -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); } diff --git a/src/extension.ts b/src/extension.ts index f6d766a0..b7f348a5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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"; @@ -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) { @@ -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); } @@ -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)}`, @@ -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(); @@ -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) => { + return config.mainClass === mainClass && _.toString(config.projectName) === _.toString(projectName); + }); + + return existConfig; +}