@@ -49,17 +49,17 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
4949 */
5050interface LaunchRequestArguments extends VSCodeDebugProtocol . LaunchRequestArguments {
5151 /** The port where the adapter should listen for XDebug connections (default: 9000) */
52- port : number ;
52+ port ? : number ;
5353 /** Automatically stop target after launch. If not specified, target does not stop. */
5454 stopOnEntry ?: boolean ;
5555 /** The source root on the server when doing remote debugging on a different host */
5656 serverSourceRoot ?: string ;
57- /** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. May be relative to cwd. */
57+ /** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
5858 localSourceRoot ?: string ;
5959 /** The current working directory, by default the project root */
6060 cwd ?: string ;
6161 /** If true, will log all communication between VS Code and the adapter to the console */
62- log : boolean ;
62+ log ? : boolean ;
6363}
6464
6565class PhpDebugSession extends vscode . DebugSession {
@@ -72,16 +72,19 @@ class PhpDebugSession extends vscode.DebugSession {
7272
7373 /**
7474 * A map from VS Code thread IDs to XDebug Connections.
75- * XDebug makes a new connection for each request to the webserver, we present hese as threads to VS Code.
75+ * XDebug makes a new connection for each request to the webserver, we present these as threads to VS Code.
7676 * The threadId key is equal to the id attribute of the connection.
7777 */
7878 private _connections = new Map < number , xdebug . Connection > ( ) ;
7979
80- /** A set of connecitons which still need to be initialized with exception breakpoints before _runOrStopOnEntry can be called. */
81- private _connectionsAwaitingExceptionBreakpoints = new Set < xdebug . Connection > ( ) ;
80+ /** A set of connections which are not yet running and are waiting for configurationDoneRequest */
81+ private _waitingConnections = new Set < xdebug . Connection > ( ) ;
8282
83- /** A set of connecitons which still need to be initialized with exception breakpoints before _runOrStopOnEntry can be called. */
84- private _connectionsAwaitingBreakpoints = new Set < xdebug . Connection > ( ) ;
83+ /** A counter for unique source IDs */
84+ private _sourceIdCounter = 1 ;
85+
86+ /** A map of VS Code source IDs to XDebug file URLs for virtual files (dpgp://whatever) and the corresponding connection */
87+ private _sources = new Map < number , { connection : xdebug . Connection , url : string } > ( ) ;
8588
8689 /** A counter for unique stackframe IDs */
8790 private _stackFrameIdCounter = 1 ;
@@ -105,28 +108,25 @@ class PhpDebugSession extends vscode.DebugSession {
105108 super ( debuggerLinesStartAt1 , isServer ) ;
106109 }
107110
111+ protected initializeRequest ( response : VSCodeDebugProtocol . InitializeResponse , args : VSCodeDebugProtocol . InitializeRequestArguments ) : void {
112+ response . body . supportsConfigurationDoneRequest = true ;
113+ response . body . supportsEvaluateForHovers = true ;
114+ this . sendResponse ( response ) ;
115+ }
116+
108117 protected attachRequest ( response : VSCodeDebugProtocol . AttachResponse , args : VSCodeDebugProtocol . AttachRequestArguments ) {
109118 this . sendErrorResponse ( response , 0 , 'Attach requests are not supported' ) ;
110119 this . shutdown ( ) ;
111120 }
112121
113122 protected launchRequest ( response : VSCodeDebugProtocol . LaunchResponse , args : LaunchRequestArguments ) : void {
114- if ( args . serverSourceRoot ) {
115- // use cwd by default for localSourceRoot
116- if ( ! args . localSourceRoot ) {
117- args . localSourceRoot = '.' ;
118- }
119- // resolve localSourceRoot relative to the project root
120- args . localSourceRoot = path . resolve ( args . cwd , args . localSourceRoot ) ;
121- }
122123 this . _args = args ;
123124 const server = this . _server = net . createServer ( ) ;
124125 server . on ( 'connection' , ( socket : net . Socket ) => {
125126 // new XDebug connection
126127 const connection = new xdebug . Connection ( socket ) ;
127128 this . _connections . set ( connection . id , connection ) ;
128- this . _connectionsAwaitingBreakpoints . add ( connection ) ;
129- this . _connectionsAwaitingExceptionBreakpoints . add ( connection ) ;
129+ this . _waitingConnections . add ( connection ) ;
130130 connection . waitForInitPacket ( )
131131 . then ( ( ) => {
132132 this . sendEvent ( new vscode . ThreadEvent ( 'started' , connection . id ) ) ;
@@ -146,16 +146,6 @@ class PhpDebugSession extends vscode.DebugSession {
146146 this . sendResponse ( response ) ;
147147 }
148148
149- /** is called after all breakpoints etc. are initialized and either runs the script or notifies VS Code that we stopped on entry, depending on launch settings */
150- private _runOrStopOnEntry ( connection : xdebug . Connection ) : void {
151- // either tell VS Code we stopped on entry or run the script
152- if ( this . _args . stopOnEntry ) {
153- this . sendEvent ( new vscode . StoppedEvent ( 'entry' , connection . id ) ) ;
154- } else {
155- connection . sendRunCommand ( ) . then ( response => this . _checkStatus ( response ) ) ;
156- }
157- }
158-
159149 /** Checks the status of a StatusResponse and notifies VS Code accordingly */
160150 private _checkStatus ( response : xdebug . StatusResponse ) : void {
161151 const connection = response . connection ;
@@ -182,9 +172,12 @@ class PhpDebugSession extends vscode.DebugSession {
182172 }
183173
184174 /** converts a server-side XDebug file URI to a local path for VS Code with respect to source root settings */
185- protected convertDebuggerPathToClient ( fileUri : string ) : string {
175+ protected convertDebuggerPathToClient ( fileUri : string | url . Url ) : string {
176+ if ( typeof fileUri === 'string' ) {
177+ fileUri = url . parse ( < string > fileUri ) ;
178+ }
186179 // convert the file URI to a path
187- let serverPath = decodeURI ( url . parse ( fileUri ) . pathname ) ;
180+ let serverPath = decodeURI ( ( < url . Url > fileUri ) . pathname ) ;
188181 // strip the trailing slash from Windows paths (indicated by a drive letter with a colon)
189182 if ( / ^ \/ [ a - z A - Z ] : \/ / . test ( serverPath ) ) {
190183 serverPath = serverPath . substr ( 1 ) ;
@@ -225,7 +218,7 @@ class PhpDebugSession extends vscode.DebugSession {
225218 }
226219
227220 /** Logs all requests before dispatching */
228- protected dispatchRequest ( request : VSCodeDebugProtocol . Request ) {
221+ protected dispatchRequest ( request : VSCodeDebugProtocol . Request ) : void {
229222 const log = `-> ${ request . command } Request\n${ util . inspect ( request , { depth : null } ) } \n\n` ;
230223 console . log ( log ) ;
231224 if ( this . _args && this . _args . log ) {
@@ -243,7 +236,7 @@ class PhpDebugSession extends vscode.DebugSession {
243236 super . sendEvent ( event ) ;
244237 }
245238
246- public sendResponse ( response : VSCodeDebugProtocol . Response ) {
239+ public sendResponse ( response : VSCodeDebugProtocol . Response ) : void {
247240 const log = `<- ${ response . command } Response\n${ util . inspect ( response , { depth : null } ) } \n\n` ;
248241 console [ response . success ? 'log' : 'error' ] ( log ) ;
249242 if ( this . _args && this . _args . log ) {
@@ -276,15 +269,6 @@ class PhpDebugSession extends vscode.DebugSession {
276269 . then ( ( ) => Promise . all ( args . lines . map ( line =>
277270 connection . sendBreakpointSetCommand ( { type : 'line' , fileUri, line} )
278271 . then ( xdebugResponse => {
279- // has this connection finally received its long-awaited breakpoints?
280- if ( this . _connectionsAwaitingBreakpoints . has ( connection ) ) {
281- // remember that the breakpoints for this connection have been set
282- this . _connectionsAwaitingBreakpoints . delete ( connection ) ;
283- // if this connection has already received exception breakpoints, run it now
284- if ( ! this . _connectionsAwaitingExceptionBreakpoints . has ( connection ) ) {
285- this . _runOrStopOnEntry ( connection ) ;
286- }
287- }
288272 // only capture each breakpoint once
289273 if ( connectionIndex === 0 ) {
290274 breakpoints . push ( new vscode . Breakpoint ( true , line ) ) ;
@@ -333,24 +317,26 @@ class PhpDebugSession extends vscode.DebugSession {
333317 return connection . sendBreakpointSetCommand ( { type : 'exception' , exception : '*' } ) ;
334318 }
335319 } )
336- . then ( ( ) => {
337- // has this connection finally received its long-awaited exception breakpoints?
338- if ( this . _connectionsAwaitingExceptionBreakpoints . has ( connection ) ) {
339- // remember that the exception breakpoints for this connection have been set
340- this . _connectionsAwaitingExceptionBreakpoints . delete ( connection ) ;
341- // if this connection has already received line breakpoints, run it now
342- if ( ! this . _connectionsAwaitingBreakpoints . has ( connection ) ) {
343- this . _runOrStopOnEntry ( connection ) ;
344- }
345- }
346- } )
347320 ) ) . then ( ( ) => {
348321 this . sendResponse ( response ) ;
349322 } ) . catch ( error => {
350323 this . sendErrorResponse ( response , error . code , error . message ) ;
351324 } ) ;
352325 }
353326
327+ /** Executed after all breakpoints have been set by VS Code */
328+ protected configurationDoneRequest ( response : VSCodeDebugProtocol . ConfigurationDoneResponse , args : VSCodeDebugProtocol . ConfigurationDoneArguments ) : void {
329+ for ( const connection of Array . from ( this . _waitingConnections ) ) {
330+ // either tell VS Code we stopped on entry or run the script
331+ if ( this . _args . stopOnEntry ) {
332+ this . sendEvent ( new vscode . StoppedEvent ( 'entry' , connection . id ) ) ;
333+ } else {
334+ connection . sendRunCommand ( ) . then ( response => this . _checkStatus ( response ) ) ;
335+ }
336+ }
337+ this . sendResponse ( response ) ;
338+ }
339+
354340 /** Executed after a successfull launch or attach request and after a ThreadEvent */
355341 protected threadsRequest ( response : VSCodeDebugProtocol . ThreadsResponse ) : void {
356342 // PHP doesn't have threads, but it may have multiple requests in parallel.
@@ -374,10 +360,18 @@ class PhpDebugSession extends vscode.DebugSession {
374360 // this._contexts.clear();
375361 response . body = {
376362 stackFrames : xdebugResponse . stack . map ( stackFrame => {
377- // XDebug paths are URIs, VS Code file paths
378- const filePath = this . convertDebuggerPathToClient ( stackFrame . fileUri ) ;
379- // "Name" of the source and the actual file path
380- const source = new vscode . Source ( path . basename ( filePath ) , filePath ) ;
363+ let source : vscode . Source ;
364+ const urlObject = url . parse ( stackFrame . fileUri ) ;
365+ if ( urlObject . protocol === 'dbgp:' ) {
366+ const sourceReference = this . _sourceIdCounter ++ ;
367+ this . _sources . set ( sourceReference , { connection, url : stackFrame . fileUri } ) ;
368+ source = new vscode . Source ( stackFrame . name , stackFrame . fileUri . substr ( 'dbgp://' . length ) , sourceReference , stackFrame . type ) ;
369+ } else {
370+ // XDebug paths are URIs, VS Code file paths
371+ const filePath = this . convertDebuggerPathToClient ( urlObject ) ;
372+ // "Name" of the source and the actual file path
373+ source = new vscode . Source ( path . basename ( filePath ) , filePath ) ;
374+ }
381375 // a new, unique ID for scopeRequests
382376 const stackFrameId = this . _stackFrameIdCounter ++ ;
383377 // save the connection this stackframe belongs to and the level of the stackframe under the stacktrace id
@@ -393,6 +387,14 @@ class PhpDebugSession extends vscode.DebugSession {
393387 } ) ;
394388 }
395389
390+ protected sourceRequest ( response : VSCodeDebugProtocol . SourceResponse , args : VSCodeDebugProtocol . SourceArguments ) : void {
391+ const { connection, url} = this . _sources . get ( args . sourceReference ) ;
392+ connection . sendSourceCommand ( url ) . then ( xdebugResponse => {
393+ response . body . content = xdebugResponse . source ;
394+ this . sendResponse ( response ) ;
395+ } ) ;
396+ }
397+
396398 protected scopesRequest ( response : VSCodeDebugProtocol . ScopesResponse , args : VSCodeDebugProtocol . ScopesArguments ) : void {
397399 const stackFrame = this . _stackFrames . get ( args . frameId ) ;
398400 stackFrame . getContexts ( )
@@ -428,7 +430,7 @@ class PhpDebugSession extends vscode.DebugSession {
428430 } else if ( this . _evalResultProperties . has ( variablesReference ) ) {
429431 // the children of properties returned from an eval command are always inlined, so we simply resolve them
430432 const property = this . _evalResultProperties . get ( variablesReference ) ;
431- propertiesPromise = Promise . resolve ( property . children ) ;
433+ propertiesPromise = Promise . resolve ( property . hasChildren ? property . children : [ ] ) ;
432434 } else {
433435 console . error ( 'Unknown variable reference: ' + variablesReference ) ;
434436 console . error ( 'Known variables: ' + JSON . stringify ( Array . from ( this . _properties ) ) ) ;
0 commit comments