import { Session , KernelSpec } from '@jupyterlab/services' ; import { IDisposable } from '@lumino/disposable' ; import { ISignal , Signal } from '@lumino/signaling' ; import { DebugProtocol } from 'vscode-debugprotocol' ; import { Debugger } from './debugger' ; import { VariablesModel } from './panels/variables/model' ; import { IDebugger } from './tokens' ; /**
 * A concrete implementation of the IDebugger interface.
 */ export class DebuggerService implements IDebugger , IDisposable { param options constructor ( options ) { this . _config = options . config ; this . _session = null ; this . _specsManager = options . specsManager ; this . _model = new Debugger . Model ( ) ; this . _debuggerSources = options . debuggerSources ; } /**
     * Signal emitted for debug event messages.
     */ get eventMessage ( ) : ISignal < IDebugger , IDebugger . ISession . Event > { return this . _eventMessage ; } /**
     * Whether the debug service is disposed.
     */ get isDisposed ( ) : boolean { return this . _isDisposed ; } /**
     * Whether the current debugger is started.
     */ get isStarted ( ) : boolean { return this . _session ?. isStarted ?? false ; } /**
     * Returns the debugger service's model.
     */ get model ( ) : IDebugger . Model . IService { return this . _model ; } /**
     * Returns the current debug session.
     */ get session ( ) : IDebugger . ISession { return this . _session ; } param session set session ( session ) { if ( this . _session === session ) { return ; } if ( this . _session ) { this . _session . dispose ( ) ; } this . _session = session ; this . _session ?. eventMessage . connect ( ( _ , event ) => { if ( event . event === 'stopped' ) { this . _model . stoppedThreads . add ( event . body . threadId ) ; void this . _getAllFrames ( ) ; } else if ( event . event === 'continued' ) { this . _model . stoppedThreads . delete ( event . body . threadId ) ; this . _clearModel ( ) ; this . _clearSignals ( ) ; } this . _eventMessage . emit ( event ) ; } ) ; this . _sessionChanged . emit ( session ) ; } /**
     * Signal emitted upon session changed.
     */ get sessionChanged ( ) : ISignal < IDebugger , IDebugger . ISession > { return this . _sessionChanged ; } /**
     * Dispose the debug service.
     */ dispose ( ) { if ( this . isDisposed ) { return ; } this . _isDisposed = true ; Signal . clearData ( this ) ; } param code getCodeId ( code ) { try { return this . _config . getCodeId ( code , this . session ?. connection ?. kernel ?. name ) ; } catch { return '' ; } } /**
     * Whether there exists a thread in stopped state.
     */ hasStoppedThreads ( ) { return this . _model ?. stoppedThreads . size > 0 ?? false ; } param connection async isAvailable ( connection ) { if ( ! this . _specsManager ) { return true ; } await this . _specsManager . ready ; const kernel = connection ?. kernel ; if ( ! kernel ) { return false ; } const name = kernel . name ; if ( ! this . _specsManager . specs . kernelspecs [ name ] ) { return true ; } return ! ! ( this . _specsManager . specs . kernelspecs [ name ] . metadata ?. [ 'debugger' ] ?? false ) ; } /**
     * Clear all the breakpoints for the current session.
     */ async clearBreakpoints ( ) { if ( ! this . session . isStarted ) { return ; } this . _model . breakpoints . breakpoints . forEach ( async ( breakpoints , path , _ ) => { await this . _setBreakpoints ( [ ] , path ) ; } ) ; let bpMap = new Map < string , IDebugger . IBreakpoint [ ] > ( ) ; this . _model . breakpoints . restoreBreakpoints ( bpMap ) ; } /**
     * Continues the execution of the current thread.
     */ async continue ( ) { try { await this . session . sendRequest ( 'continue' , { threadId : this . _currentThread ( ) } ) ; this . _model . stoppedThreads . delete ( this . _currentThread ( ) ) ; this . _clearModel ( ) ; this . _clearSignals ( ) ; } catch ( err ) { console . error ( 'Error:' , err . message ) ; } } param source async getSource ( source ) { const reply = await this . session . sendRequest ( 'source' , { source , sourceReference : source . sourceReference } ) ; return { ... reply . body , path : source . path } ; } /**
     * Makes the current thread run again for one step.
     */ async next ( ) { try { await this . session . sendRequest ( 'next' , { threadId : this . _currentThread ( ) } ) ; } catch ( err ) { console . error ( 'Error:' , err . message ) ; } } param variablesReference async inspectVariable ( variablesReference ) { const reply = await this . session . sendRequest ( 'variables' , { variablesReference } ) ; return reply . body . variables ; } /**
     * Restart the debugger.
     */ async restart ( ) { const { breakpoints } = this . _model . breakpoints ; await this . stop ( ) ; await this . start ( ) ; for ( const [ source , points ] of breakpoints ) { await this . _setBreakpoints ( points . map ( ( { line } ) => ( { line } ) ) , source ) ; } this . _model . breakpoints . restoreBreakpoints ( breakpoints ) ; } param autoStart async restoreState ( autoStart ) { if ( ! this . model || ! this . session ) { return ; } const reply = await this . session . restoreState ( ) ; const { body } = reply ; const breakpoints = this . _mapBreakpoints ( reply . body . breakpoints ) ; const stoppedThreads = new Set ( reply . body . stoppedThreads ) ; this . _config . setHashParams ( { kernel : this . session . connection . kernel . name , method : body . hashMethod , seed : body . hashSeed } ) ; this . _config . setTmpFileParams ( { kernel : this . session . connection . kernel . name , prefix : body . tmpFilePrefix , suffix : body . tmpFileSuffix } ) ; this . _model . stoppedThreads = stoppedThreads ; if ( ! this . isStarted && ( autoStart || stoppedThreads . size !== 0 ) ) { await this . start ( ) ; } if ( this . isStarted || autoStart ) { this . _model . title = this . isStarted ? this . session ?. connection ?. name : '-' ; } if ( this . _debuggerSources ) { const filtered = this . _filterBreakpoints ( breakpoints ) ; this . _model . breakpoints . restoreBreakpoints ( filtered ) ; } else { this . _model . breakpoints . restoreBreakpoints ( breakpoints ) ; } if ( stoppedThreads . size !== 0 ) { await this . _getAllFrames ( ) ; } else if ( this . isStarted ) { this . _clearModel ( ) ; this . _clearSignals ( ) ; } } /**
     * Starts a debugger.
     * Precondition: !isStarted
     */ start ( ) { return this . session . start ( ) ; } /**
     * Makes the current thread step in a function / method if possible.
     */ async stepIn ( ) { try { await this . session . sendRequest ( 'stepIn' , { threadId : this . _currentThread ( ) } ) ; } catch ( err ) { console . error ( 'Error:' , err . message ) ; } } /**
     * Makes the current thread step out a function / method if possible.
     */ async stepOut ( ) { try { await this . session . sendRequest ( 'stepOut' , { threadId : this . _currentThread ( ) } ) ; } catch ( err ) { console . error ( 'Error:' , err . message ) ; } } /**
     * Stops the debugger.
     * Precondition: isStarted
     */ async stop ( ) { await this . session . stop ( ) ; if ( this . _model ) { this . _model . clear ( ) ; } } param code param breakpoints param path async updateBreakpoints ( code , breakpoints , path ? ) { if ( ! this . session . isStarted ) { return ; } if ( ! path ) { path = ( await this . _dumpCell ( code ) ) . body . sourcePath ; } const state = await this . session . restoreState ( ) ; const localBreakpoints = breakpoints . map ( ( { line } ) => ( { line } ) ) ; const remoteBreakpoints = this . _mapBreakpoints ( state . body . breakpoints ) ; if ( this . _debuggerSources ) { const filtered = this . _filterBreakpoints ( remoteBreakpoints ) ; this . _model . breakpoints . restoreBreakpoints ( filtered ) ; } else { this . _model . breakpoints . restoreBreakpoints ( remoteBreakpoints ) ; } const reply = await this . _setBreakpoints ( localBreakpoints , path ) ; const updatedBreakpoints = reply . body . breakpoints . filter ( ( val , _ , arr ) => arr . findIndex ( el => el . line === val . line ) > - 1 ) ; this . _model . breakpoints . setBreakpoints ( path , updatedBreakpoints ) ; await this . session . sendRequest ( 'configurationDone' , { } ) ; } /**
     * Clear the current model.
     */ private _clearModel ( ) { this . _model . callstack . frames = [ ] ; this . _model . variables . scopes = [ ] ; } /**
     * Clear the signals set on the model.
     */ private _clearSignals ( ) { this . _model . callstack . currentFrameChanged . disconnect ( this . _onCurrentFrameChanged , this ) ; this . _model . variables . variableExpanded . disconnect ( this . _onVariableExpanded , this ) ; } param scopes param variables private _convertScopes ( scopes , variables ) { if ( ! variables || ! scopes ) { return ; } return scopes . map ( ( scope , i ) => { return { name : scope . name , variables : variables [ i ] . map ( variable => { return { ... variable } ; } ) } ; } ) ; } /**
     * Get the current thread from the model.
     */ private _currentThread ( ) { return 1 ; } param code private async _dumpCell ( code ) { return this . session . sendRequest ( 'dumpCell' , { code } ) ; } param breakpoints private _filterBreakpoints ( breakpoints ) { let bpMapForRestore = new Map < string , IDebugger . IBreakpoint [ ] > ( ) ; for ( const collection of breakpoints ) { const [ id , list ] = collection ; list . forEach ( ( ) => { this . _debuggerSources . find ( { focus : false , kernel : this . session . connection . kernel . name , path : this . _session . connection . path , source : id } ) . forEach ( ( ) => { if ( list . length > 0 ) { bpMapForRestore . set ( id , list ) ; } } ) ; } ) ; } return bpMapForRestore ; } /**
     * Get all the frames from the kernel.
     */ private async _getAllFrames ( ) { this . _model . callstack . currentFrameChanged . connect ( this . _onCurrentFrameChanged , this ) ; this . _model . variables . variableExpanded . connect ( this . _onVariableExpanded , this ) ; const stackFrames = await this . _getFrames ( this . _currentThread ( ) ) ; this . _model . callstack . frames = stackFrames ; } param threadId private async _getFrames ( threadId ) { const reply = await this . session . sendRequest ( 'stackTrace' , { threadId } ) ; const stackFrames = reply . body . stackFrames ; return stackFrames ; } param frame private async _getScopes ( frame ) { if ( ! frame ) { return ; } const reply = await this . session . sendRequest ( 'scopes' , { frameId : frame . id } ) ; return reply . body . scopes ; } param scope private async _getVariables ( scope ) { if ( ! scope ) { return ; } const reply = await this . session . sendRequest ( 'variables' , { variablesReference : scope . variablesReference } ) ; return reply . body . variables ; } param breakpoints private _mapBreakpoints ( breakpoints ) { if ( ! breakpoints . length ) { return new Map < string , IDebugger . IBreakpoint [ ] > ( ) ; } return breakpoints . reduce ( ( map , val ) => { const { breakpoints , source } = val ; map . set ( source , breakpoints . map ( point => ( { ... point , source : { path : source } , verified : true } ) ) ) ; return map ; } , new Map < string , IDebugger . IBreakpoint [ ] > ( ) ) ; } param _ param frame private async _onCurrentFrameChanged ( _ , frame ) { if ( ! frame ) { return ; } const scopes = await this . _getScopes ( frame ) ; const variables = await Promise . all ( scopes . map ( scope => this . _getVariables ( scope ) ) ) ; const variableScopes = this . _convertScopes ( scopes , variables ) ; this . _model . variables . scopes = variableScopes ; } param _ param variable private async _onVariableExpanded ( _ , variable ) { const reply = await this . session . sendRequest ( 'variables' , { variablesReference : variable . variablesReference } ) ; let newVariable = { ... variable , expanded : true } ; reply . body . variables . forEach ( ( variable ) => { newVariable = { [ variable . name ] : variable , ... newVariable } ; } ) ; const newScopes = this . _model . variables . scopes . map ( scope => { const findIndex = scope . variables . findIndex ( ele => ele . variablesReference === variable . variablesReference ) ; scope . variables [ findIndex ] = newVariable ; return { ... scope } ; } ) ; this . _model . variables . scopes = [ ... newScopes ] ; return reply . body . variables ; } param breakpoints param path private async _setBreakpoints ( breakpoints , path ) { return await this . session . sendRequest ( 'setBreakpoints' , { breakpoints : breakpoints , source : { path } , sourceModified : false } ) ; } private _config : IDebugger . IConfig ; private _debuggerSources : IDebugger . ISources | null ; private _eventMessage = new Signal < IDebugger , IDebugger . ISession . Event > ( this ) ; private _isDisposed = false ; private _model : IDebugger . Model . IService ; private _session : IDebugger . ISession ; private _sessionChanged = new Signal < IDebugger , IDebugger . ISession > ( this ) ; private _specsManager : KernelSpec . IManager ; } /**
 * A namespace for `DebuggerService` statics.
 */ export namespace DebuggerService { /**
     * Instantiation options for a `DebuggerService`.
     */ export interface IOptions { /**
         * The configuration instance with hash method.
         */ config : IDebugger . IConfig ; /**
         * The optional debugger sources instance.
         */ debuggerSources ? : IDebugger . ISources ; /**
         * The optional kernel specs manager.
         */ specsManager ? : KernelSpec . IManager ; } }