import * as compareVersions from "compare-versions" ; import * as _ from "lodash" ; import * as path from "path" ; 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 { HCR_EVENT , JAVA_LANGID , USER_NOTIFICATION_EVENT } from "./constants" ; import { NotificationBar } from "./customWidget" ; import { initializeCodeLensProvider , startDebugging } from "./debugCodeLensProvider" ; import { initExpService } from "./experimentationService" ; import { handleHotCodeReplaceCustomEvent , initializeHotCodeReplace , NO_BUTTON , YES_BUTTON } from "./hotCodeReplace" ; import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory" ; import { JavaInlineValuesProvider } from "./JavaInlineValueProvider" ; import { logJavaException , logJavaInfo } from "./javaLogger" ; import { IMainClassOption , IMainMethod , resolveMainMethod } from "./languageServerPlugin" ; import { mainClassPicker } from "./mainClassPicker" ; import { pickJavaProcess } from "./processPicker" ; import { IProgressReporter } from "./progressAPI" ; import { progressProvider } from "./progressImpl" ; import { JavaTerminalLinkProvder } from "./terminalLinkProvider" ; import { initializeThreadOperations } from "./threadOperations" ; import * as utility from "./utility" ; import { registerVariableMenuCommands } from "./variableMenu" ; export async function activate ( context ) { await initializeFromJsonFile ( context . asAbsolutePath ( "./package.json" ) , { firstParty : true , } ) ; await initExpService ( context ) ; return instrumentOperation ( "activation" , initializeExtension ) ( context ) ; } function initializeExtension ( _operationId , context ) { registerDebugEventListener ( context ) ; registerVariableMenuCommands ( context ) ; context . subscriptions . push ( vscode . window . registerTerminalLinkProvider ( new JavaTerminalLinkProvder ( ) ) ) ; context . subscriptions . push ( vscode . debug . registerDebugConfigurationProvider ( "java" , new JavaDebugConfigurationProvider ( ) ) ) ; context . subscriptions . push ( vscode . debug . registerDebugAdapterDescriptorFactory ( "java" , new JavaDebugAdapterDescriptorFactory ( ) ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "JavaDebug.SpecifyProgramArgs" , async ( ) => { return specifyProgramArguments ( context ) ; } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "JavaDebug.PickJavaProcess" , async ( ) => { let javaProcess ; try { javaProcess = await pickJavaProcess ( ) ; } catch ( error ) { vscode . window . showErrorMessage ( error . message ? error . message : String ( error ) ) ; } return javaProcess ? String ( javaProcess . pid ) : "${command:PickJavaProcess}" ; } ) ) ; const hcrStatusBar = new NotificationBar ( "java.hcrStatusBar" , "Java HotCodeReplace" ) ; context . subscriptions . push ( hcrStatusBar ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "java.debug.hotCodeReplace" , async ( ) => { await applyHCR ( hcrStatusBar ) ; } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "java.debug.runJavaFile" , async ( uri ) => { await runJavaFile ( uri , true ) ; } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "java.debug.debugJavaFile" , async ( uri ) => { await runJavaFile ( uri , false ) ; } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "java.debug.runFromProjectView" , async ( node ) => { await runJavaProject ( node , true ) ; } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "java.debug.debugFromProjectView" , async ( node ) => { await runJavaProject ( node , false ) ; } ) ) ; initializeHotCodeReplace ( context ) ; initializeCodeLensProvider ( context ) ; initializeThreadOperations ( context ) ; context . subscriptions . push ( vscode . languages . registerInlineValuesProvider ( "java" , new JavaInlineValuesProvider ( ) ) ) ; return { progressProvider , } ; } export async function deactivate ( ) { await disposeTelemetryWrapper ( ) ; } function registerDebugEventListener ( context ) { 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 = { } ; const measureProperties = { } ; for ( const key of Object . keys ( entry ) ) { if ( measureKeys . indexOf ( key ) >= 0 ) { measureProperties [ key ] = entry [ key ] ; } else { commonProperties [ key ] = String ( entry [ key ] ) ; } } if ( entry . scope === "exception" ) { logJavaException ( commonProperties ) ; } else { logJavaInfo ( 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 ) { const javaDebugProgramArgsKey = "JavaDebugProgramArgs" ; const options = { 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 ) => { if ( text !== undefined ) { context . workspaceState . update ( javaDebugProgramArgsKey , text ) ; } return text || " " ; } ) ; } async function applyHCR ( hcrStatusBar ) { const debugSession = vscode . debug . activeDebugSession ; if ( ! debugSession ) { return ; } if ( debugSession . configuration . noDebug ) { vscode . window . showWarningMessage ( "Failed to apply the changes because hot code replace is not supported by run mode, " + "would you like to restart the program?" , YES_BUTTON , NO_BUTTON ) . then ( ( res ) => { if ( res === YES_BUTTON ) { vscode . commands . executeCommand ( "workbench.action.debug.restart" ) ; } } ) ; return ; } const autobuildConfig = 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 ) ; try { await commands . executeJavaExtensionCommand ( commands . JAVA_BUILD_WORKSPACE , false ) ; } catch ( err ) { } } } hcrStatusBar . show ( "$(sync~spin)Applying code changes..." ) ; const start = new Date ( ) . getTime ( ) ; const response = await debugSession . customRequest ( "redefineClasses" ) ; const elapsed = new Date ( ) . getTime ( ) - start ; const humanVisibleDelay = elapsed < 150 ? 150 : 0 ; if ( humanVisibleDelay ) { await new Promise ( ( resolve ) => { setTimeout ( resolve , humanVisibleDelay ) ; } ) ; } if ( response && response . errorMessage ) { hcrStatusBar . clear ( ) ; return ; } if ( ! response || ! response . changedClasses || ! response . changedClasses . length ) { hcrStatusBar . clear ( ) ; vscode . window . showWarningMessage ( "Cannot find any changed classes for hot replace!" ) ; return ; } const changed = response . changedClasses . length ; hcrStatusBar . show ( "$(check)" + `${ changed } changed class${ changed > 1 ? "es are" : " is" } reloaded` , 5 * 1000 ) ; } async function runJavaFile ( uri , noDebug ) { const progressReporter = progressProvider . createProgressReporter ( noDebug ? "Run" : "Debug" ) ; try { const isOnStandardMode = await utility . waitForStandardMode ( progressReporter ) ; if ( ! isOnStandardMode ) { throw new utility . OperationCancelledError ( "" ) ; } const activeEditor = vscode . window . activeTextEditor ; if ( ! uri && activeEditor && _ . endsWith ( path . basename ( activeEditor . document . fileName ) , ".java" ) ) { uri = activeEditor . document . uri ; } if ( ! uri ) { vscode . window . showErrorMessage ( `${ noDebug ? "Run" : "Debug" } failed. Please open a Java file with main method first.` ) ; throw new utility . OperationCancelledError ( "" ) ; } const mainMethods = await resolveMainMethod ( uri ) ; const hasMainMethods = mainMethods . length > 0 ; const canRunTests = await canDelegateToJavaTestRunner ( uri ) ; const defaultPlaceHolder = "Select the main class to run" ; if ( ! hasMainMethods && ! canRunTests ) { progressReporter . report ( "Resolving main class..." ) ; const mainClasses = await utility . searchMainMethods ( ) ; if ( progressReporter . isCancelled ( ) ) { throw new utility . OperationCancelledError ( "" ) ; } const placeHolder = `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 ) ; } else if ( hasMainMethods && ! canRunTests ) { await launchMain ( mainMethods , uri , noDebug , progressReporter , defaultPlaceHolder ) ; } else if ( ! hasMainMethods && canRunTests ) { launchTesting ( uri , noDebug , progressReporter ) ; } else { const launchMainChoice = "main() method" ; const launchTestChoice = "unit tests" ; const choice = await vscode . window . showQuickPick ( [ launchMainChoice , launchTestChoice ] , { placeHolder : "Please select which kind of task you would like to launch" } ) ; if ( choice === launchMainChoice ) { await launchMain ( mainMethods , uri , noDebug , progressReporter , defaultPlaceHolder ) ; } else if ( choice === launchTestChoice ) { launchTesting ( uri , noDebug , progressReporter ) ; } } } catch ( ex ) { progressReporter . done ( ) ; if ( ex instanceof utility . OperationCancelledError ) { return ; } if ( ex instanceof utility . JavaExtensionNotEnabledError ) { utility . guideToInstallJavaExtension ( ) ; return ; } vscode . window . showErrorMessage ( String ( ( ex && ex . message ) || ex ) ) ; } } async function canDelegateToJavaTestRunner ( uri ) { const fsPath = uri . fsPath ; const isTestFile = /.*[\/\\]src[\/\\]test[\/\\]java[\/\\].*[Tt]ests?\.java/ . test ( fsPath ) ; if ( ! isTestFile ) { return false ; } return ( await vscode . commands . getCommands ( ) ) . includes ( "java.test.editor.run" ) ; } function launchTesting ( uri , noDebug , progressReporter ) { const command = noDebug ? "java.test.editor.run" : "java.test.editor.debug" ; vscode . commands . executeCommand ( command , uri , progressReporter ) ; if ( compareVersions ( getTestExtensionVersion ( ) , "0.26.1" ) <= 0 ) { throw new utility . OperationCancelledError ( "" ) ; } } function getTestExtensionVersion ( ) { const extension = vscode . extensions . getExtension ( "vscjava.vscode-java-test" ) ; return extension ?. packageJSON . version || "0.0.0" ; } async function launchMain ( mainMethods , uri , noDebug , progressReporter , placeHolder , autoPick = true ) { 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)" ) ; throw new utility . OperationCancelledError ( "" ) ; } if ( ! mainClassPicker . isAutoPicked ( mainMethods , autoPick ) ) { progressReporter . hide ( true ) ; } const pick = await mainClassPicker . showQuickPickWithRecentlyUsed ( mainMethods , placeHolder , autoPick ) ; if ( ! pick ) { throw new utility . OperationCancelledError ( "" ) ; } progressReporter . report ( "Launching main class..." ) ; startDebugging ( pick . mainClass , pick . projectName || "" , uri , noDebug , progressReporter ) ; } async function runJavaProject ( node , noDebug ) { if ( ! node || ! node . name || ! node . uri ) { vscode . window . showErrorMessage ( `Failed to ${ noDebug ? "run" : "debug" } the project because of invalid project node. ` + "This command only applies to Project Explorer view." ) ; const error = new Error ( `Failed to ${ noDebug ? "run" : "debug" } the project because of invalid project node.` ) ; setUserError ( error ) ; throw error ; } const progressReporter = progressProvider . createProgressReporter ( noDebug ? "Run" : "Debug" ) ; try { progressReporter . report ( "Resolving main class..." ) ; const mainClassesOptions = await utility . searchMainMethods ( vscode . Uri . parse ( node . uri ) ) ; if ( progressReporter . isCancelled ( ) ) { throw new utility . OperationCancelledError ( "" ) ; } if ( ! mainClassesOptions || ! mainClassesOptions . length ) { vscode . window . showErrorMessage ( `Failed to ${ noDebug ? "run" : "debug" } this project '${ node . _nodeData . displayName || node . name }' ` + "because it does not contain any main class." ) ; throw new utility . OperationCancelledError ( "" ) ; } if ( ! mainClassPicker . isAutoPicked ( mainClassesOptions ) ) { progressReporter . hide ( true ) ; } const pick = await mainClassPicker . showQuickPickWithRecentlyUsed ( mainClassesOptions , "Select the main class to run." ) ; if ( ! pick || progressReporter . isCancelled ( ) ) { throw new utility . OperationCancelledError ( "" ) ; } progressReporter . report ( "Launching main class..." ) ; const projectName = pick . projectName ; const mainClass = pick . mainClass ; const filePath = pick . filePath ; const workspaceFolder = filePath ? vscode . workspace . getWorkspaceFolder ( vscode . Uri . file ( filePath ) ) : undefined ; const launchConfigurations = vscode . workspace . getConfiguration ( "launch" , workspaceFolder ) ; const existingConfigs = launchConfigurations . configurations ; const existConfig = _ . find ( existingConfigs , ( config ) => { return config . mainClass === mainClass && _ . toString ( config . projectName ) === _ . toString ( projectName ) ; } ) ; const debugConfig = existConfig || { type : "java" , name : `Launch ${ mainClass . substr ( mainClass . lastIndexOf ( "." ) + 1 ) }` , request : "launch" , mainClass , projectName , } ; debugConfig . noDebug = noDebug ; debugConfig . __progressId = progressReporter . getId ( ) ; vscode . debug . startDebugging ( workspaceFolder , debugConfig ) ; } catch ( ex ) { progressReporter . done ( ) ; if ( ex instanceof utility . OperationCancelledError ) { return ; } throw ex ; } }