194 lines
7.6 KiB
TypeScript
194 lines
7.6 KiB
TypeScript
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT license.
|
|
|
|
import * as vscode from "vscode";
|
|
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, startDebugging } from "./debugCodeLensProvider"
|
|
import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace } from "./hotCodeReplace";
|
|
import { IMainMethod, resolveMainMethod } from "./languageServerPlugin";
|
|
import { logger, Type } from "./logger";
|
|
import * as utility from "./utility";
|
|
|
|
export async function activate(context: vscode.ExtensionContext) {
|
|
await initializeFromJsonFile(context.asAbsolutePath("./package.json"));
|
|
await instrumentOperation("activation", initializeExtension)(context);
|
|
}
|
|
|
|
function initializeExtension(operationId: string, context: vscode.ExtensionContext) {
|
|
logger.initialize(context);
|
|
logger.log(Type.ACTIVATEEXTENSION, {}); // TODO: Activation belongs to usage data, remove this line.
|
|
logger.log(Type.USAGEDATA, {
|
|
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"];
|
|
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((e) => {
|
|
if (e.type !== "java") {
|
|
return;
|
|
}
|
|
fetchUsageData().then((ret) => {
|
|
if (Array.isArray(ret) && ret.length) {
|
|
ret.forEach((entry) => {
|
|
const commonProperties: any = {};
|
|
const measureProperties: any = {};
|
|
for (const key of Object.keys(entry)) {
|
|
if (measureKeys.indexOf(key) >= 0) {
|
|
measureProperties[key] = entry[key];
|
|
} else {
|
|
commonProperties[key] = String(entry[key]);
|
|
}
|
|
}
|
|
logger.log(entry.scope === "exception" ? Type.EXCEPTION : Type.USAGEDATA, commonProperties, measureProperties);
|
|
});
|
|
}
|
|
});
|
|
}));
|
|
|
|
context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((customEvent) => {
|
|
const t = customEvent.session ? customEvent.session.type : undefined;
|
|
if (t !== JAVA_LANGID) {
|
|
return;
|
|
}
|
|
if (customEvent.event === HCR_EVENT) {
|
|
handleHotCodeReplaceCustomEvent(customEvent);
|
|
} else if (customEvent.event === USER_NOTIFICATION_EVENT) {
|
|
handleUserNotification(customEvent);
|
|
}
|
|
}));
|
|
}
|
|
|
|
function handleUserNotification(customEvent) {
|
|
if (customEvent.body.notificationType === "ERROR") {
|
|
utility.showErrorMessageWithTroubleshooting({
|
|
message: customEvent.body.message,
|
|
});
|
|
} else if (customEvent.body.notificationType === "WARNING") {
|
|
utility.showWarningMessageWithTroubleshooting({
|
|
message: customEvent.body.message,
|
|
});
|
|
} else {
|
|
vscode.window.showInformationMessage(customEvent.body.message);
|
|
}
|
|
}
|
|
|
|
function fetchUsageData() {
|
|
return commands.executeJavaLanguageServerCommand(commands.JAVA_FETCH_USAGE_DATA);
|
|
}
|
|
|
|
function specifyProgramArguments(context: vscode.ExtensionContext): Thenable<string> {
|
|
const javaDebugProgramArgsKey = "JavaDebugProgramArgs";
|
|
|
|
const options: vscode.InputBoxOptions = {
|
|
ignoreFocusOut: true,
|
|
placeHolder: "Enter program arguments or leave empty to pass no args",
|
|
};
|
|
|
|
const prevArgs = context.workspaceState.get(javaDebugProgramArgsKey, "");
|
|
if (prevArgs.length > 0) {
|
|
options.value = prevArgs;
|
|
}
|
|
|
|
return vscode.window.showInputBox(options).then((text) => {
|
|
// When user cancels the input box (by pressing Esc), the text value is undefined.
|
|
if (text !== undefined) {
|
|
context.workspaceState.update(javaDebugProgramArgsKey, text);
|
|
}
|
|
|
|
return text || " ";
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|