diff --git a/package.json b/package.json index 04b8424..abf2209 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ ], "activationEvents": [ "onLanguage:java", - "onCommand:vscode.java.startDebugSession" + "onDebug" ], "main": "./out/src/extension", "contributes": { @@ -42,7 +42,6 @@ { "type": "java", "label": "Java", - "startSessionCommand": "vscode.java.startDebugSession", "enableBreakpointsFor": { "languageIds": [ "java" @@ -133,22 +132,6 @@ } } }, - "initialConfigurations": [ - { - "type": "java", - "name": "Debug (Launch)", - "request": "launch", - "mainClass": "", - "args": "" - }, - { - "type": "java", - "name": "Debug (Attach)", - "request": "attach", - "hostName": "localhost", - "port": 0 - } - ], "configurationSnippets": [ { "label": "Java: Launch Program", diff --git a/src/configurationProvider.ts b/src/configurationProvider.ts new file mode 100644 index 0000000..23cedd0 --- /dev/null +++ b/src/configurationProvider.ts @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from "vscode"; +import TelemetryReporter from "vscode-extension-telemetry"; +import * as commands from "./commands"; + +export class JavaDebugConfigurationProvider implements vscode.DebugConfigurationProvider { + constructor(private _reporter: TelemetryReporter) { + } + + // Returns an initial debug configurations based on contextual information. + public provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken): + vscode.ProviderResult { + return [{ + type: "java", + name: "Debug (Launch)", + request: "launch", + mainClass: "", + args: "", + }, { + type: "java", + name: "Debug (Attach)", + request: "attach", + hostName: "localhost", + port: 0, + }]; + } + + // Try to add all missing attributes to the debug configuration being launched. + public resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): + vscode.ProviderResult { + return this.heuristicallyResolveDebugConfiguration(folder, config); + } + + private async heuristicallyResolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration) { + try { + try { + const level = await configLogLevel(vscode.workspace.getConfiguration().get("java.debug.logLevel")); + console.log("setting log level to ", level); + } catch (err) { + // log a warning message and continue, since logger failure should not block debug session + console.log("Cannot set log level to java debuggeer.") + } + if (Object.keys(config).length === 0) { // No launch.json in current workspace. + // VSCode will create a launch.json automatically if no launch.json yet. + return config; + } else if (config.request === "launch") { + if (!config.mainClass) { + vscode.window.showErrorMessage("Please specify the mainClass in the launch.json."); + this.log("usageError", "Please specify the mainClass in the launch.json."); + return undefined; + } else if (!config.classPaths || !Array.isArray(config.classPaths) || !config.classPaths.length) { + config.classPaths = await resolveClasspath(config.mainClass, config.projectName); + } + if (!config.classPaths || !Array.isArray(config.classPaths) || !config.classPaths.length) { + vscode.window.showErrorMessage("Cannot resolve the classpaths automatically, please specify the value in the launch.json."); + this.log("usageError", "Cannot resolve the classpaths automatically, please specify the value in the launch.json."); + return undefined; + } + } else if (config.request === "attach") { + if (!config.hostName || !config.port) { + vscode.window.showErrorMessage("Please specify the host name and the port of the remote debuggee in the launch.json."); + this.log("usageError", "Please specify the host name and the port of the remote debuggee in the launch.json."); + return undefined; + } + } else { + const ans = await vscode.window.showErrorMessage( + // tslint:disable-next-line:max-line-length + "Request type \"" + config.request + "\" is not supported. Only \"launch\" and \"attach\" are supported.", "Open launch.json"); + if (ans === "Open launch.json") { + await vscode.commands.executeCommand(commands.VSCODE_ADD_DEBUGCONFIGURATION); + } + this.log("usageError", "Illegal request type in launch.json"); + return undefined; + } + const debugServerPort = await startDebugSession(); + if (debugServerPort) { + config.debugServer = debugServerPort; + return config; + } else { + this.log("exception", "Failed to start debug server."); + // Information for diagnostic: + console.log("Cannot find a port for debugging session"); + return undefined; + } + } catch (ex) { + const errorMessage = (ex && ex.message) || ex; + vscode.window.showErrorMessage(String(errorMessage)); + if (this._reporter) { + const exception = (ex && ex.data && ex.data.cause) + || { stackTrace: [], detailMessage: String((ex && ex.message) || ex || "Unknown exception") }; + const properties = { + message: "", + stackTrace: "", + }; + if (exception && typeof exception === "object") { + properties.message = exception.detailMessage; + properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace)) + || String(exception.stackTrace); + } else { + properties.message = String(exception); + } + this._reporter.sendTelemetryEvent("exception", properties); + } + return undefined; + } + } + + private log(type: string, message: string) { + if (this._reporter) { + this._reporter.sendTelemetryEvent(type, { message }); + } + } +} + +export function executeJavaLanguageServerCommand(...rest) { + // TODO: need to handle error and trace telemetry + return vscode.commands.executeCommand(commands.JAVA_EXECUTE_WORKSPACE_COMMAND, ...rest); +} + +function startDebugSession() { + return executeJavaLanguageServerCommand(commands.JAVA_START_DEBUGSESSION); +} + +function resolveClasspath(mainClass, projectName) { + return executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_CLASSPATH, mainClass, projectName); +} + +function configLogLevel(level) { + return executeJavaLanguageServerCommand(commands.JAVA_CONFIG_LOG_LEVEL, convertLogLevel(level)); +} + +function convertLogLevel(commonLogLevel: string) { + // convert common log level to java log level + switch (commonLogLevel.toLowerCase()) { + case "verbose" : + return "FINE"; + case "warn" : + return "WARNING"; + case "error" : + return "SEVERE"; + case "info" : + return "INFO"; + default: + return "FINE"; + } +} diff --git a/src/extension.ts b/src/extension.ts index 606e32b..fcc63f6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,91 +5,12 @@ import * as path from "path"; import * as vscode from "vscode"; import TelemetryReporter from "vscode-extension-telemetry"; import * as commands from "./commands"; - -const status: any = {}; +import { executeJavaLanguageServerCommand, JavaDebugConfigurationProvider } from "./configurationProvider"; export function activate(context: vscode.ExtensionContext) { // The reporter will be initialized by the later telemetry handler. let reporter: TelemetryReporter = null; - vscode.commands.registerCommand(commands.JAVA_START_DEBUGSESSION, async (config) => { - - if (!status.debugging) { - status.debugging = "startDebugSession"; - - try { - try { - const level = await configLogLevel(vscode.workspace.getConfiguration().get("java.debug.logLevel")); - console.log("setting log level to ", level); - } catch (err) { - // log a warning message and continue, since logger failure should not block debug session - console.log("Cannot set log level to java debuggeer.") - } - if (Object.keys(config).length === 0) { // No launch.json in current workspace. - const ans = await vscode.window.showInformationMessage( - "\"launch.json\" is needed to start the debugger. Do you want to create it now?", "Yes", "No"); - if (ans === "Yes") { - vscode.commands.executeCommand(commands.VSCODE_ADD_DEBUGCONFIGURATION); - } - return; - } else if (config.request === "launch") { - if (!config.mainClass) { - vscode.window.showErrorMessage("Please specify the mainClass in the launch.json."); - return; - } else if (!config.classPaths || !Array.isArray(config.classPaths) || !config.classPaths.length) { - config.classPaths = await resolveClasspath(config.mainClass, config.projectName); - } - if (!config.classPaths || !Array.isArray(config.classPaths) || !config.classPaths.length) { - vscode.window.showErrorMessage("Cannot resolve the classpaths automatically, please specify the value in the launch.json."); - return; - } - } else if (config.request === "attach") { - if (!config.hostName || !config.port) { - vscode.window.showErrorMessage("Please specify the host name and the port of the remote debuggee in the launch.json."); - return; - } - } else { - const ans = await vscode.window.showErrorMessage( - // tslint:disable-next-line:max-line-length - "Request type \"" + config.request + "\" is not supported. Only \"launch\" and \"attach\" are supported.", "Open launch.json"); - if (ans === "Open launch.json") { - await vscode.commands.executeCommand(commands.VSCODE_ADD_DEBUGCONFIGURATION); - } - return; - } - const debugServerPort = await startDebugSession(); - if (debugServerPort) { - config.debugServer = debugServerPort; - await vscode.commands.executeCommand(commands.VSCODE_STARTDEBUG, config); - } else { - // Information for diagnostic: - console.log("Cannot find a port for debugging session"); - } - } catch (ex) { - const errorMessage = (ex && ex.message) || ex; - vscode.window.showErrorMessage(errorMessage); - if (reporter) { - const exception = (ex && ex.data && ex.data.cause) - || { stackTrace: [], detailMessage: String((ex && ex.message) || ex || "Unknown exception") }; - const properties = { - message: "", - stackTrace: "", - }; - if (exception && typeof exception === "object") { - properties.message = exception.detailMessage; - properties.stackTrace = (Array.isArray(exception.stackTrace) && JSON.stringify(exception.stackTrace)) - || String(exception.stackTrace); - } else { - properties.message = String(exception); - } - reporter.sendTelemetryEvent("exception", properties); - } - } finally { - delete status.debugging; - } - } - }); - // Telemetry. const extensionPackage = require(context.asAbsolutePath("./package.json")); if (extensionPackage) { @@ -122,45 +43,14 @@ export function activate(context: vscode.ExtensionContext) { }); } } + + context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("java", new JavaDebugConfigurationProvider(reporter))); } // this method is called when your extension is deactivated export function deactivate() { } -function startDebugSession() { - return executeJavaLanguageServerCommand(commands.JAVA_START_DEBUGSESSION); -} - -function resolveClasspath(mainClass, projectName) { - return executeJavaLanguageServerCommand(commands.JAVA_RESOLVE_CLASSPATH, mainClass, projectName); -} - function fetchUsageData() { return executeJavaLanguageServerCommand(commands.JAVA_FETCH_USAGE_DATA); } - -function executeJavaLanguageServerCommand(...rest) { - // TODO: need to handle error and trace telemetry - return vscode.commands.executeCommand(commands.JAVA_EXECUTE_WORKSPACE_COMMAND, ...rest); -} - -function configLogLevel(level) { - return executeJavaLanguageServerCommand(commands.JAVA_CONFIG_LOG_LEVEL, convertLogLevel(level)); -} - -function convertLogLevel(commonLogLevel: string) { - // convert common log level to java log level - switch (commonLogLevel.toLowerCase()) { - case "verbose" : - return "FINE"; - case "warn" : - return "WARNING"; - case "error" : - return "SEVERE"; - case "info" : - return "INFO"; - default: - return "FINE"; - } -}