import { spawn } from 'child_process' ; import { Socket } from 'net' ; import * as os from 'os' ; import * as path from 'path' ; import * as logger from 'vscode-debug-logger' ; import { Breakpoint , BreakpointEvent , DebugSession , Handles , InitializedEvent , OutputEvent , Scope , Source , StackFrame , StoppedEvent , TerminatedEvent , Thread , ThreadEvent } from 'vscode-debugadapter' ; import { DebugProtocol } from 'vscode-debugprotocol' ; import { createProArgs , getProBin , setupEnvironmentVariables } from '../shared/ablPath' ; import { loadConfigFile , OPENEDGE_CONFIG_FILENAME } from '../shared/openEdgeConfigFile' ; import { convertDataToDebuggerMessage , DebugMessage , DebugMessageArray , DebugMessageClassInfo , DebugMessageListing , DebugMessageListingBreapoint , DebugMessageVariables } from './messages' ; import { AblDebugKind , DebugVariable , isPrimitiveType } from './variables' ; import * as minimatch from 'minimatch' ; const DEFAULT_DEBUG_PORT = 3099 ; interface DebuggerState { exited : boolean ; exitStatus : number ; breakPoint : DebugBreakpoint ; breakPointInfo : { } ; } interface DebugBreakpoint { file : string ; procedureName ? : string ; condition ? : string ; line : number ; verified : boolean ; } interface SourceMap { [ key ] : string ; } interface CommonArguments { port ? : number ; trace ? : boolean | 'verbose' ; } interface AttachRequestArguments extends DebugProtocol . AttachRequestArguments , CommonArguments { address ? : string ; localRoot ? : string ; remoteRoot ? : string ; sourceMap ? : SourceMap ; } interface LaunchRequestArguments extends DebugProtocol . LaunchRequestArguments , CommonArguments { program : string ; args ? : string [ ] ; stopOnEntry ? : boolean ; cwd ? : string ; showLog ? : boolean ; } process . on ( 'uncaughtException' , ( err ) => { const errMessage = err && ( err . stack || err . message ) ; logger . error ( `Unhandled error in debug adapter: ${ errMessage }` ) ; throw err ; } ) ; function logArgsToString ( args ) { return args . map ( ( arg ) => { return typeof arg === 'string' ? arg : JSON . stringify ( arg ) ; } ) . join ( ' ' ) ; } function verbose ( ... args ) { logger . verbose ( logArgsToString ( args ) ) ; } function log ( ... args ) { logger . log ( logArgsToString ( args ) ) ; } function logError ( ... args ) { logger . error ( logArgsToString ( args ) ) ; } function normalizePath ( filePath ) { return path . posix . normalize ( filePath . replace ( /\\/g , '/' ) ) ; } export class AblDebugger { public sendSocket : Socket ; public recvSocket : Socket ; public connection : Promise < void > ; public msgQueue : string [ ] ; public onstdout : ( str : string ) => void ; public onstderr : ( str : string ) => void ; public onClose : ( ) => void ; public onMessage : ( msg : DebugMessage ) => void ; public timeout = 20000 ; constructor ( port , host ) { this . msgQueue = [ ] ; this . connection = new Promise ( ( resolve , reject ) => { if ( ! host ) { return reject ( 'The hostname is missing in launch.json' ) ; } this . recvSocket = new Socket ( ) ; this . recvSocket . on ( 'data' , this . dataHandler . bind ( this ) ) ; this . recvSocket . on ( 'close' , this . closeHandler . bind ( this ) ) ; this . recvSocket . on ( 'error' , this . errorHandler . bind ( this ) ) ; this . sendSocket = new Socket ( ) ; this . sendSocket . on ( 'close' , this . closeHandler . bind ( this ) ) ; this . sendSocket . on ( 'error' , this . errorHandler . bind ( this ) ) ; this . recvSocket . addListener ( 'error' , connectionErrorHandler . bind ( this ) ) ; this . recvSocket . connect ( port , host , ( ) => { this . recvSocket . removeListener ( 'error' , connectionErrorHandler ) ; this . sendSocket . connect ( port , host ) ; resolve ( ) ; } ) ; function connectionErrorHandler ( err ) { this . recvSocket . removeListener ( 'error' , connectionErrorHandler ) ; if ( err . code === 'ECONNREFUSED' ) { reject ( err ) ; } } } ) ; } public errorHandler ( err ) { log ( err ) ; } public dataHandler ( data ) { if ( ! this . onMessage ) { return ; } convertDataToDebuggerMessage ( data ) . forEach ( ( msg ) => { this . onMessage ( msg ) ; } ) ; } public closeHandler ( ) { log ( 'Connection closed' ) ; if ( this . onClose ) { this . onClose ( ) ; } } public sendMessage ( msg ) { verbose ( `send(${ msg })` ) ; msg += '\0' ; this . msgQueue . push ( msg ) ; if ( this . msgQueue . length === 1 ) { this . writeMessage ( msg ) ; } } public writeMessage ( msg ) { this . sendSocket . write ( msg , ( ) => { this . msgQueue . shift ( ) ; if ( this . msgQueue . length > 0 ) { this . writeMessage ( this . msgQueue [ 0 ] ) ; } } ) ; } public close ( ) { this . sendSocket . destroy ( ) ; this . recvSocket . destroy ( ) ; } public showStack ( ) { return this . sendMessageWithResponse < DebugMessage > ( 'show stack-ide' , 'STACK-IDE' ) ; } public listVariables ( ) { return this . sendMessageWithResponse < DebugMessageVariables > ( 'list variables' , 'MSG_VARIABLES' ) ; } public listParameters ( ) { return this . sendMessageWithResponse < DebugMessageVariables > ( 'list parameters' , 'MSG_PARAMETERS' ) ; } public listTempTables ( ) { return this . sendMessageWithResponse < DebugMessage > ( 'list temp-tables' , 'MSG_TEMPTABLES' ) ; } public getFields ( tempTableName ) { return this . sendMessageWithResponse ( `GET-FIELDS ${ tempTableName }` , 'MSG_FIELDS' ) ; } public getClassInfo ( className ) { return this . sendMessageWithResponse < DebugMessageClassInfo > ( `GET-CLASS-INFO ${ className }` , 'MSG_CLASSINFO' , 'MSG_CLASSINFO_ERR' ) ; } public getArray ( arrayName ) { return this . sendMessageWithResponse < DebugMessageArray > ( `GET-ARRAY ${ arrayName }` , 'MSG_ARRAY' ) ; } public sendMessageWithResponse < T > ( messageToSend , respCode , respCodeError ? ) { return new Promise ( ( resolve , reject ) => { let response = null ; const self = this ; const respCallback = ( data ) => { response = convertDataToDebuggerMessage ( data ) . filter ( ( message ) => message . code === respCode || ( respCodeError && ( message . code === respCodeError ) ) ) ; if ( response . length > 0 ) { if ( response [ 0 ] . code === respCode ) { resolve ( response [ 0 ] ) ; } else { reject ( response [ 0 ] ) ; } self . recvSocket . removeListener ( 'data' , respCallback ) ; } } ; this . sendMessage ( messageToSend ) ; this . recvSocket . on ( 'data' , respCallback ) ; setTimeout ( ( ) => { if ( response === null ) { this . recvSocket . removeListener ( 'data' , respCallback ) ; reject ( new Error ( 'response not received' ) ) ; } } , this . timeout ) ; } ) ; } } class AblDebugSession extends DebugSession { private variableHandles : Handles < DebugVariable > ; private breakpoints : Map < string , DebugBreakpoint [ ] > ; private watchpointExpressions : Set < string > ; private threads : Set < number > ; private localRoot : string ; private remoteRoot : string ; private sourceMap : SourceMap ; private debugState : DebuggerState ; private ablDebugger : AblDebugger ; public constructor ( debuggerLinesStartAt1 , isServer = false ) { super ( debuggerLinesStartAt1 , isServer ) ; this . variableHandles = new Handles < DebugVariable > ( ) ; this . threads = new Set < number > ( ) ; this . debugState = null ; this . ablDebugger = null ; this . breakpoints = new Map < string , DebugBreakpoint [ ] > ( ) ; this . watchpointExpressions = new Set < string > ( ) ; this . localRoot = '' ; this . remoteRoot = '' ; this . sourceMap = { } ; const logPath = path . join ( os . tmpdir ( ) , 'vscode-abl-debug.txt' ) ; logger . init ( ( e ) => this . sendEvent ( e ) , logPath , isServer ) ; } protected initializeRequest ( response , args ) { verbose ( 'InitializeRequest' ) ; response . body . supportsConfigurationDoneRequest = true ; response . body . supportsEvaluateForHovers = true ; this . sendResponse ( response ) ; verbose ( 'InitializeResponse' ) ; } protected initializeDebugger ( args ) { const logLevel = args . trace === 'verbose' ? logger . LogLevel . Verbose : args . trace ? logger . LogLevel . Log : logger . LogLevel . Error ; logger . setMinLogLevel ( logLevel ) ; if ( args . remoteRoot ) { this . remoteRoot = normalizePath ( args . remoteRoot ) ; } if ( args . localRoot ) { this . localRoot = normalizePath ( args . localRoot ) ; } if ( args . sourceMap ) { this . sourceMap = args . sourceMap ; } this . ablDebugger = new AblDebugger ( args . port , args . address ) ; this . ablDebugger . onstdout = ( str ) => { this . sendEvent ( new OutputEvent ( str , 'stdout' ) ) ; } ; this . ablDebugger . onstderr = ( str ) => { this . sendEvent ( new OutputEvent ( str , 'stderr' ) ) ; } ; this . ablDebugger . onClose = ( ) => { this . sendEvent ( new TerminatedEvent ( ) ) ; verbose ( 'TerminatedEvent' ) ; } ; this . ablDebugger . onMessage = ( msg ) => { verbose ( `recv(${ msg . code } ${ JSON . stringify ( msg . args ) })` ) ; if ( msg . code === 'MSG_INFO' ) { this . sendEvent ( new OutputEvent ( msg . args [ 0 ] . toString ( ) ) ) ; } else if ( msg . code === 'MSG_ENTER' ) { this . sendEvent ( new StoppedEvent ( 'breakpoint' , 0 ) ) ; this . sendBreakpoints ( ) ; verbose ( 'StoppedEvent("breakpoint")' ) ; } else if ( msg . code === 'MSG_LISTING' ) { const msgListing = msg as DebugMessageListing ; verbose ( `${ JSON . stringify ( msgListing ) }` ) ; } } ; return this . ablDebugger . connection . then ( ( ) => { this . ablDebugger . sendMessage ( 'SETPROP IDE 1' ) ; this . sendEvent ( new InitializedEvent ( ) ) ; verbose ( 'InitializeEvent' ) ; } ) ; } protected launchRequest ( response , args ) { verbose ( 'LaunchRequest' ) ; const filename = args . program ; const cwd = args . cwd || path . dirname ( filename ) ; this . localRoot = normalizePath ( cwd ) ; args . port = args . port || DEFAULT_DEBUG_PORT ; const configFileName = path . join ( cwd , OPENEDGE_CONFIG_FILENAME ) ; loadConfigFile ( configFileName ) . then ( ( oeConfig ) => { const cmd = getProBin ( oeConfig . dlc ) ; const env = setupEnvironmentVariables ( process . env , oeConfig , cwd ) ; env . VSABL_STARTUP_PROGRAM = filename ; const proArgs = createProArgs ( { batchMode : true , debugPort : args . port , param : args . args ? args . args . join ( ' ' ) : '' , parameterFiles : oeConfig . parameterFiles , startupProcedure : path . join ( __dirname , '../../../abl-src/run-debug.p' ) , } ) ; const spawnOptions = { env , cwd } ; const spawnedProcess = spawn ( cmd , proArgs , spawnOptions ) ; spawnedProcess . stderr . on ( 'data' , ( chunk ) => { const str = chunk . toString ( ) ; this . sendEvent ( new OutputEvent ( str , 'stderr' ) ) ; } ) ; spawnedProcess . stdout . on ( 'data' , ( chunk ) => { const str = chunk . toString ( ) ; this . sendEvent ( new OutputEvent ( str , 'stdout' ) ) ; } ) ; spawnedProcess . on ( 'close' , ( code ) => { this . sendEvent ( new TerminatedEvent ( ) ) ; logError ( 'Process exiting with code: ' + code ) ; } ) ; spawnedProcess . on ( 'error' , ( err ) => { logError ( 'Process exiting with code: ' + err ) ; } ) ; const attachArgs = { address : 'localhost' , ... args , } ; this . initializeDebugger ( attachArgs ) . then ( ( ) => { spawnedProcess . stdin . write ( '\x0D' ) ; if ( args . stopOnEntry ) { this . ablDebugger . sendMessage ( 'interrupt' ) ; } else { this . ablDebugger . sendMessage ( 'cont' ) ; } this . sendResponse ( response ) ; verbose ( 'LaunchResponse' ) ; } , ( err ) => { this . sendErrorResponse ( response , 3000 , 'Failed to continue: "{e}"' , { e : err . toString ( ) } ) ; } ) ; } , ( err ) => { this . sendErrorResponse ( response , 3000 , 'Failed to load config file {f}: "{e}"' , { e : err . toString ( ) , f : configFileName , } ) ; } ) ; } protected attachRequest ( response , args ) { verbose ( 'AttachRequest' ) ; args . port = args . port || DEFAULT_DEBUG_PORT ; this . initializeDebugger ( args ) . then ( ( ) => { this . sendResponse ( response ) ; verbose ( 'AttachResponse' ) ; } , ( err ) => { this . sendErrorResponse ( response , 3000 , 'Failed to continue: "{e}"' , { e : err . toString ( ) } ) ; } ) ; } protected disconnectRequest ( response , args ) { verbose ( 'DisconnectRequest' ) ; this . ablDebugger . close ( ) ; super . disconnectRequest ( response , args ) ; verbose ( 'DisconnectResponse' ) ; } protected configurationDoneRequest ( response , args ) { verbose ( 'ConfigurationDoneRequest' ) ; this . sendResponse ( response ) ; } protected sendBreakpoints ( ) { let msg ; if ( this . breakpoints . size > 0 ) { msg = 'break ' ; let bpIdx = 1 ; this . breakpoints . forEach ( ( breakpoints , f ) => { breakpoints . forEach ( ( bp ) => { msg += `B;${ bpIdx };E;${ bp . file };${ bp . line }; ;` ; bpIdx += 1 ; } ) ; } ) ; } else { msg = 'break;' ; } this . ablDebugger . sendMessage ( msg ) ; } protected setBreakPointsRequest ( response , args ) { verbose ( 'SetBreakPointsRequest' ) ; const file = this . convertLocalPathToRemote ( args . source . path ) ; if ( ! this . breakpoints . get ( file ) ) { this . breakpoints . set ( file , [ ] ) ; } const breakpoints = args . lines . map ( ( line ) => { return { file , line , verified : true , } ; } ) ; this . breakpoints . set ( file , breakpoints ) ; this . sendBreakpoints ( ) ; response . body = { breakpoints } ; this . sendResponse ( response ) ; verbose ( 'SetBreakPointsResponse' ) ; } protected threadsRequest ( response ) { verbose ( 'ThreadsRequest' ) ; const threads = [ new Thread ( 0 , 'abl-main' ) ] ; response . body = { threads } ; this . sendResponse ( response ) ; verbose ( 'ThreadsResponse' , threads ) ; } protected stackTraceRequest ( response , args ) { verbose ( 'StackTraceRequest' ) ; this . ablDebugger . showStack ( ) . then ( ( msg ) => { const stackFrames = msg . args . map ( ( location , i ) => { const filename = path . basename ( location [ 4 ] ) ; const localPath = this . convertRemotePathToLocal ( location [ 4 ] ) ; return new StackFrame ( i , location [ 6 ] , new Source ( filename , localPath ) , parseInt ( location [ 8 ] , 10 ) ) ; } ) ; stackFrames . reverse ( ) ; response . body = { stackFrames } ; this . sendResponse ( response ) ; verbose ( 'StackTraceResponse' ) ; } ) . catch ( ( reason ) => { logError ( 'Failed to produce stack trace!' ) ; return this . sendErrorResponse ( response , 2004 , 'Unable to produce stack trace: "{e}"' , { e : reason . toString ( ) } ) ; } ) ; } protected scopesRequest ( response , args ) { verbose ( 'ScopesRequest' ) ; this . ablDebugger . listParameters ( ) . then ( ( msgParameters ) => { this . ablDebugger . listVariables ( ) . then ( ( msgVariables ) => { this . ablDebugger . listTempTables ( ) . then ( ( msgTempTables ) => { const parameters = msgParameters . variables ; const variables = msgVariables . variables ; const tempTables = msgTempTables . args . map ( ( p ) => { return { children : [ ] , kind : AblDebugKind . TempTable , name : p [ 0 ] , type : p [ 1 ] , value : '' , } ; } ) ; const scopes = new Array < Scope > ( ) ; scopes . push ( new Scope ( 'Local' , this . variableHandles . create ( { children : parameters . concat ( variables ) . concat ( tempTables ) , kind : 0 , name : 'Local' , type : '' , value : '' , } ) , false ) ) ; response . body = { scopes } ; this . sendResponse ( response ) ; verbose ( 'ScopesResponse' ) ; } ) ; } ) ; } ) . catch ( ( reason ) => { logError ( 'Unable to list vars.' ) ; return this . sendErrorResponse ( response , 2004 , 'Unable to list vars: "{e}"' , { e : reason . toString ( ) } ) ; } ) ; } protected variablesRequest ( response , args ) { verbose ( 'VariablesRequest' ) ; const vari = this . variableHandles . get ( args . variablesReference ) ; let variables ; if ( vari . kind === AblDebugKind . TempTable ) { this . ablDebugger . getFields ( vari . name ) . then ( ( msgFields ) => { const fields = msgFields . args . slice ( 1 ) ; variables = fields . map ( ( f ) => { return { children : [ ] , kind : AblDebugKind . Variable , name : f [ 0 ] , type : f [ 1 ] , value : f [ 3 ] , } ; } ) . map ( ( f , i ) => { const { result , variablesReference } = this . convertDebugVariableToProtocolVariable ( f , i ) ; return { name : f . name , value : result , variablesReference , } ; } ) ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } ) ; } else if ( vari . kind === AblDebugKind . Class ) { let classInfoName = vari . name ; let parentRef = vari . parentReference ; while ( parentRef ) { const parentVar = this . variableHandles . get ( parentRef ) ; if ( parentVar ) { if ( parentVar . kind === AblDebugKind . Class ) { classInfoName = parentVar . name + ':' + classInfoName ; } parentRef = parentVar . parentReference ; } else { parentRef = null ; } } this . ablDebugger . getClassInfo ( classInfoName ) . then ( ( msgClassInfo ) => { const baseClass = msgClassInfo . baseClass ; const fields = msgClassInfo . properties ; if ( baseClass ) { const varBaseClass = { children : [ ] , kind : AblDebugKind . BaseClass , name : '<base>' , parentReference : args . variablesReference , type : baseClass , value : baseClass , } ; fields . unshift ( varBaseClass ) ; } variables = fields . map ( ( f , i ) => { f . parentReference = args . variablesReference ; const { result , variablesReference } = this . convertDebugVariableToProtocolVariable ( f , i ) ; return { name : f . name , value : result , variablesReference , } ; } ) ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } , ( msgErr ) => { variables = [ { name : 'error' , value : msgErr . args [ 0 ] [ 0 ] , variablesReference : 0 } ] ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } ) ; } else if ( vari . kind === AblDebugKind . Array ) { this . ablDebugger . getArray ( vari . name ) . then ( ( msgArray ) => { const values = msgArray . values ; if ( values . length > 0 ) { variables = values . map ( ( v , i ) => { const debugVariable = { children : [ ] , kind : AblDebugKind . Variable , name : `${ i + 1 }` , type : vari . type , value : v , } ; const { result , variablesReference } = this . convertDebugVariableToProtocolVariable ( debugVariable , i ) ; return { name : debugVariable . name , value : result , variablesReference , } ; } ) ; } response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } , ( msgErr ) => { variables = [ { name : 'error' , value : msgErr . args [ 0 ] [ 0 ] , variablesReference : 0 } ] ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } ) ; } else if ( vari . kind === AblDebugKind . BaseClass ) { let depth = 0 ; let classInfoName = '' ; let parentRef = vari . parentReference ; while ( parentRef ) { const parentVar = this . variableHandles . get ( parentRef ) ; if ( parentVar ) { if ( parentVar . kind !== AblDebugKind . BaseClass ) { if ( classInfoName !== '' ) { classInfoName = parentVar . name + ':' + classInfoName ; } else { classInfoName = parentVar . name ; } } depth += 1 ; parentRef = parentVar . parentReference ; } else { parentRef = null ; } } this . ablDebugger . getClassInfo ( `${ classInfoName } DEPTH ${ depth }` ) . then ( ( msgClassInfo ) => { const baseClass = msgClassInfo . baseClass ; const fields = msgClassInfo . properties ; if ( baseClass ) { const varBaseClass = { children : [ ] , kind : AblDebugKind . BaseClass , name : '<base>' , parentReference : args . variablesReference , type : baseClass , value : baseClass , } ; fields . unshift ( varBaseClass ) ; } variables = fields . map ( ( f , i ) => { f . parentReference = args . variablesReference ; const { result , variablesReference } = this . convertDebugVariableToProtocolVariable ( f , i ) ; return { name : f . name , value : result , variablesReference , } ; } ) ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } , ( msgErr ) => { variables = [ { name : 'error' , value : msgErr . args [ 0 ] [ 0 ] , variablesReference : 0 } ] ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } ) ; } else { variables = vari . children . map ( ( v , i ) => { const { result , variablesReference } = this . convertDebugVariableToProtocolVariable ( v , i ) ; return { name : v . name , value : result , variablesReference , } ; } ) ; response . body = { variables } ; this . sendResponse ( response ) ; verbose ( 'VariablesResponse' ) ; } } protected continueRequest ( response ) { verbose ( 'ContinueRequest' ) ; this . ablDebugger . sendMessage ( 'cont' ) ; this . sendResponse ( response ) ; verbose ( 'ContinueResponse' ) ; } protected nextRequest ( response ) { verbose ( 'NextRequest' ) ; this . ablDebugger . sendMessage ( 'next' ) ; this . sendResponse ( response ) ; verbose ( 'NextResponse' ) ; } protected stepInRequest ( response ) { verbose ( 'StepInRequest' ) ; this . ablDebugger . sendMessage ( 'step' ) ; this . sendResponse ( response ) ; verbose ( 'StepInResponse' ) ; } protected stepOutRequest ( response ) { verbose ( 'StepOutRequest' ) ; this . ablDebugger . sendMessage ( 'step-out' ) ; this . sendResponse ( response ) ; verbose ( 'StepOutResponse' ) ; } protected pauseRequest ( response ) { verbose ( 'PauseRequest' ) ; this . ablDebugger . sendMessage ( 'interrupt' ) ; this . sendResponse ( response ) ; verbose ( 'PauseResponse' ) ; } protected evaluateRequest ( response , args ) { verbose ( 'EvaluateRequest' ) ; if ( args . context === 'watch' || args . context === 'hover' ) { if ( ! this . watchpointExpressions . has ( args . expression ) ) { this . ablDebugger . sendMessage ( `watch ${ args . expression }` ) ; this . watchpointExpressions . add ( args . expression ) ; } this . ablDebugger . sendMessageWithResponse < DebugMessage > ( 'show watch' , 'MSG_WATCHPOINTS' ) . then ( ( msg ) => { const watchpoint = msg . args . find ( ( wp ) => { return wp [ 1 ] === args . expression ; } ) ; if ( watchpoint ) { const variable = { children : [ ] , kind : AblDebugKind . Variable , name : watchpoint [ 1 ] , type : watchpoint [ 2 ] , value : watchpoint [ 5 ] , } ; response . body = this . convertDebugVariableToProtocolVariable ( variable , 0 ) ; } this . sendResponse ( response ) ; verbose ( 'EvaluateResponse' ) ; } ) ; } else { this . sendResponse ( response ) ; verbose ( 'EvaluateResponse' ) ; } } protected convertRemotePathToLocal ( remotePath ) { if ( this . sourceMap ) { const entries = Object . keys ( this . sourceMap ) ; const entry = entries . find ( ( value ) => minimatch ( remotePath , value ) ) ; if ( entry ) { const baseRemote = entry . replace ( /\*/g , '' ) ; const baseLocal = this . sourceMap [ entry ] . replace ( /\*/g , '' ) ; remotePath = remotePath . replace ( baseRemote , baseLocal ) ; return remotePath ; } } if ( this . localRoot && this . remoteRoot ) { remotePath = normalizePath ( remotePath ) ; remotePath = remotePath . replace ( this . remoteRoot , '' ) ; remotePath = path . join ( this . localRoot , remotePath ) ; } return remotePath ; } protected convertLocalPathToRemote ( localPath ) { if ( this . localRoot && this . remoteRoot ) { localPath = normalizePath ( localPath ) ; localPath = localPath . replace ( this . localRoot , this . remoteRoot ) ; } return localPath ; } private convertDebugVariableToProtocolVariable ( v , i ) { if ( v . kind === AblDebugKind . Array ) { return { result : v . type + v . value , variablesReference : this . variableHandles . create ( v ) , } ; } else if ( v . kind === AblDebugKind . TempTable ) { return { result : '<' + v . type + '>' , variablesReference : this . variableHandles . create ( v ) , } ; } else if ( v . kind === AblDebugKind . Class || v . kind === AblDebugKind . BaseClass ) { if ( v . value !== '?' ) { return { result : v . value , variablesReference : this . variableHandles . create ( v ) , } ; } return { result : v . value , variablesReference : 0 , } ; } else if ( v . type === 'CHARACTER' || v . type === 'LONGCHAR' ) { let val = v . value ; const quoteIdx = val . indexOf ( '"' ) ; if ( quoteIdx > 0 ) { val = val . substr ( quoteIdx ) ; } return { result : val , variablesReference : 0 , } ; } else { return { result : v . value || ( '<' + v . type + '>' ) , variablesReference : v . children . length > 0 ? this . variableHandles . create ( v ) : 0 , } ; } } } DebugSession . run ( AblDebugSession ) ;