diff --git a/src/breakpoints/body.tsx b/src/breakpoints/body.tsx index 1d0b94d4..668ae176 100644 --- a/src/breakpoints/body.tsx +++ b/src/breakpoints/body.tsx @@ -5,19 +5,19 @@ import { ReactWidget } from '@jupyterlab/apputils'; import React, { useEffect, useState } from 'react'; -import { Breakpoints } from '.'; - import { IDebugger } from '../tokens'; +import { BreakpointsModel } from './model'; + /** * The body for a Breakpoints Panel. */ -export class Body extends ReactWidget { +export class BreakpointsBody extends ReactWidget { /** * Instantiate a new Body for the Breakpoints Panel. * @param model The model for the breakpoints. */ - constructor(model: Breakpoints.Model) { + constructor(model: BreakpointsModel) { super(); this._model = model; this.addClass('jp-DebuggerBreakpoints-body'); @@ -30,27 +30,27 @@ export class Body extends ReactWidget { return ; } - private _model: Breakpoints.Model; + private _model: BreakpointsModel; } /** * A React component to display a list of breakpoints. * @param model The model for the breakpoints. */ -const BreakpointsComponent = ({ model }: { model: Breakpoints.Model }) => { +const BreakpointsComponent = ({ model }: { model: BreakpointsModel }) => { const [breakpoints, setBreakpoints] = useState( Array.from(model.breakpoints.entries()) ); useEffect(() => { const updateBreakpoints = ( - _: Breakpoints.Model, + _: BreakpointsModel, updates: IDebugger.IBreakpoint[] ) => { setBreakpoints(Array.from(model.breakpoints.entries())); }; - const restoreBreakpoints = (_: Breakpoints.Model) => { + const restoreBreakpoints = (_: BreakpointsModel) => { setBreakpoints(Array.from(model.breakpoints.entries())); }; @@ -86,7 +86,7 @@ const BreakpointCellComponent = ({ model }: { breakpoints: IDebugger.IBreakpoint[]; - model: Breakpoints.Model; + model: BreakpointsModel; }) => { return ( <> @@ -115,7 +115,7 @@ const BreakpointComponent = ({ model }: { breakpoint: IDebugger.IBreakpoint; - model: Breakpoints.Model; + model: BreakpointsModel; }) => { return (
{ - return this._changed; - } - - /** - * Signal emitted when the breakpoints are restored. - */ - get restored(): Signal { - return this._restored; - } - - /** - * Signal emitted when a breakpoint is clicked. - */ - get clicked(): Signal { - return this._clicked; - } - - /** - * Get all the breakpoints. - */ - get breakpoints(): Map { - return this._breakpoints; - } - - /** - * Dispose the model. - */ - dispose(): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - Signal.clearData(this); - } - - /** - * Set the breakpoints for a given id (path). - * @param id The code id (path). - * @param breakpoints The list of breakpoints. - */ - setBreakpoints(id: string, breakpoints: IDebugger.IBreakpoint[]) { - this._breakpoints.set(id, breakpoints); - this._changed.emit(breakpoints); - } - - /** - * Get the breakpoints for a given id (path). - * @param id The code id (path). - */ - getBreakpoints(id: string): IDebugger.IBreakpoint[] { - return this._breakpoints.get(id) ?? []; - } - - /** - * Restore a map of breakpoints. - * @param breakpoints The map of breakpoints - */ - restoreBreakpoints(breakpoints: Map) { - this._breakpoints = breakpoints; - this._restored.emit(); - } - - private _isDisposed = false; - private _breakpoints = new Map(); - private _changed = new Signal(this); - private _restored = new Signal(this); - private _clicked = new Signal(this); - } - /** * Instantiation options for `Breakpoints`. */ export interface IOptions extends Panel.IOptions { /** - * The debugger model. + * The breakpoints model. */ - model: Model; + model: BreakpointsModel; /** * The debugger service. diff --git a/src/breakpoints/model.ts b/src/breakpoints/model.ts new file mode 100644 index 00000000..179ebc7a --- /dev/null +++ b/src/breakpoints/model.ts @@ -0,0 +1,92 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { IDisposable } from '@phosphor/disposable'; + +import { ISignal, Signal } from '@phosphor/signaling'; + +import { IDebugger } from '../tokens'; + +/** + * A model for a list of breakpoints. + */ +export class BreakpointsModel implements IDisposable { + /** + * Whether the model is disposed. + */ + get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * Signal emitted when the model changes. + */ + get changed(): ISignal { + return this._changed; + } + + /** + * Signal emitted when the breakpoints are restored. + */ + get restored(): Signal { + return this._restored; + } + + /** + * Signal emitted when a breakpoint is clicked. + */ + get clicked(): Signal { + return this._clicked; + } + + /** + * Get all the breakpoints. + */ + get breakpoints(): Map { + return this._breakpoints; + } + + /** + * Dispose the model. + */ + dispose(): void { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + Signal.clearData(this); + } + + /** + * Set the breakpoints for a given id (path). + * @param id The code id (path). + * @param breakpoints The list of breakpoints. + */ + setBreakpoints(id: string, breakpoints: IDebugger.IBreakpoint[]) { + this._breakpoints.set(id, breakpoints); + this._changed.emit(breakpoints); + } + + /** + * Get the breakpoints for a given id (path). + * @param id The code id (path). + */ + getBreakpoints(id: string): IDebugger.IBreakpoint[] { + return this._breakpoints.get(id) ?? []; + } + + /** + * Restore a map of breakpoints. + * @param breakpoints The map of breakpoints + */ + restoreBreakpoints(breakpoints: Map) { + this._breakpoints = breakpoints; + this._restored.emit(); + } + + private _isDisposed = false; + private _breakpoints = new Map(); + private _changed = new Signal(this); + private _restored = new Signal(this); + private _clicked = new Signal(this); +} diff --git a/src/callstack/body.tsx b/src/callstack/body.tsx index 63347225..0d98b8b7 100644 --- a/src/callstack/body.tsx +++ b/src/callstack/body.tsx @@ -1,27 +1,41 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import React, { useEffect, useState } from 'react'; +import { ReactWidget } from '@jupyterlab/apputils'; -import { Callstack } from '.'; +import React, { useEffect, useState } from 'react'; -import { ReactWidget } from '@jupyterlab/apputils'; +import { CallstackModel } from './model'; -export class Body extends ReactWidget { - constructor(model: Callstack.Model) { +/** + * The body for a Callstack Panel. + */ +export class CallstackBody extends ReactWidget { + /** + * Instantiate a new Body for the Callstack Panel. + * @param model The model for the callstack. + */ + constructor(model: CallstackModel) { super(); - this.model = model; + this._model = model; this.addClass('jp-DebuggerCallstack-body'); } + /** + * Render the FramesComponent. + */ render() { - return ; + return ; } - readonly model: Callstack.Model; + private _model: CallstackModel; } -const FramesComponent = ({ model }: { model: Callstack.Model }) => { +/** + * A React component to display a list of frames in a callstack. + * @param model The model for the callstack. + */ +const FramesComponent = ({ model }: { model: CallstackModel }) => { const [frames, setFrames] = useState(model.frames); const [selected, setSelected] = useState(model.frame); diff --git a/src/callstack/header.ts b/src/callstack/header.ts new file mode 100644 index 00000000..c47da503 --- /dev/null +++ b/src/callstack/header.ts @@ -0,0 +1,31 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { Toolbar } from '@jupyterlab/apputils'; + +import { PanelLayout, Widget } from '@phosphor/widgets'; + +/** + * The header for a Callstack Panel. + */ +export class CallstackHeader extends Widget { + /** + * Instantiate a new CallstackHeader. + */ + constructor() { + super({ node: document.createElement('header') }); + + const title = new Widget({ node: document.createElement('h2') }); + title.node.textContent = 'Callstack'; + + const layout = new PanelLayout(); + layout.addWidget(title); + layout.addWidget(this.toolbar); + this.layout = layout; + } + + /** + * The toolbar for the callstack header. + */ + readonly toolbar = new Toolbar(); +} diff --git a/src/callstack/index.ts b/src/callstack/index.ts index 70ec802b..815ebc49 100644 --- a/src/callstack/index.ts +++ b/src/callstack/index.ts @@ -1,28 +1,32 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { CommandToolbarButton, Toolbar } from '@jupyterlab/apputils'; +import { CommandToolbarButton } from '@jupyterlab/apputils'; import { CommandRegistry } from '@phosphor/commands'; -import { ISignal, Signal } from '@phosphor/signaling'; -import { Panel, PanelLayout, Widget } from '@phosphor/widgets'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { Body } from './body'; +import { Panel } from '@phosphor/widgets'; + +import { CallstackBody } from './body'; + +import { CallstackHeader } from './header'; + +import { CallstackModel } from './model'; + +/** + * A Panel to show a callstack. + */ export class Callstack extends Panel { + /** + * Instantiate a new Callstack Panel. + * @param options The instantiation options for a Callstack Panel. + */ constructor(options: Callstack.IOptions) { super(); - const { commands, model } = options; - this.model = model; - this.addClass('jp-DebuggerCallstack'); - const header = new CallstackHeader(); - const body = new Body(this.model); - - this.addWidget(header); - this.addWidget(body); + const body = new CallstackBody(model); header.toolbar.addItem( 'continue', @@ -63,67 +67,21 @@ export class Callstack extends Panel { id: commands.stepOut }) ); - } - readonly model: Callstack.Model; -} - -class CallstackHeader extends Widget { - constructor() { - super({ node: document.createElement('header') }); - - const layout = new PanelLayout(); - const title = new Widget({ node: document.createElement('h2') }); + this.addWidget(header); + this.addWidget(body); - this.layout = layout; - title.node.textContent = 'Callstack'; - layout.addWidget(title); - layout.addWidget(this.toolbar); + this.addClass('jp-DebuggerCallstack'); } - - readonly toolbar = new Toolbar(); } +/** + * A namespace for Callstack `statics`. + */ export namespace Callstack { - export interface IFrame extends DebugProtocol.StackFrame {} - - export class Model { - set frames(newFrames: IFrame[]) { - this._state = newFrames; - // default to the new frame is the previous one can't be found - if (!this.frame || !newFrames.find(frame => frame.id === this.frame.id)) { - this.frame = newFrames[0]; - } - this._framesChanged.emit(newFrames); - } - - get frames(): IFrame[] { - return this._state; - } - - set frame(frame: IFrame) { - this._currentFrame = frame; - this._currentFrameChanged.emit(frame); - } - - get frame(): IFrame { - return this._currentFrame; - } - - get framesChanged(): ISignal { - return this._framesChanged; - } - - get currentFrameChanged(): ISignal { - return this._currentFrameChanged; - } - - private _state: IFrame[] = []; - private _currentFrame: IFrame; - private _framesChanged = new Signal(this); - private _currentFrameChanged = new Signal(this); - } - + /** + * The toolbar commands and registry for the callstack. + */ export interface ICommands { /** * The command registry. @@ -156,12 +114,18 @@ export namespace Callstack { stepOut: string; } + /** + * Instantiation options for `Callstack`. + */ export interface IOptions extends Panel.IOptions { /** * The toolbar commands interface for the callstack. */ commands: ICommands; - model: Model; + /** + * The model for the callstack. + */ + model: CallstackModel; } } diff --git a/src/callstack/model.ts b/src/callstack/model.ts new file mode 100644 index 00000000..9f0830f3 --- /dev/null +++ b/src/callstack/model.ts @@ -0,0 +1,74 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { ISignal, Signal } from '@phosphor/signaling'; + +import { DebugProtocol } from 'vscode-debugprotocol'; + +/** + * A model for a callstack. + */ +export class CallstackModel { + /** + * Get all the frames. + */ + get frames(): CallstackModel.IFrame[] { + return this._state; + } + + /** + * Set the frames. + */ + set frames(newFrames: CallstackModel.IFrame[]) { + this._state = newFrames; + // default to the new frame is the previous one can't be found + if (!this.frame || !newFrames.find(frame => frame.id === this.frame.id)) { + this.frame = newFrames[0]; + } + this._framesChanged.emit(newFrames); + } + + /** + * Get the current frame. + */ + get frame(): CallstackModel.IFrame { + return this._currentFrame; + } + + /** + * Set the current frame. + */ + set frame(frame: CallstackModel.IFrame) { + this._currentFrame = frame; + this._currentFrameChanged.emit(frame); + } + + /** + * Signal emitted when the frames have changed. + */ + get framesChanged(): ISignal { + return this._framesChanged; + } + + /** + * Signal emitted when the current frame has changed. + */ + get currentFrameChanged(): ISignal { + return this._currentFrameChanged; + } + + private _state: CallstackModel.IFrame[] = []; + private _currentFrame: CallstackModel.IFrame; + private _framesChanged = new Signal(this); + private _currentFrameChanged = new Signal(this); +} + +/** + * A namespace for CallstackModel `statics`. + */ +export namespace CallstackModel { + /** + * An interface for a frame. + */ + export interface IFrame extends DebugProtocol.StackFrame {} +} diff --git a/src/debugger.ts b/src/debugger.ts index ed407269..e5bba534 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -3,17 +3,17 @@ import { IEditorServices } from '@jupyterlab/codeeditor'; -import { ISignal, Signal } from '@phosphor/signaling'; - import { SplitPanel } from '@phosphor/widgets'; import { Breakpoints } from './breakpoints'; import { Callstack } from './callstack'; -import { Sources } from './sources'; +import { DebuggerModel } from './model'; + +import { DebuggerService } from './service'; -import { DebugService } from './service'; +import { Sources } from './sources'; import { IDebugger } from './tokens'; @@ -23,7 +23,14 @@ import { Variables } from './variables'; * A namespace for `Debugger` statics. */ export namespace Debugger { + /** + * A debugger sidebar. + */ export class Sidebar extends SplitPanel { + /** + * Instantiate a new Debugger.Sidebar + * @param options The instantiation options for a Debugger.Sidebar + */ constructor(options: Sidebar.IOptions) { super(); this.id = 'jp-debugger-sidebar'; @@ -32,8 +39,8 @@ export namespace Debugger { const { callstackCommands, editorServices, service } = options; - this.model = new Debugger.Model(); - this.service = service as DebugService; + this.model = new DebuggerModel(); + this.service = service as DebuggerService; this.service.model = this.model; this.variables = new Variables({ model: this.model.variables }); @@ -62,8 +69,14 @@ export namespace Debugger { this.addClass('jp-DebuggerSidebar'); } + /** + * Whether the sidebar is disposed. + */ isDisposed: boolean; + /** + * Dispose the sidebar. + */ dispose(): void { if (this.isDisposed) { return; @@ -72,83 +85,58 @@ export namespace Debugger { this.service.model = null; } - readonly model: Debugger.Model; - readonly service: DebugService; - readonly variables: Variables; - readonly callstack: Callstack; - readonly breakpoints: Breakpoints; - readonly sources: Sources; - } - - export class Model implements IDebugger.IModel { - constructor() { - this.breakpoints = new Breakpoints.Model(); - this.callstack = new Callstack.Model(); - this.variables = new Variables.Model(); - this.sources = new Sources.Model({ - currentFrameChanged: this.callstack.currentFrameChanged - }); - } - - readonly breakpoints: Breakpoints.Model; - readonly callstack: Callstack.Model; - readonly variables: Variables.Model; - readonly sources: Sources.Model; - - dispose(): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - this._disposed.emit(); - } - /** - * A signal emitted when the debugger widget is disposed. + * The debugger model. */ - get disposed(): ISignal { - return this._disposed; - } + readonly model: DebuggerModel; - get isDisposed(): boolean { - return this._isDisposed; - } + /** + * The debugger service. + */ + readonly service: DebuggerService; /** - * The set of threads in stopped state. + * The variables widget. */ - get stoppedThreads(): Set { - return this._stoppedThreads; - } + readonly variables: Variables; /** - * Assigns the parameters to the set of threads in stopped state. + * The callstack widget. */ - set stoppedThreads(threads: Set) { - this._stoppedThreads = threads; - } + readonly callstack: Callstack; /** - * Clear the model. + * The breakpoints widget. */ - clear() { - this._stoppedThreads.clear(); - const breakpoints = new Map(); - this.breakpoints.restoreBreakpoints(breakpoints); - this.callstack.frames = []; - this.variables.scopes = []; - this.sources.currentSource = null; - } + readonly breakpoints: Breakpoints; - private _isDisposed = false; - private _stoppedThreads = new Set(); - private _disposed = new Signal(this); + /** + * The sources widget. + */ + readonly sources: Sources; } + /** + * A namespace for Sidebar `statics` + */ export namespace Sidebar { + /** + * Instantiation options for `Sidebar`. + */ export interface IOptions { + /** + * The debug service. + */ service: IDebugger; + + /** + * The callstack toolbar commands. + */ callstackCommands: Callstack.ICommands; + + /** + * The editor services. + */ editorServices: IEditorServices; } } diff --git a/src/handlers/editor.ts b/src/handlers/editor.ts index 1dec34b5..94e394bf 100644 --- a/src/handlers/editor.ts +++ b/src/handlers/editor.ts @@ -15,14 +15,20 @@ import { Signal } from '@phosphor/signaling'; import { Editor } from 'codemirror'; -import { Breakpoints } from '../breakpoints'; +import { IDebugger } from '../tokens'; -import { Debugger } from '../debugger'; +import { BreakpointsModel } from '../breakpoints/model'; -import { IDebugger } from '../tokens'; +import { DebuggerModel } from '../model'; +/** + * The class name added to the current line. + */ const LINE_HIGHLIGHT_CLASS = 'jp-DebuggerEditor-highlight'; +/** + * The timeout for listening to editor content changes. + */ const EDITOR_CHANGED_TIMEOUT = 1000; /** @@ -76,7 +82,7 @@ export class EditorHandler implements IDisposable { * Handle when the debug model changes. */ private _onModelChanged() { - this._debuggerModel = this._debuggerService.model as Debugger.Model; + this._debuggerModel = this._debuggerService.model as DebuggerModel; if (!this._debuggerModel) { return; } @@ -117,7 +123,7 @@ export class EditorHandler implements IDisposable { 'CodeMirror-linenumbers', 'breakpoints' ]); - editor.editor.on('gutterClick', this.onGutterClick); + editor.editor.on('gutterClick', this._onGutterClick); } /** @@ -132,7 +138,7 @@ export class EditorHandler implements IDisposable { EditorHandler.clearGutter(editor); editor.setOption('lineNumbers', false); editor.editor.setOption('gutters', []); - editor.editor.off('gutterClick', this.onGutterClick); + editor.editor.off('gutterClick', this._onGutterClick); } /** @@ -162,7 +168,7 @@ export class EditorHandler implements IDisposable { * @param editor The editor from where the click originated. * @param lineNumber The line corresponding to the click event. */ - private onGutterClick = (editor: Editor, lineNumber: number) => { + private _onGutterClick = (editor: Editor, lineNumber: number) => { const info = editor.lineInfo(lineNumber); if (!info || this._id !== this._debuggerService.session.client.path) { @@ -237,8 +243,8 @@ export class EditorHandler implements IDisposable { private _id: string; private _path: string; private _editor: CodeEditor.IEditor; - private _debuggerModel: Debugger.Model; - private _breakpointsModel: Breakpoints.Model; + private _debuggerModel: DebuggerModel; + private _breakpointsModel: BreakpointsModel; private _debuggerService: IDebugger; private _editorMonitor: ActivityMonitor< IObservableString, diff --git a/src/handlers/tracker.ts b/src/handlers/tracker.ts index 07a258a7..32b0cf62 100644 --- a/src/handlers/tracker.ts +++ b/src/handlers/tracker.ts @@ -34,16 +34,18 @@ import { IDisposable } from '@phosphor/disposable'; import { Signal } from '@phosphor/signaling'; -import { Callstack } from '../callstack'; - import { EditorHandler } from './editor'; -import { Debugger } from '../debugger'; +import { CallstackModel } from '../callstack/model'; + +import { ReadOnlyEditorFactory } from '../sources/factory'; -import { ReadOnlyEditorFactory, Sources } from '../sources'; +import { SourcesModel } from '../sources/model'; import { IDebugger } from '../tokens'; +import { DebuggerModel } from '../model'; + /** * A class which handles notebook, console and editor trackers. */ @@ -93,7 +95,7 @@ export class TrackerHandler implements IDisposable { * Handle when the debug model changes. */ private _onModelChanged() { - this._debuggerModel = this._debuggerService.model as Debugger.Model; + this._debuggerModel = this._debuggerService.model as DebuggerModel; if (!this._debuggerModel) { return; } @@ -123,7 +125,10 @@ export class TrackerHandler implements IDisposable { * @param _ The sender. * @param frame The current frame. */ - private _onCurrentFrameChanged(_: Callstack.Model, frame: Callstack.IFrame) { + private _onCurrentFrameChanged( + _: CallstackModel, + frame: CallstackModel.IFrame + ) { const debugSessionPath = this._debuggerService.session?.client?.path; const source = frame?.source.path ?? null; each(this._find(debugSessionPath, source), editor => { @@ -138,7 +143,7 @@ export class TrackerHandler implements IDisposable { * @param _ The sender. * @param source The source to open. */ - private _onCurrentSourceOpened(_: Sources.Model, source: IDebugger.ISource) { + private _onCurrentSourceOpened(_: SourcesModel, source: IDebugger.ISource) { if (!source) { return; } @@ -320,7 +325,7 @@ export class TrackerHandler implements IDisposable { } private _debuggerService: IDebugger; - private _debuggerModel: Debugger.Model; + private _debuggerModel: DebuggerModel; private _shell: JupyterFrontEnd.IShell; private _readOnlyEditorFactory: ReadOnlyEditorFactory; private _readOnlyEditorTracker: WidgetTracker< diff --git a/src/index.ts b/src/index.ts index 8e598a69..5208aed5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,9 @@ import { NotebookHandler } from './handlers/notebook'; import { TrackerHandler } from './handlers/tracker'; -import { DebugService } from './service'; +import { DebuggerModel } from './model'; + +import { DebuggerService } from './service'; import { DebugSession } from './session'; @@ -62,8 +64,6 @@ export namespace CommandIDs { export const stepIn = 'debugger:stepIn'; export const stepOut = 'debugger:stepOut'; - - export const debugConsole = 'debugger:debug-console'; } /** @@ -225,7 +225,7 @@ class DebuggerHandler< // clear the model if the handler being removed corresponds // to the current active debug session if (debug.session?.client?.path === client.path) { - const model = debug.model as Debugger.Model; + const model = debug.model as DebuggerModel; model.clear(); } @@ -442,7 +442,7 @@ const main: JupyterFrontEndPlugin = { ): IDebugger => { const { commands, shell } = app; - const service = new DebugService(); + const service = new DebuggerService(); commands.addCommand(CommandIDs.debugContinue, { label: 'Continue', diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 00000000..aa2a8fde --- /dev/null +++ b/src/model.ts @@ -0,0 +1,106 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { IDebugger } from './tokens'; + +import { ISignal, Signal } from '@phosphor/signaling'; + +import { BreakpointsModel } from './breakpoints/model'; + +import { CallstackModel } from './callstack/model'; + +import { SourcesModel } from './sources/model'; + +import { VariablesModel } from './variables/model'; + +/** + * A model for a debugger. + */ +export class DebuggerModel implements IDebugger.IModel { + /** + * Instantiate a new DebuggerModel + */ + constructor() { + this.breakpoints = new BreakpointsModel(); + this.callstack = new CallstackModel(); + this.variables = new VariablesModel(); + this.sources = new SourcesModel({ + currentFrameChanged: this.callstack.currentFrameChanged + }); + } + + /** + * The breakpoints model. + */ + readonly breakpoints: BreakpointsModel; + + /** + * The callstack model. + */ + readonly callstack: CallstackModel; + + /** + * The variables model. + */ + readonly variables: VariablesModel; + + /** + * The sources model. + */ + readonly sources: SourcesModel; + + /** + * A signal emitted when the debugger widget is disposed. + */ + get disposed(): ISignal { + return this._disposed; + } + + /** + * Whether the model is disposed. + */ + get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * The set of threads in stopped state. + */ + get stoppedThreads(): Set { + return this._stoppedThreads; + } + + /** + * Assigns the parameters to the set of threads in stopped state. + */ + set stoppedThreads(threads: Set) { + this._stoppedThreads = threads; + } + + /** + * Dispose the model. + */ + dispose(): void { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + this._disposed.emit(); + } + + /** + * Clear the model. + */ + clear() { + this._stoppedThreads.clear(); + const breakpoints = new Map(); + this.breakpoints.restoreBreakpoints(breakpoints); + this.callstack.frames = []; + this.variables.scopes = []; + this.sources.currentSource = null; + } + + private _isDisposed = false; + private _stoppedThreads = new Set(); + private _disposed = new Signal(this); +} diff --git a/src/service.ts b/src/service.ts index d1cc0730..6cce9ad7 100644 --- a/src/service.ts +++ b/src/service.ts @@ -13,18 +13,21 @@ import { murmur2 } from 'murmurhash-js'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { Debugger } from './debugger'; +import { CallstackModel } from './callstack/model'; -import { IDebugger } from './tokens'; +import { DebuggerModel } from './model'; -import { Variables } from './variables'; +import { IDebugger } from './tokens'; -import { Callstack } from './callstack'; +import { VariablesModel } from './variables/model'; /** * A concrete implementation of IDebugger. */ -export class DebugService implements IDebugger, IDisposable { +export class DebuggerService implements IDebugger, IDisposable { + /** + * Instantiate a new DebuggerService. + */ constructor() { // Avoids setting session with invalid client // session should be set only when a notebook or @@ -97,7 +100,7 @@ export class DebugService implements IDebugger, IDisposable { * @param model - The new debugger model. */ set model(model: IDebugger.IModel) { - this._model = model as Debugger.Model; + this._model = model as DebuggerModel; this._modelChanged.emit(model); } @@ -402,7 +405,10 @@ export class DebugService implements IDebugger, IDisposable { /** * Handle a change of the current active frame. */ - private async _onChangeFrame(_: Callstack.Model, frame: Callstack.IFrame) { + private async _onChangeFrame( + _: CallstackModel, + frame: CallstackModel.IFrame + ) { if (!frame) { return; } @@ -502,7 +508,7 @@ export class DebugService implements IDebugger, IDisposable { private _convertScopes( scopes: DebugProtocol.Scope[], variables: DebugProtocol.Variable[] - ): Variables.IScope[] { + ): VariablesModel.IScope[] { if (!variables || !scopes) { return; } @@ -573,7 +579,7 @@ export class DebugService implements IDebugger, IDisposable { private _isDisposed: boolean = false; private _session: IDebugger.ISession; - private _model: Debugger.Model; + private _model: DebuggerModel; private _sessionChanged = new Signal(this); private _modelChanged = new Signal(this); private _eventMessage = new Signal(this); diff --git a/src/sources/body.ts b/src/sources/body.ts new file mode 100644 index 00000000..3dc9f927 --- /dev/null +++ b/src/sources/body.ts @@ -0,0 +1,160 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + CodeEditorWrapper, + IEditorMimeTypeService, + IEditorServices +} from '@jupyterlab/codeeditor'; + +import { Signal } from '@phosphor/signaling'; + +import { PanelLayout, Widget } from '@phosphor/widgets'; + +import { CallstackModel } from '../callstack/model'; + +import { EditorHandler } from '../handlers/editor'; + +import { IDebugger } from '../tokens'; + +import { ReadOnlyEditorFactory } from './factory'; + +import { SourcesModel } from './model'; + +/** + * The body for a Sources Panel. + */ +export class SourcesBody extends Widget { + /** + * Instantiate a new Body for the Sources Panel. + * @param model The model for the sources. + */ + constructor(options: SourcesBody.IOptions) { + super(); + this._model = options.model; + this._debuggerService = options.service; + this._mimeTypeService = options.editorServices.mimeTypeService; + + const factory = new ReadOnlyEditorFactory({ + editorServices: options.editorServices + }); + + this._editor = factory.createNewEditor({ + content: '', + mimeType: '', + path: '' + }); + this._editor.hide(); + + this._model.currentFrameChanged.connect(async (_, frame) => { + if (!frame) { + this._clearEditor(); + return; + } + + void this._showSource(frame); + }); + + const layout = new PanelLayout(); + layout.addWidget(this._editor); + this.layout = layout; + + this.addClass('jp-DebuggerSources-body'); + } + + /** + * Dispose the sources body widget. + */ + dispose(): void { + if (this.isDisposed) { + return; + } + this._editorHandler.dispose(); + Signal.clearData(this); + } + + /** + * Clear the content of the source read-only editor. + */ + private _clearEditor() { + this._model.currentSource = null; + this._editor.hide(); + } + + /** + * Show the content of the source for the given frame. + * @param frame The current frame. + */ + private async _showSource(frame: CallstackModel.IFrame) { + const path = frame.source.path; + const source = await this._debuggerService.getSource({ + sourceReference: 0, + path + }); + + if (!source?.content) { + this._clearEditor(); + return; + } + + if (this._editorHandler) { + this._editorHandler.dispose(); + } + + const { content, mimeType } = source; + const editorMimeType = + mimeType || this._mimeTypeService.getMimeTypeByFilePath(path); + + this._editor.model.value.text = content; + this._editor.model.mimeType = editorMimeType; + + this._editorHandler = new EditorHandler({ + debuggerService: this._debuggerService, + editor: this._editor.editor, + path + }); + + this._model.currentSource = { + content, + mimeType: editorMimeType, + path + }; + + requestAnimationFrame(() => { + EditorHandler.showCurrentLine(this._editor.editor, frame.line); + }); + + this._editor.show(); + } + + private _model: SourcesModel; + private _editor: CodeEditorWrapper; + private _editorHandler: EditorHandler; + private _debuggerService: IDebugger; + private _mimeTypeService: IEditorMimeTypeService; +} + +/** + * A namespace for SourcesBody `statics`. + */ +export namespace SourcesBody { + /** + * Instantiation options for `Breakpoints`. + */ + export interface IOptions { + /** + * The debug service. + */ + service: IDebugger; + + /** + * The sources model. + */ + model: SourcesModel; + + /** + * The editor services used to create new read-only editors. + */ + editorServices: IEditorServices; + } +} diff --git a/src/sources/factory.ts b/src/sources/factory.ts new file mode 100644 index 00000000..73151580 --- /dev/null +++ b/src/sources/factory.ts @@ -0,0 +1,63 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + CodeEditor, + CodeEditorWrapper, + IEditorServices +} from '@jupyterlab/codeeditor'; + +import { IDebugger } from '../tokens'; + +/** + * A widget factory for read only editors. + */ +export class ReadOnlyEditorFactory { + /** + * Construct a new editor widget factory. + * @param options The instantiation options for a ReadOnlyEditorFactory. + */ + constructor(options: ReadOnlyEditorFactory.IOptions) { + this._services = options.editorServices; + } + + /** + * Create a new CodeEditorWrapper given a Source. + * @param source The source to create a new editor for. + */ + createNewEditor(source: IDebugger.ISource): CodeEditorWrapper { + const { content, mimeType, path } = source; + const factory = this._services.factoryService.newInlineEditor; + const mimeTypeService = this._services.mimeTypeService; + const editor = new CodeEditorWrapper({ + model: new CodeEditor.Model({ + value: content, + mimeType: mimeType || mimeTypeService.getMimeTypeByFilePath(path) + }), + factory, + config: { + readOnly: true, + lineNumbers: true + } + }); + editor.node.setAttribute('data-jp-debugger', 'true'); + return editor; + } + + private _services: IEditorServices; +} + +/** + * The namespace for `ReadOnlyEditorFactory` class statics. + */ +export namespace ReadOnlyEditorFactory { + /** + * The options used to create a read only editor widget factory. + */ + export interface IOptions { + /** + * The editor services used by the factory. + */ + editorServices: IEditorServices; + } +} diff --git a/src/sources/header.ts b/src/sources/header.ts new file mode 100644 index 00000000..1840948e --- /dev/null +++ b/src/sources/header.ts @@ -0,0 +1,45 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { Toolbar } from '@jupyterlab/apputils'; + +import { PanelLayout, Widget } from '@phosphor/widgets'; + +import { SourcesModel } from './model'; + +/** + * The header for a Source Panel. + */ +export class SourcesHeader extends Widget { + /** + * Instantiate a new SourcesHeader. + * @param model The model for the Sources. + */ + constructor(model: SourcesModel) { + super({ node: document.createElement('header') }); + + const layout = new PanelLayout(); + this.layout = layout; + + const title = new Widget({ node: document.createElement('h2') }); + title.node.textContent = 'Source'; + + const sourcePath = new Widget({ node: document.createElement('span') }); + model.currentSourceChanged.connect((_, source) => { + const path = source?.path ?? ''; + sourcePath.node.textContent = path; + sourcePath.node.title = path; + }); + + layout.addWidget(title); + layout.addWidget(this.toolbar); + layout.addWidget(sourcePath); + + this.addClass('jp-DebuggerSources-header'); + } + + /** + * The toolbar for the sources header. + */ + readonly toolbar = new Toolbar(); +} diff --git a/src/sources/index.ts b/src/sources/index.ts index 7b9f13c2..a8983b75 100644 --- a/src/sources/index.ts +++ b/src/sources/index.ts @@ -3,24 +3,19 @@ | Distributed under the terms of the Modified BSD License. |----------------------------------------------------------------------------*/ -import { Toolbar, ToolbarButton } from '@jupyterlab/apputils'; +import { ToolbarButton } from '@jupyterlab/apputils'; -import { - CodeEditor, - CodeEditorWrapper, - IEditorMimeTypeService, - IEditorServices -} from '@jupyterlab/codeeditor'; +import { IEditorServices } from '@jupyterlab/codeeditor'; -import { ISignal, Signal } from '@phosphor/signaling'; +import { Panel } from '@phosphor/widgets'; -import { Panel, PanelLayout, Widget } from '@phosphor/widgets'; +import { IDebugger } from '../tokens'; -import { Callstack } from '../callstack'; +import { SourcesBody } from './body'; -import { EditorHandler } from '../handlers/editor'; +import { SourcesHeader } from './header'; -import { IDebugger } from '../tokens'; +import { SourcesModel } from './model'; /** * A Panel that shows a preview of the source code while debugging. @@ -32,154 +27,25 @@ export class Sources extends Panel { */ constructor(options: Sources.IOptions) { super(); - this.model = options.model; - this._debuggerService = options.service; - this._mimeTypeService = options.editorServices.mimeTypeService; + const { model, service, editorServices } = options; - const header = new SourcesHeader(this.model); + const header = new SourcesHeader(model); + const body = new SourcesBody({ + service, + model, + editorServices + }); header.toolbar.addItem( 'open', new ToolbarButton({ iconClassName: 'jp-ViewBreakpointIcon', - onClick: () => this.model.open(), + onClick: () => model.open(), tooltip: 'Open in the Main Area' }) ); - - const factory = new ReadOnlyEditorFactory({ - editorServices: options.editorServices - }); - - this._editor = factory.createNewEditor({ - content: '', - mimeType: '', - path: '' - }); - this._editor.hide(); - - this.model.currentFrameChanged.connect(async (_, frame) => { - if (!frame) { - this._clearEditor(); - return; - } - - void this._showSource(frame); - }); - this.addWidget(header); - this.addWidget(this._editor); - } - - /** - * The debugger sources model. - */ - readonly model: Sources.Model; - - /** - * Dispose the debug sources. - */ - dispose(): void { - if (this.isDisposed) { - return; - } - this._editorHandler.dispose(); - Signal.clearData(this); - } - - /** - * Clear the content of the source read-only editor. - */ - private _clearEditor() { - this.model.currentSource = null; - this._editor.hide(); + this.addWidget(body); } - - /** - * Show the content of the source for the given frame. - * @param frame The current frame. - */ - private async _showSource(frame: Callstack.IFrame) { - const path = frame.source.path; - const source = await this._debuggerService.getSource({ - sourceReference: 0, - path - }); - - if (!source?.content) { - this._clearEditor(); - return; - } - - if (this._editorHandler) { - this._editorHandler.dispose(); - } - - const { content, mimeType } = source; - const editorMimeType = - mimeType || this._mimeTypeService.getMimeTypeByFilePath(path); - - this._editor.model.value.text = content; - this._editor.model.mimeType = editorMimeType; - - this._editorHandler = new EditorHandler({ - debuggerService: this._debuggerService, - editor: this._editor.editor, - path - }); - - this.model.currentSource = { - content, - mimeType: editorMimeType, - path - }; - - requestAnimationFrame(() => { - EditorHandler.showCurrentLine(this._editor.editor, frame.line); - }); - - this._editor.show(); - } - - private _debuggerService: IDebugger; - private _mimeTypeService: IEditorMimeTypeService; - private _editor: CodeEditorWrapper; - private _editorHandler: EditorHandler; -} - -/** - * The header for a Source Panel. - */ -class SourcesHeader extends Widget { - /** - * Instantiate a new SourcesHeader. - * @param model The model for the Sources. - */ - constructor(model: Sources.Model) { - super({ node: document.createElement('header') }); - this.addClass('jp-DebuggerSources'); - - const layout = new PanelLayout(); - this.layout = layout; - - const title = new Widget({ node: document.createElement('h2') }); - title.node.textContent = 'Source'; - - const sourcePath = new Widget({ node: document.createElement('span') }); - model.currentSourceChanged.connect((_, source) => { - const path = source?.path ?? ''; - sourcePath.node.textContent = path; - sourcePath.node.title = path; - }); - - layout.addWidget(title); - layout.addWidget(this.toolbar); - layout.addWidget(sourcePath); - } - - /** - * The toolbar for the sources header. - */ - readonly toolbar = new Toolbar(); } /** @@ -198,143 +64,11 @@ export namespace Sources { /** * The model for the sources. */ - model: Sources.Model; + model: SourcesModel; /** * The editor services used to create new read-only editors. */ editorServices: IEditorServices; } - - /** - * The model to keep track of the current source being displayed. - */ - export class Model { - /** - * Instantiate a new Sources.Model - * @param options The Sources.Model instantiation options. - */ - constructor(options: Sources.Model.IOptions) { - this.currentFrameChanged = options.currentFrameChanged; - } - - /** - * Signal emitted when the current frame changes. - */ - currentFrameChanged: ISignal; - - /** - * Signal emitted when a source should be open in the main area. - */ - get currentSourceOpened(): ISignal { - return this._currentSourceOpened; - } - - /** - * Signal emitted when the current source changes. - */ - get currentSourceChanged(): ISignal { - return this._currentSourceChanged; - } - - /** - * Return the current source. - */ - get currentSource() { - return this._currentSource; - } - - /** - * Set the current source. - * @param source The source to set as the current source. - */ - set currentSource(source: IDebugger.ISource | null) { - this._currentSource = source; - this._currentSourceChanged.emit(source); - } - - /** - * Open a source in the main area. - */ - open() { - this._currentSourceOpened.emit(this._currentSource); - } - - private _currentSource: IDebugger.ISource | null; - private _currentSourceOpened = new Signal( - this - ); - private _currentSourceChanged = new Signal< - Sources.Model, - IDebugger.ISource - >(this); - } - - /** - * A namespace for the Model `statics`. - */ - export namespace Model { - /** - * The options used to initialize a Sources.Model object. - */ - export interface IOptions { - /** - * Signal emitted when the current frame changes. - */ - currentFrameChanged: ISignal; - } - } -} - -/** - * A widget factory for read only editors. - */ -export class ReadOnlyEditorFactory { - /** - * Construct a new editor widget factory. - * @param options The instantiation options for a ReadOnlyEditorFactory. - */ - constructor(options: ReadOnlyEditorFactory.IOptions) { - this._services = options.editorServices; - } - - /** - * Create a new CodeEditorWrapper given a Source. - * @param source The source to create a new editor for. - */ - createNewEditor(source: IDebugger.ISource): CodeEditorWrapper { - const { content, mimeType, path } = source; - const factory = this._services.factoryService.newInlineEditor; - const mimeTypeService = this._services.mimeTypeService; - const editor = new CodeEditorWrapper({ - model: new CodeEditor.Model({ - value: content, - mimeType: mimeType || mimeTypeService.getMimeTypeByFilePath(path) - }), - factory, - config: { - readOnly: true, - lineNumbers: true - } - }); - editor.node.setAttribute('data-jp-debugger', 'true'); - return editor; - } - - private _services: IEditorServices; -} - -/** - * The namespace for `ReadOnlyEditorFactory` class statics. - */ -export namespace ReadOnlyEditorFactory { - /** - * The options used to create a read only editor widget factory. - */ - export interface IOptions { - /** - * The editor services used by the factory. - */ - editorServices: IEditorServices; - } } diff --git a/src/sources/model.ts b/src/sources/model.ts new file mode 100644 index 00000000..f26130e0 --- /dev/null +++ b/src/sources/model.ts @@ -0,0 +1,86 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { ISignal, Signal } from '@phosphor/signaling'; + +import { CallstackModel } from '../callstack/model'; + +import { IDebugger } from '../tokens'; + +/** + * The model to keep track of the current source being displayed. + */ +export class SourcesModel { + /** + * Instantiate a new Sources.Model + * @param options The Sources.Model instantiation options. + */ + constructor(options: SourcesModel.IOptions) { + this.currentFrameChanged = options.currentFrameChanged; + } + + /** + * Signal emitted when the current frame changes. + */ + currentFrameChanged: ISignal; + + /** + * Signal emitted when a source should be open in the main area. + */ + get currentSourceOpened(): ISignal { + return this._currentSourceOpened; + } + + /** + * Signal emitted when the current source changes. + */ + get currentSourceChanged(): ISignal { + return this._currentSourceChanged; + } + + /** + * Return the current source. + */ + get currentSource() { + return this._currentSource; + } + + /** + * Set the current source. + * @param source The source to set as the current source. + */ + set currentSource(source: IDebugger.ISource | null) { + this._currentSource = source; + this._currentSourceChanged.emit(source); + } + + /** + * Open a source in the main area. + */ + open() { + this._currentSourceOpened.emit(this._currentSource); + } + + private _currentSource: IDebugger.ISource | null; + private _currentSourceOpened = new Signal( + this + ); + private _currentSourceChanged = new Signal( + this + ); +} + +/** + * A namespace for SourcesModel `statics`. + */ +export namespace SourcesModel { + /** + * The options used to initialize a SourcesModel object. + */ + export interface IOptions { + /** + * Signal emitted when the current frame changes. + */ + currentFrameChanged: ISignal; + } +} diff --git a/src/variables/body/index.tsx b/src/variables/body.tsx similarity index 83% rename from src/variables/body/index.tsx rename to src/variables/body.tsx index 6ed77350..06ff0745 100644 --- a/src/variables/body/index.tsx +++ b/src/variables/body.tsx @@ -1,36 +1,50 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { Variables } from '../index'; - -import { ITheme, ObjectInspector, ObjectRootLabel } from 'react-inspector'; - import { ReactWidget } from '@jupyterlab/apputils'; import { ArrayExt } from '@phosphor/algorithm'; +import { ITheme, ObjectInspector, ObjectRootLabel } from 'react-inspector'; + import React, { useEffect, useState } from 'react'; -export class Body extends ReactWidget { - constructor(model: Variables.Model) { +import { VariablesModel } from './model'; + +/** + * The body for a Variables Panel. + */ +export class VariablesBody extends ReactWidget { + /** + * Instantiate a new Body for the Variables Panel. + * @param model The model for the variables. + */ + constructor(model: VariablesModel) { super(); - this.model = model; + this._model = model; this.addClass('jp-DebuggerVariables-body'); } - model: Variables.Model; - + /** + * Render the VariablesComponent. + */ render() { - return ; + return ; } + + private _model: VariablesModel; } -const VariableComponent = ({ model }: { model: Variables.Model }) => { +/** + * A React component to display a list of variables. + * @param model The model for the variables. + */ +const VariablesComponent = ({ model }: { model: VariablesModel }) => { const [data, setData] = useState(model.scopes); // TODO: this should be simplified and extracted from the component const filterVariable = ( - variable: Variables.IVariable, + variable: VariablesModel.IVariable, isObject?: boolean, keyObj?: string ): Object => { @@ -70,7 +84,7 @@ const VariableComponent = ({ model }: { model: Variables.Model }) => { return filteredObj; }; - const convertForObjectInspector = (scopes: Variables.IScope[]) => { + const convertForObjectInspector = (scopes: VariablesModel.IScope[]) => { return scopes.map(scope => { const newVariable = scope.variables.map(variable => { if (variable.expanded || variable.variablesReference === 0) { @@ -87,7 +101,7 @@ const VariableComponent = ({ model }: { model: Variables.Model }) => { }; useEffect(() => { - const updateScopes = (self: Variables.Model) => { + const updateScopes = (self: VariablesModel) => { if (ArrayExt.shallowEqual(data, self.scopes)) { return; } @@ -119,7 +133,11 @@ const VariableComponent = ({ model }: { model: Variables.Model }) => { return <>{List()}; }; -const convertType = (variable: Variables.IVariable) => { +/** + * Convert a variable to a primitive type. + * @param variable The variable. + */ +const convertType = (variable: VariablesModel.IVariable) => { const { type, value } = variable; switch (type) { case 'int': @@ -135,6 +153,9 @@ const convertType = (variable: Variables.IVariable) => { } }; +/** + * Default renderer for the variable tree view. + */ const defaultNodeRenderer = ({ depth, name, @@ -178,6 +199,9 @@ const defaultNodeRenderer = ({ ); }; +/** + * Default theme for the variable tree view. + */ const THEME = { BASE_FONT_FAMILY: 'var(--jp-code-font-family)', BASE_FONT_SIZE: 'var(--jp-code-font-size)', diff --git a/src/variables/header.ts b/src/variables/header.ts new file mode 100644 index 00000000..d7c4199b --- /dev/null +++ b/src/variables/header.ts @@ -0,0 +1,23 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { PanelLayout, Widget } from '@phosphor/widgets'; + +/** + * The header for a Variables Panel. + */ +export class VariablesHeader extends Widget { + /** + * Instantiate a new VariablesHeader. + */ + constructor() { + super({ node: document.createElement('header') }); + + const title = new Widget({ node: document.createElement('h2') }); + title.node.textContent = 'Variables'; + + const layout = new PanelLayout(); + this.layout = layout; + layout.addWidget(title); + } +} diff --git a/src/variables/index.ts b/src/variables/index.ts index aca76a9c..0f4d13d7 100644 --- a/src/variables/index.ts +++ b/src/variables/index.ts @@ -1,93 +1,65 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { Panel, PanelLayout, Widget } from '@phosphor/widgets'; +import { Panel, Widget } from '@phosphor/widgets'; -import { Body } from './body'; +import { VariablesBody } from './body'; -import { ISignal, Signal } from '@phosphor/signaling'; +import { VariablesHeader } from './header'; -import { DebugProtocol } from 'vscode-debugprotocol'; +import { VariablesModel } from './model'; +/** + * A Panel to show a variable explorer. + */ export class Variables extends Panel { + /** + * Instantiate a new Variables Panel. + * @param options The instantiation options for a Variables Panel. + */ constructor(options: Variables.IOptions) { super(); + this._header = new VariablesHeader(); + this._body = new VariablesBody(options.model); - this.model = options.model; - this.addClass('jp-DebuggerVariables'); - - this.header = new VariablesHeader(); - this.body = new Body(this.model); + this.addWidget(this._header); + this.addWidget(this._body); - this.addWidget(this.header); - this.addWidget(this.body); + this.addClass('jp-DebuggerVariables'); } - readonly model: Variables.Model; - readonly header: VariablesHeader; - readonly body: Widget; + private _header: VariablesHeader; + private _body: Widget; + /** + * A message handler invoked on a `'resize'` message. + */ protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); - this.resizeBody(msg); + this._resizeBody(msg); } - private resizeBody(msg: Widget.ResizeMessage) { - const height = msg.height - this.header.node.offsetHeight; - this.body.node.style.height = `${height}px`; - } -} - -class VariablesHeader extends Widget { - constructor() { - super({ node: document.createElement('header') }); - const layout = new PanelLayout(); - const title = new Widget({ node: document.createElement('h2') }); - - this.layout = layout; - title.node.textContent = 'Variables'; - layout.addWidget(title); + /** + * Resize the body. + * @param msg The resize message. + */ + private _resizeBody(msg: Widget.ResizeMessage) { + const height = msg.height - this._header.node.offsetHeight; + this._body.node.style.height = `${height}px`; } } +/** + * A namespace for Variables `statics`. + */ export namespace Variables { - export interface IVariable extends DebugProtocol.Variable { - expanded?: boolean; - } - - export interface IScope { - name: string; - variables: IVariable[]; - } - - export class Model { - get changed(): ISignal { - return this._changed; - } - - get scopes(): IScope[] { - return this._state; - } - - set scopes(scopes: IScope[]) { - this._state = scopes; - this._changed.emit(); - } - - get variableExpanded(): ISignal { - return this._variableExpanded; - } - - expandVariable(variable: IVariable) { - this._variableExpanded.emit(variable); - } - - protected _state: IScope[] = []; - private _variableExpanded = new Signal(this); - private _changed = new Signal(this); - } - + /** + * Instantiation options for `Variables`. + */ export interface IOptions extends Panel.IOptions { - model: Model; + /** + * The variables model. + */ + model: VariablesModel; } } diff --git a/src/variables/model.ts b/src/variables/model.ts new file mode 100644 index 00000000..4d1f044f --- /dev/null +++ b/src/variables/model.ts @@ -0,0 +1,82 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { ISignal, Signal } from '@phosphor/signaling'; + +import { DebugProtocol } from 'vscode-debugprotocol'; + +/** + * A model for a variable explorer. + */ +export class VariablesModel { + /** + * Get all the scopes. + */ + get scopes(): VariablesModel.IScope[] { + return this._state; + } + + /** + * Set the scopes. + */ + set scopes(scopes: VariablesModel.IScope[]) { + this._state = scopes; + this._changed.emit(); + } + + /** + * Signal emitted when the current variable has changed. + */ + get changed(): ISignal { + return this._changed; + } + + /** + * Signal emitted when the current variable has been expanded. + */ + get variableExpanded(): ISignal { + return this._variableExpanded; + } + + /** + * Expand a variable. + * @param variable The variable to expand. + */ + expandVariable(variable: VariablesModel.IVariable) { + this._variableExpanded.emit(variable); + } + + private _state: VariablesModel.IScope[] = []; + private _variableExpanded = new Signal(this); + private _changed = new Signal(this); +} + +/** + * A namespace for VariablesModel `statics`. + */ +export namespace VariablesModel { + /** + * An interface for a variable. + */ + export interface IVariable extends DebugProtocol.Variable { + /** + * Whether the variable is expanded. + */ + expanded?: boolean; + } + + /** + * An interface for a scope. + */ + export interface IScope { + /** + * The name of the scope. + */ + name: string; + + /** + * The list of variables. + */ + variables: IVariable[]; + } +} diff --git a/src/variables/body/typings.d.ts b/src/variables/typings.d.ts similarity index 56% rename from src/variables/body/typings.d.ts rename to src/variables/typings.d.ts index 1e1bc27d..1ee45a07 100644 --- a/src/variables/body/typings.d.ts +++ b/src/variables/typings.d.ts @@ -1,3 +1,3 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -/// +/// diff --git a/style/sources.css b/style/sources.css index 5ed06b09..48db624b 100644 --- a/style/sources.css +++ b/style/sources.css @@ -1,13 +1,17 @@ -[data-jp-debugger='true'].jp-Editor .jp-mod-readOnly { +.jp-DebuggerSources-body [data-jp-debugger='true'].jp-Editor .jp-mod-readOnly { background: var(--jp-layout-color2); height: 100%; } -[data-jp-debugger='true'].jp-Editor { +.jp-DebuggerSources-body [data-jp-debugger='true'].jp-Editor { height: 100%; } -.jp-DebuggerSources > span { +.jp-DebuggerSources-body { + height: 100%; +} + +.jp-DebuggerSources-header > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/tests/src/debugger.spec.ts b/tests/src/debugger.spec.ts index 181a3bf2..09bd2cb8 100644 --- a/tests/src/debugger.spec.ts +++ b/tests/src/debugger.spec.ts @@ -9,12 +9,12 @@ import { CommandRegistry } from '@phosphor/commands'; import { Debugger } from '../../lib/debugger'; -import { DebugService } from '../../lib/service'; +import { DebuggerService } from '../../lib/service'; class TestSidebar extends Debugger.Sidebar {} describe('Debugger', () => { - const service = new DebugService(); + const service = new DebuggerService(); const registry = new CommandRegistry(); const factoryService = new CodeMirrorEditorFactory(); const mimeTypeService = new CodeMirrorMimeTypeService(); diff --git a/tests/src/service.spec.ts b/tests/src/service.spec.ts index 0ef38542..a40e0fc6 100644 --- a/tests/src/service.spec.ts +++ b/tests/src/service.spec.ts @@ -6,16 +6,16 @@ import { createClientSession } from '@jupyterlab/testutils'; import { PromiseDelegate } from '@phosphor/coreutils'; -import { Debugger } from '../../lib/debugger'; +import { DebuggerModel } from '../../lib/model'; -import { DebugService } from '../../lib/service'; +import { DebuggerService } from '../../lib/service'; import { DebugSession } from '../../lib/session'; import { IDebugger } from '../../lib/tokens'; describe('Debugging support', () => { - const service = new DebugService(); + const service = new DebuggerService(); let xpythonClient: IClientSession; let ipykernelClient: IClientSession; @@ -57,9 +57,9 @@ describe('Debugging support', () => { }); }); -describe('DebugService', () => { +describe('DebuggerService', () => { let client: IClientSession; - let model: Debugger.Model; + let model: DebuggerModel; let session: IDebugger.ISession; let service: IDebugger; @@ -72,19 +72,19 @@ describe('DebugService', () => { await (client as ClientSession).initialize(); await client.kernel.ready; session = new DebugSession({ client }); - model = new Debugger.Model(); - service = new DebugService(); + model = new DebuggerModel(); + service = new DebuggerService(); }); afterEach(async () => { await client.shutdown(); session.dispose(); - (service as DebugService).dispose(); + (service as DebuggerService).dispose(); }); describe('#constructor()', () => { it('should create a new instance', () => { - expect(service).to.be.an.instanceOf(DebugService); + expect(service).to.be.an.instanceOf(DebuggerService); }); }); @@ -115,7 +115,7 @@ describe('DebugService', () => { describe('#session', () => { it('should emit the sessionChanged signal when setting the session', () => { - let sessionChangedEvents: IDebugger.ISession[] = []; + const sessionChangedEvents: IDebugger.ISession[] = []; service.sessionChanged.connect((_, newSession) => { sessionChangedEvents.push(newSession); }); @@ -127,9 +127,9 @@ describe('DebugService', () => { describe('#model', () => { it('should emit the modelChanged signal when setting the model', () => { - let modelChangedEvents: Debugger.Model[] = []; + const modelChangedEvents: DebuggerModel[] = []; service.modelChanged.connect((_, newModel) => { - modelChangedEvents.push(newModel as Debugger.Model); + modelChangedEvents.push(newModel as DebuggerModel); }); service.model = model; expect(modelChangedEvents.length).to.equal(1);