@jupyterlab/debugger
Version:
JupyterLab - Debugger Extension
314 lines • 12.8 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { nullTranslator } from '@jupyterlab/translation';
import { bugDotIcon, bugIcon, ToolbarButton } from '@jupyterlab/ui-components';
import { Debugger } from './debugger';
import { ConsoleHandler } from './handlers/console';
import { FileHandler } from './handlers/file';
import { NotebookHandler } from './handlers/notebook';
const TOOLBAR_DEBUGGER_ITEM = 'debugger-icon';
/**
* Add a bug icon to the widget toolbar to enable and disable debugging.
*
* @param widget The widget to add the debug toolbar button to.
* @param onClick The callback when the toolbar button is clicked.
*/
function updateIconButton(widget, onClick, enabled, pressed, translator = nullTranslator) {
const trans = translator.load('jupyterlab');
const icon = new ToolbarButton({
className: 'jp-DebuggerBugButton',
icon: bugIcon,
tooltip: trans.__('Enable Debugger'),
pressedIcon: bugDotIcon,
pressedTooltip: trans.__('Disable Debugger'),
disabledTooltip: trans.__('Select a kernel that supports debugging to enable debugger'),
enabled,
pressed,
onClick
});
if (!widget.toolbar.insertBefore('kernelName', TOOLBAR_DEBUGGER_ITEM, icon)) {
widget.toolbar.addItem(TOOLBAR_DEBUGGER_ITEM, icon);
}
return icon;
}
/**
* Updates button state to on/off,
* adds/removes css class to update styling
*
* @param widget the debug button widget
* @param pressed true if pressed, false otherwise
* @param enabled true if widget enabled, false otherwise
* @param onClick click handler
*/
function updateIconButtonState(widget, pressed, enabled = true, onClick) {
if (widget) {
widget.enabled = enabled;
widget.pressed = pressed;
if (onClick) {
widget.onClick = onClick;
}
}
}
/**
* A handler for debugging a widget.
*/
export class DebuggerHandler {
/**
* Instantiate a new DebuggerHandler.
*
* @param options The instantiation options for a DebuggerHandler.
*/
constructor(options) {
this._handlers = {};
this._contextKernelChangedHandlers = {};
this._kernelChangedHandlers = {};
this._statusChangedHandlers = {};
this._iopubMessageHandlers = {};
this._iconButtons = {};
this._type = options.type;
this._shell = options.shell;
this._service = options.service;
}
/**
* Get the active widget.
*/
get activeWidget() {
return this._activeWidget;
}
/**
* Update a debug handler for the given widget, and
* handle kernel changed events.
*
* @param widget The widget to update.
* @param connection The session connection.
*/
async update(widget, connection) {
if (!connection) {
delete this._kernelChangedHandlers[widget.id];
delete this._statusChangedHandlers[widget.id];
delete this._iopubMessageHandlers[widget.id];
return this.updateWidget(widget, connection);
}
const kernelChanged = () => {
void this.updateWidget(widget, connection);
};
const kernelChangedHandler = this._kernelChangedHandlers[widget.id];
if (kernelChangedHandler) {
connection.kernelChanged.disconnect(kernelChangedHandler);
}
this._kernelChangedHandlers[widget.id] = kernelChanged;
connection.kernelChanged.connect(kernelChanged);
const statusChanged = (_, status) => {
if (status.endsWith('restarting')) {
void this.updateWidget(widget, connection);
}
};
const statusChangedHandler = this._statusChangedHandlers[widget.id];
if (statusChangedHandler) {
connection.statusChanged.disconnect(statusChangedHandler);
}
connection.statusChanged.connect(statusChanged);
this._statusChangedHandlers[widget.id] = statusChanged;
const iopubMessage = (_, msg) => {
if (this._service.isStarted &&
!this._service.hasStoppedThreads() &&
msg.parent_header.msg_type ===
'execute_request') {
void this._service.displayDefinedVariables();
}
};
const iopubMessageHandler = this._iopubMessageHandlers[widget.id];
if (iopubMessageHandler) {
connection.iopubMessage.disconnect(iopubMessageHandler);
}
connection.iopubMessage.connect(iopubMessage);
this._iopubMessageHandlers[widget.id] = iopubMessage;
this._activeWidget = widget;
return this.updateWidget(widget, connection);
}
/**
* Update a debug handler for the given widget, and
* handle connection kernel changed events.
*
* @param widget The widget to update.
* @param sessionContext The session context.
*/
async updateContext(widget, sessionContext) {
const connectionChanged = () => {
const { session: connection } = sessionContext;
void this.update(widget, connection);
};
const contextKernelChangedHandlers = this._contextKernelChangedHandlers[widget.id];
if (contextKernelChangedHandlers) {
sessionContext.kernelChanged.disconnect(contextKernelChangedHandlers);
}
this._contextKernelChangedHandlers[widget.id] = connectionChanged;
sessionContext.kernelChanged.connect(connectionChanged);
return this.update(widget, sessionContext.session);
}
/**
* Update a debug handler for the given widget.
*
* @param widget The widget to update.
* @param connection The session connection.
*/
async updateWidget(widget, connection) {
var _a, _b, _c, _d;
if (!this._service.model || !connection) {
return;
}
const hasFocus = () => {
return this._shell.currentWidget === widget;
};
const updateAttribute = () => {
if (!this._handlers[widget.id]) {
widget.node.removeAttribute('data-jp-debugger');
return;
}
widget.node.setAttribute('data-jp-debugger', 'true');
};
const createHandler = () => {
if (this._handlers[widget.id]) {
return;
}
switch (this._type) {
case 'notebook':
this._handlers[widget.id] = new NotebookHandler({
debuggerService: this._service,
widget: widget
});
break;
case 'console':
this._handlers[widget.id] = new ConsoleHandler({
debuggerService: this._service,
widget: widget
});
break;
case 'file':
this._handlers[widget.id] = new FileHandler({
debuggerService: this._service,
widget: widget
});
break;
default:
throw Error(`No handler for the type ${this._type}`);
}
updateAttribute();
};
const removeHandlers = () => {
var _a, _b, _c, _d;
const handler = this._handlers[widget.id];
if (!handler) {
return;
}
handler.dispose();
delete this._handlers[widget.id];
delete this._kernelChangedHandlers[widget.id];
delete this._statusChangedHandlers[widget.id];
delete this._iopubMessageHandlers[widget.id];
delete this._contextKernelChangedHandlers[widget.id];
// Clear the model if the handler being removed corresponds
// to the current active debug session, or if the connection
// does not have a kernel.
if (((_b = (_a = this._service.session) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.path) === (connection === null || connection === void 0 ? void 0 : connection.path) ||
!((_d = (_c = this._service.session) === null || _c === void 0 ? void 0 : _c.connection) === null || _d === void 0 ? void 0 : _d.kernel)) {
const model = this._service.model;
model.clear();
}
updateAttribute();
};
const addToolbarButton = (enabled = true) => {
const debugButton = this._iconButtons[widget.id];
if (!debugButton) {
this._iconButtons[widget.id] = updateIconButton(widget, toggleDebugging, this._service.isStarted, enabled);
}
else {
updateIconButtonState(debugButton, this._service.isStarted, enabled, toggleDebugging);
}
};
const isDebuggerOn = () => {
var _a;
return (this._service.isStarted &&
((_a = this._previousConnection) === null || _a === void 0 ? void 0 : _a.id) === (connection === null || connection === void 0 ? void 0 : connection.id));
};
const stopDebugger = async () => {
this._service.session.connection = connection;
await this._service.stop();
};
const startDebugger = async () => {
var _a, _b;
this._service.session.connection = connection;
this._previousConnection = connection;
await this._service.restoreState(true);
await this._service.displayDefinedVariables();
if ((_b = (_a = this._service.session) === null || _a === void 0 ? void 0 : _a.capabilities) === null || _b === void 0 ? void 0 : _b.supportsModulesRequest) {
await this._service.displayModules();
}
};
const toggleDebugging = async () => {
// bail if the widget doesn't have focus
if (!hasFocus()) {
return;
}
const debugButton = this._iconButtons[widget.id];
if (isDebuggerOn()) {
await stopDebugger();
removeHandlers();
updateIconButtonState(debugButton, false);
}
else {
await startDebugger();
createHandler();
updateIconButtonState(debugButton, true);
}
};
addToolbarButton(false);
// listen to the disposed signals
widget.disposed.connect(async () => {
if (isDebuggerOn()) {
await stopDebugger();
}
removeHandlers();
delete this._iconButtons[widget.id];
delete this._contextKernelChangedHandlers[widget.id];
});
const debuggingEnabled = await this._service.isAvailable(connection);
if (!debuggingEnabled) {
removeHandlers();
updateIconButtonState(this._iconButtons[widget.id], false, false);
return;
}
// update the active debug session
if (!this._service.session) {
this._service.session = new Debugger.Session({
connection,
config: this._service.config
});
}
else {
this._previousConnection = ((_a = this._service.session.connection) === null || _a === void 0 ? void 0 : _a.kernel)
? this._service.session.connection
: null;
this._service.session.connection = connection;
}
await this._service.restoreState(false);
if (this._service.isStarted && !this._service.hasStoppedThreads()) {
await this._service.displayDefinedVariables();
if ((_c = (_b = this._service.session) === null || _b === void 0 ? void 0 : _b.capabilities) === null || _c === void 0 ? void 0 : _c.supportsModulesRequest) {
await this._service.displayModules();
}
}
updateIconButtonState(this._iconButtons[widget.id], this._service.isStarted, true);
// check the state of the debug session
if (!this._service.isStarted) {
removeHandlers();
this._service.session.connection = (_d = this._previousConnection) !== null && _d !== void 0 ? _d : connection;
await this._service.restoreState(false);
return;
}
// if the debugger is started but there is no handler, create a new one
createHandler();
this._previousConnection = connection;
}
}
//# sourceMappingURL=handler.js.map