import * as _ from "lodash" ; import * as path from "path" ; import * as vscode from "vscode" ; import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper" ; import { JAVA_LANGID } from "./constants" ; import { initializeHoverProvider } from "./hoverProvider" ; import { IMainMethod , isOnClasspath , resolveMainMethod } from "./languageServerPlugin" ; import { IProgressReporter } from "./progressAPI" ; import { getJavaExtensionAPI , isJavaExtEnabled , ServerMode } from "./utility" ; 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" ; export function initializeCodeLensProvider ( context ) { if ( isActivatedByJavaFile ( ) && isJavaExtEnabled ( ) ) { getJavaExtensionAPI ( ) . then ( ( api ) => { if ( api && ( api . serverMode === ServerMode . LIGHTWEIGHT || api . serverMode === ServerMode . HYBRID ) ) { api . onDidServerModeChange ( ( mode ) => { if ( mode === ServerMode . STANDARD ) { context . subscriptions . push ( new DebugCodeLensContainer ( ) ) ; } } ) ; } else { context . subscriptions . push ( new DebugCodeLensContainer ( ) ) ; } } ) ; } else { context . subscriptions . push ( new DebugCodeLensContainer ( ) ) ; } } function isActivatedByJavaFile ( ) { if ( vscode . window . activeTextEditor ) { return vscode . window . activeTextEditor . document && vscode . window . activeTextEditor . document . languageId === "java" ; } return false ; } class DebugCodeLensContainer implements vscode . Disposable { private runCommand : vscode . Disposable ; private debugCommand : vscode . Disposable ; private lensProvider : vscode . Disposable | undefined ; private hoverProvider : vscode . Disposable | undefined ; private configurationEvent : vscode . Disposable ; constructor ( ) { 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 ) ; if ( isCodeLensEnabled ) { this . lensProvider = vscode . languages . registerCodeLensProvider ( JAVA_LANGID , new DebugCodeLensProvider ( ) ) ; } else { this . hoverProvider = initializeHoverProvider ( ) ; } this . configurationEvent = vscode . workspace . onDidChangeConfiguration ( ( event ) => { if ( event . affectsConfiguration ( JAVA_DEBUG_CONFIGURATION ) ) { const newConfiguration = vscode . workspace . getConfiguration ( JAVA_DEBUG_CONFIGURATION ) ; const newEnabled = newConfiguration . get < boolean > ( ENABLE_CODE_LENS_VARIABLE ) ; if ( newEnabled && this . lensProvider === undefined ) { this . lensProvider = vscode . languages . registerCodeLensProvider ( JAVA_LANGID , new DebugCodeLensProvider ( ) ) ; } else if ( ! newEnabled && this . lensProvider !== undefined ) { this . lensProvider . dispose ( ) ; this . lensProvider = undefined ; } if ( newEnabled && this . hoverProvider ) { this . hoverProvider . dispose ( ) ; this . hoverProvider = undefined ; } else if ( ! newEnabled && ! this . hoverProvider ) { this . hoverProvider = initializeHoverProvider ( ) ; } } } , this ) ; } public dispose ( ) { if ( this . lensProvider !== undefined ) { this . lensProvider . dispose ( ) ; } if ( this . hoverProvider ) { this . hoverProvider . dispose ( ) ; } this . runCommand . dispose ( ) ; this . debugCommand . dispose ( ) ; this . configurationEvent . dispose ( ) ; } } class DebugCodeLensProvider implements vscode . CodeLensProvider { public async provideCodeLenses ( document , token ) { try { const mainMethods = await resolveMainMethod ( document . uri , token ) ; 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 ) { return [ ] ; } } } function runJavaProgram ( mainClass , projectName , uri ) { return startDebugging ( mainClass , projectName , uri , true ) ; } function debugJavaProgram ( mainClass , projectName , uri ) { return startDebugging ( mainClass , projectName , uri , false ) ; } async function constructDebugConfig ( mainClass , projectName , workspace ? ) { const launchConfigurations = vscode . workspace . getConfiguration ( "launch" , workspace ) ; const rawConfigs = launchConfigurations . configurations ; let debugConfig = _ . find ( rawConfigs , ( config ) => { return config . mainClass === mainClass && _ . toString ( config . projectName ) === _ . toString ( projectName ) ; } ) ; if ( ! debugConfig ) { debugConfig = _ . find ( rawConfigs , ( config ) => { return config . mainClass === mainClass && ! config . projectName ; } ) ; } if ( ! debugConfig ) { debugConfig = { type : "java" , name : `Launch ${ mainClass . substr ( mainClass . lastIndexOf ( "." ) + 1 ) }` , request : "launch" , mainClass , projectName , } ; if ( ( rawConfigs && rawConfigs . length ) || await launchJsonExists ( workspace ) ) { try { rawConfigs . splice ( 0 , 0 , debugConfig ) ; await launchConfigurations . update ( "configurations" , rawConfigs , vscode . ConfigurationTarget . WorkspaceFolder ) ; } catch ( error ) { } } } return _ . cloneDeep ( debugConfig ) ; } async function launchJsonExists ( workspace ? ) { if ( ! workspace ) { return false ; } const workspaceFolder = vscode . workspace . getWorkspaceFolder ( workspace ) ; const results = await vscode . workspace . findFiles ( ".vscode/launch.json" ) ; return ! ! results . find ( ( launchJson ) => vscode . workspace . getWorkspaceFolder ( launchJson ) === workspaceFolder ) ; } export async function startDebugging ( mainClass , projectName , uri , noDebug , progressReporter ? ) { const workspaceFolder = vscode . workspace . getWorkspaceFolder ( uri ) ; const workspaceUri = workspaceFolder ? workspaceFolder . uri : undefined ; const onClasspath = await isOnClasspath ( uri . toString ( ) ) ; if ( workspaceUri && onClasspath === false && ! ( await addToClasspath ( uri ) ) ) { return false ; } const debugConfig = await constructDebugConfig ( mainClass , projectName , workspaceUri ) ; debugConfig . projectName = projectName ; debugConfig . noDebug = noDebug ; debugConfig . __progressId = progressReporter ?. getId ( ) ; return vscode . debug . startDebugging ( workspaceFolder , debugConfig ) ; } async function addToClasspath ( uri ) { const fileName = path . basename ( uri . fsPath || "" ) ; const parentFsPath = path . dirname ( uri . fsPath || "" ) ; if ( ! parentFsPath ) { return true ; } const parentUri = vscode . Uri . file ( parentFsPath ) ; let parentPath = vscode . workspace . asRelativePath ( parentUri , true ) ; if ( parentPath === parentUri . fsPath ) { parentPath = path . basename ( parentFsPath ) ; } const ans = await vscode . window . showWarningMessage ( `The file ${ fileName } isn't on the classpath, the runtime may throw class not found error. ` + `Do you want to add the parent folder "${ parentPath }" to Java source path?` , "Add to Source Path" , "Skip" ) ; if ( ans === "Skip" ) { return true ; } else if ( ans === "Add to Source Path" ) { vscode . commands . executeCommand ( "java.project.addToSourcePath" , parentUri ) ; } return false ; }