@@ -25,6 +25,8 @@ const Repl = require('repl');
2525const util = require ( 'util' ) ;
2626const vm = require ( 'vm' ) ;
2727
28+ const debuglog = util . debuglog ( 'inspect' ) ;
29+
2830const NATIVES = process . binding ( 'natives' ) ;
2931
3032const SHORTCUTS = {
@@ -211,8 +213,15 @@ function createRepl(inspector) {
211213 return util . inspect ( value , INSPECT_OPTIONS ) ;
212214 }
213215
216+ function getCurrentLocation ( ) {
217+ if ( ! selectedFrame ) {
218+ throw new Error ( 'Requires execution to be paused' ) ;
219+ }
220+ return selectedFrame . location ;
221+ }
222+
214223 function isCurrentScript ( script ) {
215- return selectedFrame && selectedFrame . location . scriptId === script . scriptId ;
224+ return getCurrentLocation ( ) . scriptId === script . scriptId ;
216225 }
217226
218227 function formatScripts ( displayNatives = false ) {
@@ -274,17 +283,23 @@ function createRepl(inspector) {
274283 }
275284
276285 function controlEval ( input , context , filename , callback ) {
286+ debuglog ( 'eval:' , input ) ;
287+ function returnToCallback ( error , result ) {
288+ debuglog ( 'end-eval:' , input , error ) ;
289+ callback ( error , result ) ;
290+ }
291+
277292 try {
278293 const code = prepareControlCode ( input ) ;
279294 const result = vm . runInContext ( code , context , filename ) ;
280295
281296 if ( result && typeof result . then === 'function' ) {
282- toCallback ( result , callback ) ;
297+ toCallback ( result , returnToCallback ) ;
283298 return ;
284299 }
285- callback ( null , result ) ;
300+ returnToCallback ( null , result ) ;
286301 } catch ( e ) {
287- callback ( e ) ;
302+ returnToCallback ( e ) ;
288303 }
289304 }
290305
@@ -294,20 +309,24 @@ function createRepl(inspector) {
294309 return Promise . reject ( 'client.reqScopes not implemented' ) ;
295310 }
296311
297- const params = {
298- callFrameId : selectedFrame . callFrameId ,
312+ if ( selectedFrame ) {
313+ return Debugger . evaluateOnCallFrame ( {
314+ callFrameId : selectedFrame . callFrameId ,
315+ expression : code ,
316+ objectGroup : 'node-inspect' ,
317+ generatePreview : true ,
318+ } ) . then ( convertResultToRemoteObject ) ;
319+ }
320+ return Runtime . evaluate ( {
299321 expression : code ,
300322 objectGroup : 'node-inspect' ,
301323 generatePreview : true ,
302- } ;
303-
304- return Debugger . evaluateOnCallFrame ( params )
305- . then ( convertResultToRemoteObject ) ;
324+ } ) . then ( convertResultToRemoteObject ) ;
306325 }
307326
308- function watchers ( verbose = false ) {
327+ function formatWatchers ( verbose = false ) {
309328 if ( ! watchedExpressions . length ) {
310- return Promise . resolve ( ) ;
329+ return Promise . resolve ( '' ) ;
311330 }
312331
313332 const inspectValue = expr =>
@@ -317,38 +336,39 @@ function createRepl(inspector) {
317336
318337 return Promise . all ( watchedExpressions . map ( inspectValue ) )
319338 . then ( values => {
320- if ( verbose ) print ( 'Watchers:' ) ;
321-
322- watchedExpressions . forEach ( ( expr , idx ) => {
323- const prefix = `${ leftPad ( idx , ' ' , watchedExpressions . length - 1 ) } : ${ expr } =` ;
324- const value = inspect ( values [ idx ] , { colors : true } ) ;
325- if ( value . indexOf ( '\n' ) === - 1 ) {
326- print ( `${ prefix } ${ value } ` ) ;
327- } else {
328- print ( `${ prefix } \n ${ value . split ( '\n' ) . join ( '\n ' ) } ` ) ;
329- }
330- } ) ;
339+ const lines = watchedExpressions
340+ . map ( ( expr , idx ) => {
341+ const prefix = `${ leftPad ( idx , ' ' , watchedExpressions . length - 1 ) } : ${ expr } =` ;
342+ const value = inspect ( values [ idx ] , { colors : true } ) ;
343+ if ( value . indexOf ( '\n' ) === - 1 ) {
344+ return `${ prefix } ${ value } ` ;
345+ }
346+ return `${ prefix } \n ${ value . split ( '\n' ) . join ( '\n ' ) } ` ;
347+ } ) ;
348+ return lines . join ( '\n' ) ;
349+ } )
350+ . then ( ( valueList ) => ( verbose ? `Watchers:\n${ valueList } \n` : valueList ) ) ;
351+ }
331352
332- if ( verbose ) print ( '' ) ;
333- } ) ;
353+ function watchers ( verbose = false ) {
354+ return formatWatchers ( verbose ) . then ( print ) ;
334355 }
335356
336- // List source code
337- function list ( delta = 5 ) {
338- const { scriptId, lineNumber, columnNumber } = selectedFrame . location ;
357+ function formatSourceContext ( delta = 5 ) {
358+ const { scriptId, lineNumber, columnNumber } = getCurrentLocation ( ) ;
339359 const start = Math . max ( 1 , lineNumber - delta + 1 ) ;
340360 const end = lineNumber + delta + 1 ;
341361
342362 return Debugger . getScriptSource ( { scriptId } )
343363 . then ( ( { scriptSource } ) => {
344364 const lines = scriptSource . split ( '\n' ) ;
345- for ( let i = start ; i <= lines . length && i <= end ; ++ i ) {
365+ return lines . slice ( start - 1 , end ) . map ( ( lineText , offset ) => {
366+ const i = start + offset ;
346367 const isCurrent = i === ( lineNumber + 1 ) ;
347368
348- let lineText = lines [ i - 1 ] ;
349- if ( isCurrent ) {
350- lineText = markSourceColumn ( lineText , columnNumber , inspector . repl ) ;
351- }
369+ const markedLine = isCurrent
370+ ? markSourceColumn ( lineText , columnNumber , inspector . repl )
371+ : lineText ;
352372
353373 let isBreakpoint = false ;
354374 knownBreakpoints . forEach ( ( { location } ) => {
@@ -363,10 +383,15 @@ function createRepl(inspector) {
363383 } else if ( isBreakpoint ) {
364384 prefixChar = '*' ;
365385 }
366- print ( `${ leftPad ( i , prefixChar , end ) } ${ lineText } ` ) ;
367- }
368- } )
369- . then ( null , error => {
386+ return `${ leftPad ( i , prefixChar , end ) } ${ markedLine } ` ;
387+ } ) . join ( '\n' ) ;
388+ } ) ;
389+ }
390+
391+ // List source code
392+ function list ( delta = 5 ) {
393+ return formatSourceContext ( delta )
394+ . then ( print , ( error ) => {
370395 print ( 'You can\'t list source code right now' ) ;
371396 throw error ;
372397 } ) ;
@@ -392,9 +417,10 @@ function createRepl(inspector) {
392417 const scriptUrl = script ? script . url : location . scriptUrl ;
393418 return `${ getRelativePath ( scriptUrl ) } :${ location . lineNumber + 1 } ` ;
394419 }
395- knownBreakpoints . forEach ( ( bp , idx ) => {
396- print ( `#${ idx } ${ formatLocation ( bp . location ) } ` ) ;
397- } ) ;
420+ const breaklist = knownBreakpoints
421+ . map ( ( bp , idx ) => `#${ idx } ${ formatLocation ( bp . location ) } ` )
422+ . join ( '\n' ) ;
423+ print ( breaklist ) ;
398424 }
399425
400426 function setBreakpoint ( script , line , condition , silent ) {
@@ -410,17 +436,16 @@ function createRepl(inspector) {
410436
411437 // setBreakpoint(): set breakpoint at current location
412438 if ( script === undefined ) {
413- // TODO: assertIsPaused()
414- return Debugger . setBreakpoint ( { location : selectedFrame . location , condition } )
439+ return Debugger . setBreakpoint ( { location : getCurrentLocation ( ) , condition } )
415440 . then ( registerBreakpoint ) ;
416441 }
417442
418443 // setBreakpoint(line): set breakpoint in current script at specific line
419444 if ( line === undefined && typeof script === 'number' ) {
420- // TODO: assertIsPaused()
421- const location = Object . assign ( { } , selectedFrame . location , {
445+ const location = {
446+ scriptId : getCurrentLocation ( ) . scriptId ,
422447 lineNumber : script - 1 ,
423- } ) ;
448+ } ;
424449 return Debugger . setBreakpoint ( { location, condition } )
425450 . then ( registerBreakpoint ) ;
426451 }
@@ -431,12 +456,18 @@ function createRepl(inspector) {
431456
432457 // setBreakpoint('fn()'): Break when a function is called
433458 if ( script . endsWith ( '()' ) ) {
434- // TODO: handle !currentFrame (~Runtime.evaluate)
435- return Debugger . evaluateOnCallFrame ( {
436- callFrameId : selectedFrame . callFrameId ,
437- expression : `debug(${ script . slice ( 0 , - 2 ) } )` ,
438- includeCommandLineAPI : true ,
439- } ) . then ( ( { result, wasThrown } ) => {
459+ const debugExpr = `debug(${ script . slice ( 0 , - 2 ) } )` ;
460+ const debugCall = selectedFrame
461+ ? Debugger . evaluateOnCallFrame ( {
462+ callFrameId : selectedFrame . callFrameId ,
463+ expression : debugExpr ,
464+ includeCommandLineAPI : true ,
465+ } )
466+ : Runtime . evaluate ( {
467+ expression : debugExpr ,
468+ includeCommandLineAPI : true ,
469+ } ) ;
470+ return debugCall . then ( ( { result, wasThrown } ) => {
440471 if ( wasThrown ) return convertResultToError ( result ) ;
441472 return undefined ; // This breakpoint can't be removed the same way
442473 } ) ;
@@ -517,10 +548,18 @@ function createRepl(inspector) {
517548 print ( `${ reason === 'other' ? 'break' : reason } in ${ scriptUrl } :${ lineNumber + 1 } ` ) ;
518549
519550 inspector . suspendReplWhile ( ( ) =>
520- watchers ( true )
521- . then ( ( ) => list ( 2 ) ) ) ;
551+ Promise . all ( [ formatWatchers ( true ) , formatSourceContext ( 2 ) ] )
552+ . then ( ( [ watcherList , context ] ) => ( watcherList ? `${ watcherList } \n${ context } ` : context ) )
553+ . then ( print ) ) ;
522554 } ) ;
523555
556+ function handleResumed ( ) {
557+ currentBacktrace = null ;
558+ selectedFrame = null ;
559+ }
560+
561+ Debugger . on ( 'resumed' , handleResumed ) ;
562+
524563 Debugger . on ( 'breakpointResolved' , handleBreakpointResolved ) ;
525564
526565 Debugger . on ( 'scriptParsed' , ( script ) => {
@@ -535,18 +574,22 @@ function createRepl(inspector) {
535574 function initializeContext ( context ) {
536575 copyOwnProperties ( context , {
537576 get cont ( ) {
577+ handleResumed ( ) ;
538578 return Debugger . resume ( ) ;
539579 } ,
540580
541581 get next ( ) {
582+ handleResumed ( ) ;
542583 return Debugger . stepOver ( ) ;
543584 } ,
544585
545586 get step ( ) {
587+ handleResumed ( ) ;
546588 return Debugger . stepInto ( ) ;
547589 } ,
548590
549591 get out ( ) {
592+ handleResumed ( ) ;
550593 return Debugger . stepOut ( ) ;
551594 } ,
552595
@@ -583,6 +626,57 @@ function createRepl(inspector) {
583626 watchedExpressions . splice ( index !== - 1 ? index : + expr , 1 ) ;
584627 } ,
585628
629+ get repl ( ) {
630+ // if (!this.requireConnection()) return;
631+ print ( 'Press Ctrl + C to leave debug repl' ) ;
632+
633+ // Don't display any default messages
634+ const listeners = repl . rli . listeners ( 'SIGINT' ) . slice ( 0 ) ;
635+ repl . rli . removeAllListeners ( 'SIGINT' ) ;
636+
637+ function exitDebugRepl ( ) {
638+ // Restore all listeners
639+ process . nextTick ( ( ) => {
640+ listeners . forEach ( ( listener ) => {
641+ repl . rli . on ( 'SIGINT' , listener ) ;
642+ } ) ;
643+ } ) ;
644+
645+ // Exit debug repl
646+ repl . eval = controlEval ;
647+
648+ // Swap history
649+ history . debug = repl . rli . history ;
650+ repl . rli . history = history . control ;
651+
652+ repl . context = inspector . context ;
653+ repl . rli . setPrompt ( 'debug> ' ) ;
654+ repl . displayPrompt ( ) ;
655+
656+ repl . rli . removeListener ( 'SIGINT' , exitDebugRepl ) ;
657+ repl . removeListener ( 'exit' , exitDebugRepl ) ;
658+ }
659+
660+ // Exit debug repl on SIGINT
661+ repl . rli . on ( 'SIGINT' , exitDebugRepl ) ;
662+
663+ // Exit debug repl on repl exit
664+ repl . on ( 'exit' , exitDebugRepl ) ;
665+
666+ // Set new
667+ repl . eval = ( code , evalCtx , file , cb ) => {
668+ toCallback ( debugEval ( code ) , cb ) ;
669+ } ;
670+ repl . context = { } ;
671+
672+ // Swap history
673+ history . control = repl . rli . history ;
674+ repl . rli . history = history . debug ;
675+
676+ repl . rli . setPrompt ( '> ' ) ;
677+ repl . displayPrompt ( ) ;
678+ } ,
679+
586680 get version ( ) {
587681 return Runtime . evaluate ( {
588682 expression : 'process.versions.v8' ,
0 commit comments