diff --git a/package.json b/package.json index 6ab1c78..d4320d4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "onLanguage:java", "onDebugInitialConfigurations", "onDebugResolve:java", - "onCommand:JavaDebug.SpecifyProgramArgs" + "onCommand:JavaDebug.SpecifyProgramArgs", + "onCommand:java.debug.runJavaFile", + "onCommand:java.debug.debugJavaFile" ], "main": "./dist/extension", "contributes": { @@ -54,9 +56,41 @@ "dark": "images/commands/hot_code_replace.svg", "light": "images/commands/hot_code_replace.svg" } + }, + { + "command": "java.debug.runJavaFile", + "title": "Run" + }, + { + "command": "java.debug.debugJavaFile", + "title": "Debug" } ], "menus": { + "explorer/context": [ + { + "command": "java.debug.runJavaFile", + "when": "resourceExtname == .java", + "group": "javadebug@1" + }, + { + "command": "java.debug.debugJavaFile", + "when": "resourceExtname == .java", + "group": "javadebug@2" + } + ], + "editor/context": [ + { + "command": "java.debug.runJavaFile", + "when": "editorLangId == java && resourceExtname == .java", + "group": "javadebug@1" + }, + { + "command": "java.debug.debugJavaFile", + "when": "editorLangId == java && resourceExtname == .java", + "group": "javadebug@2" + } + ], "debug/toolBar": [ { "command": "java.debug.hotCodeReplace", @@ -68,6 +102,14 @@ { "command": "java.debug.hotCodeReplace", "when": "false" + }, + { + "command": "java.debug.runJavaFile", + "when": "false" + }, + { + "command": "java.debug.debugJavaFile", + "when": "false" } ] }, diff --git a/src/debugCodeLensProvider.ts b/src/debugCodeLensProvider.ts index 0f8a92e..948b6e3 100644 --- a/src/debugCodeLensProvider.ts +++ b/src/debugCodeLensProvider.ts @@ -3,13 +3,13 @@ import * as _ from "lodash"; import * as vscode from "vscode"; +import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper"; import { JAVA_LANGID } from "./constants"; import { IMainMethod, resolveMainMethod } from "./languageServerPlugin"; -import { logger, Type } from "./logger"; -const JAVA_RUN_COMMAND = "vscode.java.run"; -const JAVA_DEBUG_COMMAND = "vscode.java.debug"; +const JAVA_RUN_CODELENS_COMMAND = "java.debug.runCodeLens"; +const JAVA_DEBUG_CODELENS_COMMAND = "java.debug.debugCodeLens"; const JAVA_DEBUG_CONFIGURATION = "java.debug.settings"; const ENABLE_CODE_LENS_VARIABLE = "enableRunDebugCodeLens"; @@ -24,8 +24,8 @@ class DebugCodeLensContainer implements vscode.Disposable { private configurationEvent: vscode.Disposable; constructor() { - this.runCommand = vscode.commands.registerCommand(JAVA_RUN_COMMAND, runJavaProgram); - this.debugCommand = vscode.commands.registerCommand(JAVA_DEBUG_COMMAND, debugJavaProgram); + this.runCommand = instrumentOperationAsVsCodeCommand(JAVA_RUN_CODELENS_COMMAND, runJavaProgram); + this.debugCommand = instrumentOperationAsVsCodeCommand(JAVA_DEBUG_CODELENS_COMMAND, debugJavaProgram); const configuration = vscode.workspace.getConfiguration(JAVA_DEBUG_CONFIGURATION) const isCodeLensEnabled = configuration.get(ENABLE_CODE_LENS_VARIABLE); @@ -62,48 +62,37 @@ class DebugCodeLensContainer implements vscode.Disposable { class DebugCodeLensProvider implements vscode.CodeLensProvider { public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { - const mainMethods: IMainMethod[] = await resolveMainMethod(document.uri); - return _.flatten(mainMethods.map((method) => { - return [ - new vscode.CodeLens(method.range, { - title: "Run", - command: JAVA_RUN_COMMAND, - tooltip: "Run Java Program", - arguments: [ method.mainClass, method.projectName, document.uri ], - }), - new vscode.CodeLens(method.range, { - title: "Debug", - command: JAVA_DEBUG_COMMAND, - tooltip: "Debug Java Program", - arguments: [ method.mainClass, method.projectName, document.uri ], - }), - ]; - })); + try { + const mainMethods: IMainMethod[] = await resolveMainMethod(document.uri); + return _.flatten(mainMethods.map((method) => { + return [ + new vscode.CodeLens(method.range, { + title: "Run", + command: JAVA_RUN_CODELENS_COMMAND, + tooltip: "Run Java Program", + arguments: [ method.mainClass, method.projectName, document.uri ], + }), + new vscode.CodeLens(method.range, { + title: "Debug", + command: JAVA_DEBUG_CODELENS_COMMAND, + tooltip: "Debug Java Program", + arguments: [ method.mainClass, method.projectName, document.uri ], + }), + ]; + })); + } catch (ex) { + // do nothing. + return []; + } } } -function runJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise { - return runCodeLens(mainClass, projectName, uri, true); +function runJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise { + return startDebugging(mainClass, projectName, uri, true); } -function debugJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise { - return runCodeLens(mainClass, projectName, uri, false); -} - -async function runCodeLens(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean): Promise { - const workspaceFolder: vscode.WorkspaceFolder = vscode.workspace.getWorkspaceFolder(uri); - const workspaceUri: vscode.Uri = workspaceFolder ? workspaceFolder.uri : undefined; - - const debugConfig: vscode.DebugConfiguration = await constructDebugConfig(mainClass, projectName, workspaceUri); - debugConfig.projectName = projectName; - debugConfig.noDebug = noDebug; - - vscode.debug.startDebugging(workspaceFolder, debugConfig); - - logger.log(Type.USAGEDATA, { - runCodeLens: "yes", - noDebug: String(noDebug), - }); +function debugJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise { + return startDebugging(mainClass, projectName, uri, false); } async function constructDebugConfig(mainClass: string, projectName: string, workspace: vscode.Uri): Promise { @@ -139,3 +128,14 @@ async function constructDebugConfig(mainClass: string, projectName: string, work return _.cloneDeep(debugConfig); } + +export async function startDebugging(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean): Promise { + const workspaceFolder: vscode.WorkspaceFolder = vscode.workspace.getWorkspaceFolder(uri); + const workspaceUri: vscode.Uri = workspaceFolder ? workspaceFolder.uri : undefined; + + const debugConfig: vscode.DebugConfiguration = await constructDebugConfig(mainClass, projectName, workspaceUri); + debugConfig.projectName = projectName; + debugConfig.noDebug = noDebug; + + return vscode.debug.startDebugging(workspaceFolder, debugConfig); +} diff --git a/src/extension.ts b/src/extension.ts index 97ac7a3..10dfda4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,12 +2,14 @@ // Licensed under the MIT license. import * as vscode from "vscode"; -import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation } from "vscode-extension-telemetry-wrapper"; +import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation, + instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper"; import * as commands from "./commands"; import { JavaDebugConfigurationProvider } from "./configurationProvider"; import { HCR_EVENT, JAVA_LANGID, USER_NOTIFICATION_EVENT } from "./constants"; -import { initializeCodeLensProvider } from "./debugCodeLensProvider" +import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvider" import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace } from "./hotCodeReplace"; +import { IMainMethod, resolveMainMethod } from "./languageServerPlugin"; import { logger, Type } from "./logger"; import * as utility from "./utility"; @@ -23,8 +25,31 @@ function initializeExtension(operationId: string, context: vscode.ExtensionConte description: "activateExtension", }); + registerDebugEventListener(context); + context.subscriptions.push(logger); + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("java", new JavaDebugConfigurationProvider())); + context.subscriptions.push(instrumentOperationAsVsCodeCommand("JavaDebug.SpecifyProgramArgs", async () => { + return specifyProgramArguments(context); + })); + context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.hotCodeReplace", applyHCR)); + context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.runJavaFile", async (uri: vscode.Uri) => { + await runJavaFile(uri, true); + })); + context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.debugJavaFile", async (uri: vscode.Uri) => { + await runJavaFile(uri, false); + })); + initializeHotCodeReplace(context); + initializeCodeLensProvider(context); +} + +// this method is called when your extension is deactivated +export async function deactivate() { + await disposeTelemetryWrapper(); +} + +function registerDebugEventListener(context: vscode.ExtensionContext) { const measureKeys = ["duration"]; - vscode.debug.onDidTerminateDebugSession((e) => { + context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((e) => { if (e.type !== "java") { return; } @@ -44,45 +69,8 @@ function initializeExtension(operationId: string, context: vscode.ExtensionConte }); } }); - }); - - context.subscriptions.push(logger); - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("java", new JavaDebugConfigurationProvider())); - context.subscriptions.push(instrumentAndRegisterCommand("JavaDebug.SpecifyProgramArgs", async () => { - return specifyProgramArguments(context); })); - context.subscriptions.push(instrumentAndRegisterCommand("java.debug.hotCodeReplace", async (args: any) => { - const autobuildConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.autobuild"); - if (!autobuildConfig.enabled) { - const ans = await vscode.window.showWarningMessage( - "The hot code replace feature requires you to enable the autobuild flag, do you want to enable it?", - "Yes", "No"); - if (ans === "Yes") { - await autobuildConfig.update("enabled", true); - // Force an incremental build to avoid auto build is not finishing during HCR. - try { - await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false) - } catch (err) { - // do nothing. - } - } - } - const debugSession: vscode.DebugSession = vscode.debug.activeDebugSession; - if (!debugSession) { - return; - } - - return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, async (progress) => { - progress.report({ message: "Applying code changes..." }); - - const response = await debugSession.customRequest("redefineClasses"); - if (!response || !response.changedClasses || !response.changedClasses.length) { - vscode.window.showWarningMessage("Cannot find any changed classes for hot replace!"); - } - }); - })); - initializeHotCodeReplace(context); context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((customEvent) => { const t = customEvent.session ? customEvent.session.type : undefined; if (t !== JAVA_LANGID) { @@ -94,13 +82,6 @@ function initializeExtension(operationId: string, context: vscode.ExtensionConte handleUserNotification(customEvent); } })); - - initializeCodeLensProvider(context); -} - -// this method is called when your extension is deactivated -export async function deactivate() { - await disposeTelemetryWrapper(); } function handleUserNotification(customEvent) { @@ -144,7 +125,69 @@ function specifyProgramArguments(context: vscode.ExtensionContext): Thenable any) { - const instrumented = instrumentOperation(name, async (_operationId, myargs) => await cb(myargs)); - return vscode.commands.registerCommand(name, instrumented); +async function applyHCR() { + const autobuildConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.autobuild"); + if (!autobuildConfig.enabled) { + const ans = await vscode.window.showWarningMessage( + "The hot code replace feature requires you to enable the autobuild flag, do you want to enable it?", + "Yes", "No"); + if (ans === "Yes") { + await autobuildConfig.update("enabled", true); + // Force an incremental build to avoid auto build is not finishing during HCR. + try { + await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false) + } catch (err) { + // do nothing. + } + } + } + + const debugSession: vscode.DebugSession = vscode.debug.activeDebugSession; + if (!debugSession) { + return; + } + + return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, async (progress) => { + progress.report({ message: "Applying code changes..." }); + + const response = await debugSession.customRequest("redefineClasses"); + if (!response || !response.changedClasses || !response.changedClasses.length) { + vscode.window.showWarningMessage("Cannot find any changed classes for hot replace!"); + } + }); +} + +async function runJavaFile(uri: vscode.Uri, noDebug: boolean) { + try { + // Wait for Java Language Support extension being activated. + await utility.getJavaExtensionAPI(); + } catch (ex) { + if (ex instanceof utility.JavaExtensionNotActivatedError) { + utility.guideToInstallJavaExtension(); + return; + } + + throw ex; + } + + const mainMethods: IMainMethod[] = await resolveMainMethod(uri); + 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; + } + + const projectName = mainMethods[0].projectName; + let mainClass = mainMethods[0].mainClass; + if (mainMethods.length > 1) { + mainClass = await vscode.window.showQuickPick(mainMethods.map((mainMethod) => mainMethod.mainClass), { + placeHolder: "Select the main class to launch.", + }); + } + + if (!mainClass) { + return; + } + + await startDebugging(mainClass, projectName, uri, noDebug); } diff --git a/src/languageServerPlugin.ts b/src/languageServerPlugin.ts index cdeb870..94de302 100644 --- a/src/languageServerPlugin.ts +++ b/src/languageServerPlugin.ts @@ -29,12 +29,7 @@ export interface ILaunchValidationResponse { } export async function resolveMainMethod(uri: vscode.Uri): Promise { - try { - return await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINMETHOD, uri.toString()); - } catch (ex) { - logger.log(Type.EXCEPTION, utility.formatErrorProperties(ex)); - return []; - } + return await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINMETHOD, uri.toString()); } export function startDebugSession() { diff --git a/src/utility.ts b/src/utility.ts index c7a6e15..e317ca2 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -137,22 +137,24 @@ export function formatErrorProperties(ex: any): IProperties { } export async function getJavaHome(): Promise { - const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID); - if (!extension) { - throw new JavaExtensionNotActivatedError("VS Code Java Extension is not enabled."); - } - try { - const extensionApi = await extension.activate(); - if (extensionApi && extensionApi.javaRequirement) { - return extensionApi.javaRequirement.java_home; - } - } catch (ex) { + const extensionApi = await getJavaExtensionAPI(); + if (extensionApi && extensionApi.javaRequirement) { + return extensionApi.javaRequirement.java_home; } return ""; } -export function isJavaExtEnabled() { +export function getJavaExtensionAPI(): Thenable { + const extension = vscode.extensions.getExtension(JAVA_EXTENSION_ID); + if (!extension) { + throw new JavaExtensionNotActivatedError("VS Code Java Extension is not enabled."); + } + + return extension.activate(); +} + +export function isJavaExtEnabled(): boolean { const javaExt = vscode.extensions.getExtension(JAVA_EXTENSION_ID); return !!javaExt; }