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
44 changes: 43 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand All @@ -68,6 +102,14 @@
{
"command": "java.debug.hotCodeReplace",
"when": "false"
},
{
"command": "java.debug.runJavaFile",
"when": "false"
},
{
"command": "java.debug.debugJavaFile",
"when": "false"
}
]
},
Expand Down
84 changes: 42 additions & 42 deletions src/debugCodeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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<boolean>(ENABLE_CODE_LENS_VARIABLE);
Expand Down Expand Up @@ -62,48 +62,37 @@ class DebugCodeLensContainer implements vscode.Disposable {
class DebugCodeLensProvider implements vscode.CodeLensProvider {

public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
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<void> {
return runCodeLens(mainClass, projectName, uri, true);
function runJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise<boolean> {
return startDebugging(mainClass, projectName, uri, true);
}

function debugJavaProgram(mainClass: string, projectName: string, uri: vscode.Uri): Promise<void> {
return runCodeLens(mainClass, projectName, uri, false);
}

async function runCodeLens(mainClass: string, projectName: string, uri: vscode.Uri, noDebug: boolean): Promise<void> {
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<boolean> {
return startDebugging(mainClass, projectName, uri, false);
}

async function constructDebugConfig(mainClass: string, projectName: string, workspace: vscode.Uri): Promise<vscode.DebugConfiguration> {
Expand Down Expand Up @@ -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<boolean> {
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);
}
143 changes: 93 additions & 50 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
}
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -144,7 +125,69 @@ function specifyProgramArguments(context: vscode.ExtensionContext): Thenable<str
});
}

function instrumentAndRegisterCommand(name: string, cb: (...args: any[]) => 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);
}
7 changes: 1 addition & 6 deletions src/languageServerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ export interface ILaunchValidationResponse {
}

export async function resolveMainMethod(uri: vscode.Uri): Promise<IMainMethod[]> {
try {
return <IMainMethod[]> await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINMETHOD, uri.toString());
} catch (ex) {
logger.log(Type.EXCEPTION, utility.formatErrorProperties(ex));
return [];
}
return <IMainMethod[]> await commands.executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_MAINMETHOD, uri.toString());
}

export function startDebugSession() {
Expand Down
Loading