123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- 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 {
- /**
- * Instantiate a new DebuggerService.
- *
- * @param options The instantiation options for a DebuggerService.
- */
- constructor(options: DebuggerService.IOptions) {
- this._config = options.config;
- // Avoids setting session with invalid client
- // session should be set only when a notebook or
- // a console get the focus.
- // TODO: also checks that the notebook or console
- // runs a kernel with debugging ability
- 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;
- }
- /**
- * Sets the current debug session to the given parameter.
- *
- * @param session - the new debugger session.
- */
- set session(session: IDebugger.ISession) {
- 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(): void {
- if (this.isDisposed) {
- return;
- }
- this._isDisposed = true;
- Signal.clearData(this);
- }
- /**
- * Computes an id based on the given code.
- *
- * @param code The source code.
- */
- getCodeId(code: string): string {
- try {
- return this._config.getCodeId(
- code,
- this.session?.connection?.kernel?.name
- );
- } catch {
- return '';
- }
- }
- /**
- * Whether there exists a thread in stopped state.
- */
- hasStoppedThreads(): boolean {
- return this._model?.stoppedThreads.size > 0 ?? false;
- }
- /**
- * Request whether debugging is available for the session connection.
- *
- * @param connection The session connection.
- */
- async isAvailable(connection: Session.ISessionConnection): Promise<boolean> {
- 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(): Promise<void> {
- 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(): Promise<void> {
- try {
- await this.session.sendRequest('continue', {
- threadId: this._currentThread()
- });
- this._model.stoppedThreads.delete(this._currentThread());
- } catch (err) {
- console.error('Error:', err.message);
- }
- }
- /**
- * Retrieve the content of a source file.
- *
- * @param source The source object containing the path to the file.
- */
- async getSource(source: DebugProtocol.Source): Promise<IDebugger.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(): Promise<void> {
- try {
- await this.session.sendRequest('next', {
- threadId: this._currentThread()
- });
- } catch (err) {
- console.error('Error:', err.message);
- }
- }
- /**
- * Request variables for a given variable reference.
- *
- * @param variablesReference The variable reference to request.
- */
- async inspectVariable(
- variablesReference: number
- ): Promise<DebugProtocol.Variable[]> {
- const reply = await this.session.sendRequest('variables', {
- variablesReference
- });
- return reply.body.variables;
- }
- /**
- * Restart the debugger.
- */
- async restart(): Promise<void> {
- const { breakpoints } = this._model.breakpoints;
- await this.stop();
- await this.start();
- // Re-send the breakpoints to the kernel and update the model.
- for (const [source, points] of breakpoints) {
- await this._setBreakpoints(
- points.map(({ line }) => ({ line })),
- source
- );
- }
- this._model.breakpoints.restoreBreakpoints(breakpoints);
- }
- /**
- * Restore the state of a debug session.
- *
- * @param autoStart - If true, starts the debugger if it has not been started.
- */
- async restoreState(autoStart: boolean): Promise<void> {
- 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(): Promise<void> {
- return this.session.start();
- }
- /**
- * Makes the current thread step in a function / method if possible.
- */
- async stepIn(): Promise<void> {
- 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(): Promise<void> {
- try {
- await this.session.sendRequest('stepOut', {
- threadId: this._currentThread()
- });
- } catch (err) {
- console.error('Error:', err.message);
- }
- }
- /**
- * Stops the debugger.
- * Precondition: isStarted
- */
- async stop(): Promise<void> {
- await this.session.stop();
- if (this._model) {
- this._model.clear();
- }
- }
- /**
- * Update all breakpoints at once.
- *
- * @param code - The code in the cell where the breakpoints are set.
- * @param breakpoints - The list of breakpoints to set.
- * @param path - Optional path to the file where to set the breakpoints.
- */
- async updateBreakpoints(
- code: string,
- breakpoints: IDebugger.IBreakpoint[],
- path?: string
- ): Promise<void> {
- 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);
- // Set the local copy of breakpoints to reflect only editors that exist.
- if (this._debuggerSources) {
- const filtered = this._filterBreakpoints(remoteBreakpoints);
- this._model.breakpoints.restoreBreakpoints(filtered);
- } else {
- this._model.breakpoints.restoreBreakpoints(remoteBreakpoints);
- }
- // Set the kernel's breakpoints for this path.
- const reply = await this._setBreakpoints(localBreakpoints, path);
- const updatedBreakpoints = reply.body.breakpoints.filter(
- (val, _, arr) => arr.findIndex(el => el.line === val.line) > -1
- );
- // Update the local model and finish kernel configuration.
- this._model.breakpoints.setBreakpoints(path, updatedBreakpoints);
- await this.session.sendRequest('configurationDone', {});
- }
- /**
- * Clear the current model.
- */
- private _clearModel(): void {
- this._model.callstack.frames = [];
- this._model.variables.scopes = [];
- }
- /**
- * Clear the signals set on the model.
- */
- private _clearSignals(): void {
- this._model.callstack.currentFrameChanged.disconnect(
- this._onCurrentFrameChanged,
- this
- );
- this._model.variables.variableExpanded.disconnect(
- this._onVariableExpanded,
- this
- );
- }
- /**
- * Map a list of scopes to a list of variables.
- *
- * @param scopes The list of scopes.
- * @param variables The list of variables.
- */
- private _convertScopes(
- scopes: DebugProtocol.Scope[],
- variables: DebugProtocol.Variable[]
- ): IDebugger.IScope[] {
- if (!variables || !scopes) {
- return;
- }
- return scopes.map(scope => {
- return {
- name: scope.name,
- variables: variables.map(variable => {
- return { ...variable };
- })
- };
- });
- }
- /**
- * Get the current thread from the model.
- */
- private _currentThread(): number {
- // TODO: ask the model for the current thread ID
- return 1;
- }
- /**
- * Dump the content of a cell.
- *
- * @param code The source code to dump.
- */
- private async _dumpCell(
- code: string
- ): Promise<IDebugger.ISession.IDumpCellResponse> {
- return this.session.sendRequest('dumpCell', { code });
- }
- /**
- * Filter breakpoints and only return those associated with a known editor.
- *
- * @param breakpoints - Map of breakpoints.
- *
- */
- private _filterBreakpoints(
- breakpoints: Map<string, IDebugger.IBreakpoint[]>
- ): Map<string, IDebugger.IBreakpoint[]> {
- 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(): Promise<void> {
- 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;
- }
- /**
- * Get all the frames for the given thread id.
- *
- * @param threadId The thread id.
- */
- private async _getFrames(
- threadId: number
- ): Promise<DebugProtocol.StackFrame[]> {
- const reply = await this.session.sendRequest('stackTrace', {
- threadId
- });
- const stackFrames = reply.body.stackFrames;
- return stackFrames;
- }
- /**
- * Get all the scopes for the given frame.
- *
- * @param frame The frame.
- */
- private async _getScopes(
- frame: DebugProtocol.StackFrame
- ): Promise<DebugProtocol.Scope[]> {
- if (!frame) {
- return;
- }
- const reply = await this.session.sendRequest('scopes', {
- frameId: frame.id
- });
- return reply.body.scopes;
- }
- /**
- * Get the variables for a given scope.
- *
- * @param scope The scope to get variables for.
- */
- private async _getVariables(
- scope: DebugProtocol.Scope
- ): Promise<DebugProtocol.Variable[]> {
- if (!scope) {
- return;
- }
- const reply = await this.session.sendRequest('variables', {
- variablesReference: scope.variablesReference
- });
- return reply.body.variables;
- }
- /**
- * Process the list of breakpoints from the server and return as a map.
- *
- * @param breakpoints - The list of breakpoints from the kernel.
- *
- */
- private _mapBreakpoints(
- breakpoints: IDebugger.ISession.IDebugInfoBreakpoints[]
- ): Map<string, IDebugger.IBreakpoint[]> {
- if (!breakpoints.length) {
- return new Map<string, IDebugger.IBreakpoint[]>();
- }
- return breakpoints.reduce(
- (
- map: Map<string, IDebugger.IBreakpoint[]>,
- val: IDebugger.ISession.IDebugInfoBreakpoints
- ) => {
- const { breakpoints, source } = val;
- map.set(
- source,
- breakpoints.map(point => ({ ...point, verified: true }))
- );
- return map;
- },
- new Map<string, IDebugger.IBreakpoint[]>()
- );
- }
- /**
- * Handle a change of the current active frame.
- *
- * @param _ The callstack model
- * @param frame The frame.
- */
- private async _onCurrentFrameChanged(
- _: IDebugger.Model.ICallstack,
- frame: IDebugger.IStackFrame
- ): Promise<void> {
- if (!frame) {
- return;
- }
- const scopes = await this._getScopes(frame);
- const variables = await this._getVariables(scopes[0]);
- const variableScopes = this._convertScopes(scopes, variables);
- this._model.variables.scopes = variableScopes;
- }
- /**
- * Handle a variable expanded event and request variables from the kernel.
- *
- * @param _ The variables model.
- * @param variable The expanded variable.
- */
- private async _onVariableExpanded(
- _: VariablesModel,
- variable: DebugProtocol.Variable
- ): Promise<DebugProtocol.Variable[]> {
- const reply = await this.session.sendRequest('variables', {
- variablesReference: variable.variablesReference
- });
- let newVariable = { ...variable, expanded: true };
- reply.body.variables.forEach((variable: DebugProtocol.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;
- }
- /**
- * Set the breakpoints for a given file.
- *
- * @param breakpoints The list of breakpoints to set.
- * @param path The path to where to set the breakpoints.
- */
- private async _setBreakpoints(
- breakpoints: DebugProtocol.SourceBreakpoint[],
- path: string
- ): Promise<DebugProtocol.SetBreakpointsResponse> {
- 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;
- }
- }
|