@@ -7,12 +7,6 @@ import * as url from 'url';
77import * as path from 'path' ;
88import * as util from 'util' ;
99
10- /** PHP expression that is executed with an eval command if breaking on exceptions is enabled */
11- const SET_EXCEPTION_HANDLER_PHP = `
12- set_exception_handler("xdebug_break");
13- set_error_handler("xdebug_break");
14- ` ;
15-
1610/** converts a file path to file:// URI */
1711function path2uri ( str : string ) : string {
1812 var pathName = str . replace ( / \\ / g, '/' ) ;
@@ -27,6 +21,32 @@ function uri2path(uri: string): string {
2721 return url . parse ( uri ) . pathname . substr ( 1 ) ;
2822}
2923
24+ /** formats a xdebug property value for VS Code */
25+ function formatPropertyValue ( property : xdebug . BaseProperty ) : string {
26+ let displayValue : string ;
27+ if ( property . hasChildren || property . type === 'array' || property . type === 'object' ) {
28+ if ( property . type === 'array' ) {
29+ // for arrays, show the length, like a var_dump would do
30+ displayValue = 'array(' + ( property . hasChildren ? property . numberOfChildren : 0 ) + ')' ;
31+ } else if ( property . type === 'object' && property . class ) {
32+ // for objects, show the class name as type (if specified)
33+ displayValue = property . class ;
34+ } else {
35+ // edge case: show the type of the property as the value
36+ displayValue = property . type ;
37+ }
38+ } else {
39+ // for null, uninitialized, resource, etc. show the type
40+ displayValue = property . value || property . type === 'string' ? property . value : property . type ;
41+ if ( property . type === 'string' ) {
42+ displayValue = '"' + displayValue + '"' ;
43+ } else if ( property . type === 'bool' ) {
44+ displayValue = ! ! parseInt ( displayValue ) + '' ;
45+ }
46+ }
47+ return displayValue ;
48+ }
49+
3050/**
3151 * This interface should always match the schema found in the mock-debug extension manifest.
3252 */
@@ -47,8 +67,8 @@ class PhpDebugSession extends vscode.DebugSession {
4767 private _connections = new Map < number , xdebug . Connection > ( ) ;
4868 /** The first connection we receive */
4969 private _mainConnection : xdebug . Connection = null ;
50- /** Gets set to true after _runOrStopOnEntry is called the first time */
51- private _running = false ;
70+ /** Gets set to true after _runOrStopOnEntry is called the first time, which means all exceptions etc. are set */
71+ private _initialized = false ;
5272 /** A map of file URIs to lines: breakpoints received from VS Code */
5373 private _breakpoints = new Map < string , number [ ] > ( ) ;
5474 /** Gets set after a setExceptionBreakpointsRequest */
@@ -82,7 +102,7 @@ class PhpDebugSession extends vscode.DebugSession {
82102 // new XDebug connection
83103 const connection = new xdebug . Connection ( socket ) ;
84104 this . _connections . set ( connection . id , connection ) ;
85- if ( this . _running ) {
105+ if ( this . _initialized ) {
86106 // this is a new connection, for example triggered by a seperate, parallel request to the webserver.
87107 connection . waitForInitPacket ( )
88108 // tell VS Code that this is a new thread
@@ -102,7 +122,7 @@ class PhpDebugSession extends vscode.DebugSession {
102122 // restore exception breakpoint settings for the new connection
103123 . then ( ( ) => {
104124 if ( this . _breakOnExceptions ) {
105- return connection . sendEvalCommand ( SET_EXCEPTION_HANDLER_PHP ) ;
125+ return connection . sendBreakpointSetCommand ( { type : 'exception' , exception : '*' } ) ;
106126 }
107127 } )
108128 // run the script or stop on entry
@@ -139,6 +159,7 @@ class PhpDebugSession extends vscode.DebugSession {
139159 /** 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 */
140160 private _runOrStopOnEntry ( connection : xdebug . Connection ) : void {
141161 // either tell VS Code we stopped on entry or run the script
162+ this . _initialized = true ;
142163 if ( this . _args . stopOnEntry ) {
143164 this . sendEvent ( new vscode . StoppedEvent ( 'entry' , connection . id ) ) ;
144165 } else {
@@ -197,28 +218,36 @@ class PhpDebugSession extends vscode.DebugSession {
197218 /** This is called for each source file that has breakpoints with all the breakpoints in that file and whenever these change. */
198219 protected setBreakPointsRequest ( response : VSCodeDebugProtocol . SetBreakpointsResponse , args : VSCodeDebugProtocol . SetBreakpointsArguments ) {
199220 const file = path2uri ( args . source . path ) ;
200- this . _breakpoints . set ( file , args . lines ) ;
201221 const breakpoints : vscode . Breakpoint [ ] = [ ] ;
202222 const connections = Array . from ( this . _connections . values ( ) ) ;
203223 return Promise . all ( connections . map ( connection =>
204- Promise . all ( args . lines . map ( line =>
205- connection . sendBreakpointSetCommand ( { type : 'line' , file, line} )
206- . then ( xdebugResponse => {
207- // only capture each breakpoint once (for the main connection)
208- if ( connection === this . _mainConnection ) {
209- breakpoints . push ( new vscode . Breakpoint ( true , line ) ) ;
210- }
211- } )
212- . catch ( error => {
213- // only capture each breakpoint once (for the main connection)
214- if ( connection === this . _mainConnection ) {
215- console . error ( 'breakpoint could not be set: ' , error ) ;
216- breakpoints . push ( new vscode . Breakpoint ( false , line ) ) ;
217- }
218- } )
219- ) )
224+ // clear breakpoints for this file
225+ connection . sendBreakpointListCommand ( )
226+ . then ( response => Promise . all (
227+ response . breakpoints
228+ . filter ( breakpoint => breakpoint . type === 'line' && breakpoint . fileUri === file )
229+ . map ( breakpoint => breakpoint . remove ( ) )
230+ ) )
231+ // set them
232+ . then ( ( ) => Promise . all ( args . lines . map ( line =>
233+ connection . sendBreakpointSetCommand ( { type : 'line' , file, line} )
234+ . then ( xdebugResponse => {
235+ // only capture each breakpoint once (for the main connection)
236+ if ( connection === this . _mainConnection ) {
237+ breakpoints . push ( new vscode . Breakpoint ( true , line ) ) ;
238+ }
239+ } )
240+ . catch ( error => {
241+ // only capture each breakpoint once (for the main connection)
242+ if ( connection === this . _mainConnection ) {
243+ console . error ( 'breakpoint could not be set: ' , error ) ;
244+ breakpoints . push ( new vscode . Breakpoint ( false , line ) ) ;
245+ }
246+ } )
247+ ) ) )
220248 ) ) . then ( ( ) => {
221249 response . body = { breakpoints} ;
250+ this . _breakpoints . set ( file , args . lines ) ;
222251 this . sendResponse ( response ) ;
223252 } ) . catch ( error => {
224253 this . sendErrorResponse ( response , error . code , error . message ) ;
@@ -228,21 +257,37 @@ class PhpDebugSession extends vscode.DebugSession {
228257 /** This is called once after all line breakpoints have been set and whenever the breakpoints settings change */
229258 protected setExceptionBreakPointsRequest ( response : VSCodeDebugProtocol . SetExceptionBreakpointsResponse , args : VSCodeDebugProtocol . SetExceptionBreakpointsArguments ) : void {
230259 // args.filters can contain 'all' and 'uncaught', but 'uncaught' is the only setting XDebug supports
231- this . _breakOnExceptions = args . filters . indexOf ( 'uncaught' ) !== - 1 ;
260+ const breakOnExceptions = args . filters . indexOf ( 'uncaught' ) !== - 1 ;
261+ if ( args . filters . indexOf ( 'all' ) !== - 1 ) {
262+ this . sendEvent ( new vscode . OutputEvent ( 'breaking on caught exceptions is not supported by XDebug' , 'stderr' ) ) ;
263+ }
232264 Promise . resolve ( )
233- . then ( ( ) => {
234- if ( this . _breakOnExceptions ) {
235- // tell PHP to break on uncaught exceptions and errors
265+ . then < any > ( ( ) => {
266+ // does the new setting differ from the current setting?
267+ if ( breakOnExceptions !== ! ! this . _breakOnExceptions ) {
236268 const connections = Array . from ( this . _connections . values ( ) ) ;
237- return Promise . all ( connections . map ( connection => connection . sendEvalCommand ( SET_EXCEPTION_HANDLER_PHP ) ) ) ;
269+ if ( breakOnExceptions ) {
270+ // set an exception breakpoint for all exceptions
271+ return Promise . all ( connections . map ( connection => connection . sendBreakpointSetCommand ( { type : 'exception' , exception : '*' } ) ) ) ;
272+ } else {
273+ // remove all exception breakpoints
274+ return Promise . all ( connections . map ( connection =>
275+ connection . sendBreakpointListCommand ( )
276+ . then ( response => Promise . all (
277+ response . breakpoints
278+ . filter ( breakpoint => breakpoint . type === 'exception' )
279+ . map ( breakpoint => breakpoint . remove ( ) )
280+ ) )
281+ ) ) ;
282+ }
238283 }
239284 } )
240285 . then ( ( ) => {
286+ this . _breakOnExceptions = breakOnExceptions ;
241287 this . sendResponse ( response ) ;
242288 // if this is the first time this is called and the main connection is not yet running, trigger a run because now everything is set up
243- if ( ! this . _running ) {
289+ if ( ! this . _initialized ) {
244290 this . _runOrStopOnEntry ( this . _mainConnection ) ;
245- this . _running = true ;
246291 }
247292 } )
248293 . catch ( error => {
@@ -314,7 +359,7 @@ class PhpDebugSession extends vscode.DebugSession {
314359 }
315360
316361 protected variablesRequest ( response : VSCodeDebugProtocol . VariablesResponse , args : VSCodeDebugProtocol . VariablesArguments ) : void {
317- const variablesReference = args . variablesReference
362+ const variablesReference = args . variablesReference ;
318363 let propertiesPromise : Promise < xdebug . BaseProperty [ ] > ;
319364 if ( this . _contexts . has ( variablesReference ) ) {
320365 // VS Code is requesting the variables for a SCOPE, so we have to do a context_get
@@ -323,7 +368,7 @@ class PhpDebugSession extends vscode.DebugSession {
323368 } else if ( this . _properties . has ( variablesReference ) ) {
324369 // VS Code is requesting the subelements for a variable, so we have to do a property_get
325370 const property = this . _properties . get ( variablesReference ) ;
326- propertiesPromise = property . getChildren ( ) ;
371+ propertiesPromise = property . hasChildren ? property . getChildren ( ) : Promise . resolve ( [ ] ) ;
327372 } else if ( this . _evalResultProperties . has ( variablesReference ) ) {
328373 // the children of properties returned from an eval command are always inlined, so we simply resolve them
329374 const property = this . _evalResultProperties . get ( variablesReference ) ;
@@ -338,8 +383,8 @@ class PhpDebugSession extends vscode.DebugSession {
338383 . then ( properties => {
339384 response . body = {
340385 variables : properties . map ( property => {
386+ const displayValue = formatPropertyValue ( property ) ;
341387 let variablesReference : number ;
342- let displayValue : string ;
343388 if ( property . hasChildren || property . type === 'array' || property . type === 'object' ) {
344389 // if the property has children, we have to send a variableReference back to VS Code
345390 // so it can receive the child elements in another request.
@@ -350,26 +395,8 @@ class PhpDebugSession extends vscode.DebugSession {
350395 } else if ( property instanceof xdebug . EvalResultProperty ) {
351396 this . _evalResultProperties . set ( variablesReference , property ) ;
352397 }
353- // we show the type of the property ("array", "object") as the value
354- displayValue = property . type ;
355- if ( property . type === 'array' ) {
356- // show the length, like a var_dump would do
357- displayValue += '(' + property . numberOfChildren + ')' ;
358- }
359398 } else {
360399 variablesReference = 0 ;
361- if ( property . value ) {
362- displayValue = property . value ;
363- } else if ( property . type === 'uninitialized' || property . type === 'null' ) {
364- displayValue = property . type ;
365- } else {
366- displayValue = '' ;
367- }
368- if ( property . type === 'string' ) {
369- displayValue = '"' + displayValue + '"' ;
370- } else if ( property . type === 'bool' ) {
371- displayValue = ! ! parseInt ( displayValue ) + '' ;
372- }
373400 }
374401 return new vscode . Variable ( property . name , displayValue , variablesReference ) ;
375402 } )
@@ -383,31 +410,31 @@ class PhpDebugSession extends vscode.DebugSession {
383410 }
384411
385412 protected continueRequest ( response : VSCodeDebugProtocol . ContinueResponse , args : VSCodeDebugProtocol . ContinueArguments ) : void {
386- const connection = this . _connections . get ( args . threadId ) ;
413+ const connection = this . _connections . get ( args . threadId ) || this . _mainConnection ;
387414 connection . sendRunCommand ( )
388415 . then ( response => this . _checkStatus ( response ) )
389416 . catch ( error => this . sendErrorResponse ( response , error . code , error . message ) ) ;
390417 this . sendResponse ( response ) ;
391418 }
392419
393420 protected nextRequest ( response : VSCodeDebugProtocol . NextResponse , args : VSCodeDebugProtocol . NextArguments ) : void {
394- const connection = this . _connections . get ( args . threadId ) ;
421+ const connection = this . _connections . get ( args . threadId ) || this . _mainConnection ;
395422 connection . sendStepOverCommand ( )
396423 . then ( response => this . _checkStatus ( response ) )
397424 . catch ( error => this . sendErrorResponse ( response , error . code , error . message ) ) ;
398425 this . sendResponse ( response ) ;
399426 }
400427
401428 protected stepInRequest ( response : VSCodeDebugProtocol . StepInResponse , args : VSCodeDebugProtocol . StepInArguments ) : void {
402- const connection = this . _connections . get ( args . threadId ) ;
429+ const connection = this . _connections . get ( args . threadId ) || this . _mainConnection ;
403430 connection . sendStepIntoCommand ( )
404431 . then ( response => this . _checkStatus ( response ) )
405432 . catch ( error => this . sendErrorResponse ( response , error . code , error . message ) ) ;
406433 this . sendResponse ( response ) ;
407434 }
408435
409436 protected stepOutRequest ( response : VSCodeDebugProtocol . StepOutResponse , args : VSCodeDebugProtocol . StepOutArguments ) : void {
410- const connection = this . _connections . get ( args . threadId ) ;
437+ const connection = this . _connections . get ( args . threadId ) || this . _mainConnection ;
411438 connection . sendStepOutCommand ( )
412439 . then ( response => this . _checkStatus ( response ) )
413440 . catch ( error => this . sendErrorResponse ( response , error . code , error . message ) ) ;
@@ -428,6 +455,7 @@ class PhpDebugSession extends vscode.DebugSession {
428455 this . _mainConnection = null ;
429456 }
430457 } )
458+ . catch ( ( ) => { } )
431459 ) ) . then ( ( ) => {
432460 this . _server . close ( ( ) => {
433461 this . shutdown ( ) ;
@@ -439,18 +467,23 @@ class PhpDebugSession extends vscode.DebugSession {
439467 }
440468
441469 protected evaluateRequest ( response : VSCodeDebugProtocol . EvaluateResponse , args : VSCodeDebugProtocol . EvaluateArguments ) : void {
442- this . _stackFrames . get ( args . frameId ) . connection . sendEvalCommand ( args . expression )
470+ const connection = this . _stackFrames . has ( args . frameId ) ? this . _stackFrames . get ( args . frameId ) . connection : this . _mainConnection ;
471+ connection . sendEvalCommand ( args . expression )
443472 . then ( xdebugResponse => {
444- const value = xdebugResponse . result . value ;
445- let variablesReference : number ;
446- // if the property has children, generate a variable ID and save the property (including children) so VS Code can request them
447- if ( xdebugResponse . result . hasChildren ) {
448- variablesReference = this . _variableIdCounter ++ ;
449- this . _evalResultProperties . set ( variablesReference , xdebugResponse . result ) ;
473+ if ( xdebugResponse . result ) {
474+ const displayValue = formatPropertyValue ( xdebugResponse . result ) ;
475+ let variablesReference : number ;
476+ // if the property has children, generate a variable ID and save the property (including children) so VS Code can request them
477+ if ( xdebugResponse . result . hasChildren || xdebugResponse . result . type === 'array' || xdebugResponse . result . type === 'object' ) {
478+ variablesReference = this . _variableIdCounter ++ ;
479+ this . _evalResultProperties . set ( variablesReference , xdebugResponse . result ) ;
480+ } else {
481+ variablesReference = 0 ;
482+ }
483+ response . body = { result : displayValue , variablesReference} ;
450484 } else {
451- variablesReference = 0 ;
485+ response . body = { result : 'no result' , variablesReference : 0 } ;
452486 }
453- response . body = { result : value , variablesReference} ;
454487 this . sendResponse ( response ) ;
455488 } )
456489 . catch ( error => {
0 commit comments