@jupyterlab/debugger-extension 4.0.0-alpha.8 → 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +183 -118
- package/lib/index.js.map +1 -1
- package/package.json +23 -26
- package/schema/main.json +10 -15
- package/src/index.ts +985 -0
- package/style/index.css +1 -1
- package/style/index.js +1 -1
package/src/index.ts
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/**
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
* @module debugger-extension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ILabShell,
|
|
10
|
+
ILayoutRestorer,
|
|
11
|
+
JupyterFrontEnd,
|
|
12
|
+
JupyterFrontEndPlugin
|
|
13
|
+
} from '@jupyterlab/application';
|
|
14
|
+
import {
|
|
15
|
+
Clipboard,
|
|
16
|
+
ICommandPalette,
|
|
17
|
+
InputDialog,
|
|
18
|
+
ISessionContextDialogs,
|
|
19
|
+
IThemeManager,
|
|
20
|
+
MainAreaWidget,
|
|
21
|
+
SessionContextDialogs,
|
|
22
|
+
WidgetTracker
|
|
23
|
+
} from '@jupyterlab/apputils';
|
|
24
|
+
import { CodeCell } from '@jupyterlab/cells';
|
|
25
|
+
import { IEditorServices } from '@jupyterlab/codeeditor';
|
|
26
|
+
import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
|
|
27
|
+
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
|
28
|
+
import {
|
|
29
|
+
Debugger,
|
|
30
|
+
IDebugger,
|
|
31
|
+
IDebuggerConfig,
|
|
32
|
+
IDebuggerHandler,
|
|
33
|
+
IDebuggerSidebar,
|
|
34
|
+
IDebuggerSources
|
|
35
|
+
} from '@jupyterlab/debugger';
|
|
36
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
37
|
+
import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
|
|
38
|
+
import { ILoggerRegistry } from '@jupyterlab/logconsole';
|
|
39
|
+
import {
|
|
40
|
+
INotebookTracker,
|
|
41
|
+
NotebookActions,
|
|
42
|
+
NotebookPanel
|
|
43
|
+
} from '@jupyterlab/notebook';
|
|
44
|
+
import {
|
|
45
|
+
standardRendererFactories as initialFactories,
|
|
46
|
+
IRenderMimeRegistry,
|
|
47
|
+
RenderMimeRegistry
|
|
48
|
+
} from '@jupyterlab/rendermime';
|
|
49
|
+
import { Session } from '@jupyterlab/services';
|
|
50
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
51
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
52
|
+
|
|
53
|
+
function notifyCommands(app: JupyterFrontEnd): void {
|
|
54
|
+
Object.values(Debugger.CommandIDs).forEach(command => {
|
|
55
|
+
if (app.commands.hasCommand(command)) {
|
|
56
|
+
app.commands.notifyCommandChanged(command);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A plugin that provides visual debugging support for consoles.
|
|
63
|
+
*/
|
|
64
|
+
const consoles: JupyterFrontEndPlugin<void> = {
|
|
65
|
+
id: '@jupyterlab/debugger-extension:consoles',
|
|
66
|
+
autoStart: true,
|
|
67
|
+
requires: [IDebugger, IConsoleTracker],
|
|
68
|
+
optional: [ILabShell],
|
|
69
|
+
activate: (
|
|
70
|
+
app: JupyterFrontEnd,
|
|
71
|
+
debug: IDebugger,
|
|
72
|
+
consoleTracker: IConsoleTracker,
|
|
73
|
+
labShell: ILabShell | null
|
|
74
|
+
) => {
|
|
75
|
+
const handler = new Debugger.Handler({
|
|
76
|
+
type: 'console',
|
|
77
|
+
shell: app.shell,
|
|
78
|
+
service: debug
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const updateHandlerAndCommands = async (
|
|
82
|
+
widget: ConsolePanel
|
|
83
|
+
): Promise<void> => {
|
|
84
|
+
const { sessionContext } = widget;
|
|
85
|
+
await sessionContext.ready;
|
|
86
|
+
await handler.updateContext(widget, sessionContext);
|
|
87
|
+
notifyCommands(app);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (labShell) {
|
|
91
|
+
labShell.currentChanged.connect((_, update) => {
|
|
92
|
+
const widget = update.newValue;
|
|
93
|
+
if (widget instanceof ConsolePanel) {
|
|
94
|
+
void updateHandlerAndCommands(widget);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
consoleTracker.currentChanged.connect((_, consolePanel) => {
|
|
99
|
+
if (consolePanel) {
|
|
100
|
+
void updateHandlerAndCommands(consolePanel);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A plugin that provides visual debugging support for file editors.
|
|
109
|
+
*/
|
|
110
|
+
const files: JupyterFrontEndPlugin<void> = {
|
|
111
|
+
id: '@jupyterlab/debugger-extension:files',
|
|
112
|
+
autoStart: true,
|
|
113
|
+
requires: [IDebugger, IEditorTracker],
|
|
114
|
+
optional: [ILabShell],
|
|
115
|
+
activate: (
|
|
116
|
+
app: JupyterFrontEnd,
|
|
117
|
+
debug: IDebugger,
|
|
118
|
+
editorTracker: IEditorTracker,
|
|
119
|
+
labShell: ILabShell | null
|
|
120
|
+
) => {
|
|
121
|
+
const handler = new Debugger.Handler({
|
|
122
|
+
type: 'file',
|
|
123
|
+
shell: app.shell,
|
|
124
|
+
service: debug
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const activeSessions: {
|
|
128
|
+
[id: string]: Session.ISessionConnection;
|
|
129
|
+
} = {};
|
|
130
|
+
|
|
131
|
+
const updateHandlerAndCommands = async (
|
|
132
|
+
widget: DocumentWidget
|
|
133
|
+
): Promise<void> => {
|
|
134
|
+
const sessions = app.serviceManager.sessions;
|
|
135
|
+
try {
|
|
136
|
+
const model = await sessions.findByPath(widget.context.path);
|
|
137
|
+
if (!model) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
let session = activeSessions[model.id];
|
|
141
|
+
if (!session) {
|
|
142
|
+
// Use `connectTo` only if the session does not exist.
|
|
143
|
+
// `connectTo` sends a kernel_info_request on the shell
|
|
144
|
+
// channel, which blocks the debug session restore when waiting
|
|
145
|
+
// for the kernel to be ready
|
|
146
|
+
session = sessions.connectTo({ model });
|
|
147
|
+
activeSessions[model.id] = session;
|
|
148
|
+
}
|
|
149
|
+
await handler.update(widget, session);
|
|
150
|
+
notifyCommands(app);
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (labShell) {
|
|
157
|
+
labShell.currentChanged.connect((_, update) => {
|
|
158
|
+
const widget = update.newValue;
|
|
159
|
+
if (widget instanceof DocumentWidget) {
|
|
160
|
+
const { content } = widget;
|
|
161
|
+
if (content instanceof FileEditor) {
|
|
162
|
+
void updateHandlerAndCommands(widget);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
editorTracker.currentChanged.connect((_, documentWidget) => {
|
|
168
|
+
if (documentWidget) {
|
|
169
|
+
void updateHandlerAndCommands(
|
|
170
|
+
documentWidget as unknown as DocumentWidget
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* A plugin that provides visual debugging support for notebooks.
|
|
180
|
+
*/
|
|
181
|
+
const notebooks: JupyterFrontEndPlugin<IDebugger.IHandler> = {
|
|
182
|
+
id: '@jupyterlab/debugger-extension:notebooks',
|
|
183
|
+
autoStart: true,
|
|
184
|
+
requires: [IDebugger, INotebookTracker],
|
|
185
|
+
optional: [ILabShell, ICommandPalette, ISessionContextDialogs, ITranslator],
|
|
186
|
+
provides: IDebuggerHandler,
|
|
187
|
+
activate: (
|
|
188
|
+
app: JupyterFrontEnd,
|
|
189
|
+
service: IDebugger,
|
|
190
|
+
notebookTracker: INotebookTracker,
|
|
191
|
+
labShell: ILabShell | null,
|
|
192
|
+
palette: ICommandPalette | null,
|
|
193
|
+
sessionDialogs_: ISessionContextDialogs | null,
|
|
194
|
+
translator_: ITranslator | null
|
|
195
|
+
): Debugger.Handler => {
|
|
196
|
+
const translator = translator_ ?? nullTranslator;
|
|
197
|
+
const sessionDialogs =
|
|
198
|
+
sessionDialogs_ ?? new SessionContextDialogs({ translator });
|
|
199
|
+
const handler = new Debugger.Handler({
|
|
200
|
+
type: 'notebook',
|
|
201
|
+
shell: app.shell,
|
|
202
|
+
service
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const trans = translator.load('jupyterlab');
|
|
206
|
+
app.commands.addCommand(Debugger.CommandIDs.restartDebug, {
|
|
207
|
+
label: trans.__('Restart Kernel and Debug…'),
|
|
208
|
+
caption: trans.__('Restart Kernel and Debug…'),
|
|
209
|
+
isEnabled: () => service.isStarted,
|
|
210
|
+
execute: async () => {
|
|
211
|
+
const state = service.getDebuggerState();
|
|
212
|
+
await service.stop();
|
|
213
|
+
|
|
214
|
+
const widget = notebookTracker.currentWidget;
|
|
215
|
+
if (!widget) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { content, sessionContext } = widget;
|
|
220
|
+
const restarted = await sessionDialogs.restart(sessionContext);
|
|
221
|
+
if (!restarted) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await service.restoreDebuggerState(state);
|
|
226
|
+
await handler.updateWidget(widget, sessionContext.session);
|
|
227
|
+
await NotebookActions.runAll(
|
|
228
|
+
content,
|
|
229
|
+
sessionContext,
|
|
230
|
+
sessionDialogs,
|
|
231
|
+
translator
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const updateHandlerAndCommands = async (
|
|
237
|
+
widget: NotebookPanel
|
|
238
|
+
): Promise<void> => {
|
|
239
|
+
if (widget) {
|
|
240
|
+
const { sessionContext } = widget;
|
|
241
|
+
await sessionContext.ready;
|
|
242
|
+
await handler.updateContext(widget, sessionContext);
|
|
243
|
+
}
|
|
244
|
+
notifyCommands(app);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (labShell) {
|
|
248
|
+
labShell.currentChanged.connect((_, update) => {
|
|
249
|
+
const widget = update.newValue;
|
|
250
|
+
if (widget instanceof NotebookPanel) {
|
|
251
|
+
void updateHandlerAndCommands(widget);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
notebookTracker.currentChanged.connect((_, notebookPanel) => {
|
|
256
|
+
if (notebookPanel) {
|
|
257
|
+
void updateHandlerAndCommands(notebookPanel);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (palette) {
|
|
263
|
+
palette.addItem({
|
|
264
|
+
category: 'Notebook Operations',
|
|
265
|
+
command: Debugger.CommandIDs.restartDebug
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return handler;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A plugin that provides a debugger service.
|
|
275
|
+
*/
|
|
276
|
+
const service: JupyterFrontEndPlugin<IDebugger> = {
|
|
277
|
+
id: '@jupyterlab/debugger-extension:service',
|
|
278
|
+
autoStart: true,
|
|
279
|
+
provides: IDebugger,
|
|
280
|
+
requires: [IDebuggerConfig],
|
|
281
|
+
optional: [IDebuggerSources, ITranslator],
|
|
282
|
+
activate: (
|
|
283
|
+
app: JupyterFrontEnd,
|
|
284
|
+
config: IDebugger.IConfig,
|
|
285
|
+
debuggerSources: IDebugger.ISources | null,
|
|
286
|
+
translator: ITranslator | null
|
|
287
|
+
) =>
|
|
288
|
+
new Debugger.Service({
|
|
289
|
+
config,
|
|
290
|
+
debuggerSources,
|
|
291
|
+
specsManager: app.serviceManager.kernelspecs,
|
|
292
|
+
translator
|
|
293
|
+
})
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* A plugin that provides a configuration with hash method.
|
|
298
|
+
*/
|
|
299
|
+
const configuration: JupyterFrontEndPlugin<IDebugger.IConfig> = {
|
|
300
|
+
id: '@jupyterlab/debugger-extension:config',
|
|
301
|
+
provides: IDebuggerConfig,
|
|
302
|
+
autoStart: true,
|
|
303
|
+
activate: () => new Debugger.Config()
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* A plugin that provides source/editor functionality for debugging.
|
|
308
|
+
*/
|
|
309
|
+
const sources: JupyterFrontEndPlugin<IDebugger.ISources> = {
|
|
310
|
+
id: '@jupyterlab/debugger-extension:sources',
|
|
311
|
+
autoStart: true,
|
|
312
|
+
provides: IDebuggerSources,
|
|
313
|
+
requires: [IDebuggerConfig, IEditorServices],
|
|
314
|
+
optional: [INotebookTracker, IConsoleTracker, IEditorTracker],
|
|
315
|
+
activate: (
|
|
316
|
+
app: JupyterFrontEnd,
|
|
317
|
+
config: IDebugger.IConfig,
|
|
318
|
+
editorServices: IEditorServices,
|
|
319
|
+
notebookTracker: INotebookTracker | null,
|
|
320
|
+
consoleTracker: IConsoleTracker | null,
|
|
321
|
+
editorTracker: IEditorTracker | null
|
|
322
|
+
): IDebugger.ISources => {
|
|
323
|
+
return new Debugger.Sources({
|
|
324
|
+
config,
|
|
325
|
+
shell: app.shell,
|
|
326
|
+
editorServices,
|
|
327
|
+
notebookTracker,
|
|
328
|
+
consoleTracker,
|
|
329
|
+
editorTracker
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
/*
|
|
334
|
+
* A plugin to open detailed views for variables.
|
|
335
|
+
*/
|
|
336
|
+
const variables: JupyterFrontEndPlugin<void> = {
|
|
337
|
+
id: '@jupyterlab/debugger-extension:variables',
|
|
338
|
+
autoStart: true,
|
|
339
|
+
requires: [IDebugger, IDebuggerHandler, ITranslator],
|
|
340
|
+
optional: [IThemeManager, IRenderMimeRegistry],
|
|
341
|
+
activate: (
|
|
342
|
+
app: JupyterFrontEnd,
|
|
343
|
+
service: IDebugger,
|
|
344
|
+
handler: Debugger.Handler,
|
|
345
|
+
translator: ITranslator,
|
|
346
|
+
themeManager: IThemeManager | null,
|
|
347
|
+
rendermime: IRenderMimeRegistry | null
|
|
348
|
+
) => {
|
|
349
|
+
const trans = translator.load('jupyterlab');
|
|
350
|
+
const { commands, shell } = app;
|
|
351
|
+
const tracker = new WidgetTracker<MainAreaWidget<Debugger.VariablesGrid>>({
|
|
352
|
+
namespace: 'debugger/inspect-variable'
|
|
353
|
+
});
|
|
354
|
+
const trackerMime = new WidgetTracker<Debugger.VariableRenderer>({
|
|
355
|
+
namespace: 'debugger/render-variable'
|
|
356
|
+
});
|
|
357
|
+
const CommandIDs = Debugger.CommandIDs;
|
|
358
|
+
|
|
359
|
+
// Add commands
|
|
360
|
+
commands.addCommand(CommandIDs.inspectVariable, {
|
|
361
|
+
label: trans.__('Inspect Variable'),
|
|
362
|
+
caption: trans.__('Inspect Variable'),
|
|
363
|
+
isEnabled: args =>
|
|
364
|
+
!!service.session?.isStarted &&
|
|
365
|
+
Number(
|
|
366
|
+
args.variableReference ??
|
|
367
|
+
service.model.variables.selectedVariable?.variablesReference ??
|
|
368
|
+
0
|
|
369
|
+
) > 0,
|
|
370
|
+
execute: async args => {
|
|
371
|
+
let { variableReference, name } = args as {
|
|
372
|
+
variableReference?: number;
|
|
373
|
+
name?: string;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (!variableReference) {
|
|
377
|
+
variableReference =
|
|
378
|
+
service.model.variables.selectedVariable?.variablesReference;
|
|
379
|
+
}
|
|
380
|
+
if (!name) {
|
|
381
|
+
name = service.model.variables.selectedVariable?.name;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const id = `jp-debugger-variable-${name}`;
|
|
385
|
+
if (
|
|
386
|
+
!name ||
|
|
387
|
+
!variableReference ||
|
|
388
|
+
tracker.find(widget => widget.id === id)
|
|
389
|
+
) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const variables = await service.inspectVariable(
|
|
394
|
+
variableReference as number
|
|
395
|
+
);
|
|
396
|
+
if (!variables || variables.length === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const model = service.model.variables;
|
|
401
|
+
const widget = new MainAreaWidget<Debugger.VariablesGrid>({
|
|
402
|
+
content: new Debugger.VariablesGrid({
|
|
403
|
+
model,
|
|
404
|
+
commands,
|
|
405
|
+
scopes: [{ name, variables }],
|
|
406
|
+
themeManager
|
|
407
|
+
})
|
|
408
|
+
});
|
|
409
|
+
widget.addClass('jp-DebuggerVariables');
|
|
410
|
+
widget.id = id;
|
|
411
|
+
widget.title.icon = Debugger.Icons.variableIcon;
|
|
412
|
+
widget.title.label = `${service.session?.connection?.name} - ${name}`;
|
|
413
|
+
void tracker.add(widget);
|
|
414
|
+
const disposeWidget = () => {
|
|
415
|
+
widget.dispose();
|
|
416
|
+
model.changed.disconnect(disposeWidget);
|
|
417
|
+
};
|
|
418
|
+
model.changed.connect(disposeWidget);
|
|
419
|
+
shell.add(widget, 'main', {
|
|
420
|
+
mode: tracker.currentWidget ? 'split-right' : 'split-bottom',
|
|
421
|
+
activate: false,
|
|
422
|
+
type: 'Debugger Variables'
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
commands.addCommand(CommandIDs.renderMimeVariable, {
|
|
428
|
+
label: trans.__('Render Variable'),
|
|
429
|
+
caption: trans.__('Render variable according to its mime type'),
|
|
430
|
+
isEnabled: () => !!service.session?.isStarted,
|
|
431
|
+
isVisible: () =>
|
|
432
|
+
service.model.hasRichVariableRendering &&
|
|
433
|
+
(rendermime !== null || handler.activeWidget instanceof NotebookPanel),
|
|
434
|
+
execute: args => {
|
|
435
|
+
let { name, frameId } = args as {
|
|
436
|
+
frameId?: number;
|
|
437
|
+
name?: string;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
if (!name) {
|
|
441
|
+
name = service.model.variables.selectedVariable?.name;
|
|
442
|
+
}
|
|
443
|
+
if (!frameId) {
|
|
444
|
+
frameId = service.model.callstack.frame?.id;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const activeWidget = handler.activeWidget;
|
|
448
|
+
let activeRendermime =
|
|
449
|
+
activeWidget instanceof NotebookPanel
|
|
450
|
+
? activeWidget.content.rendermime
|
|
451
|
+
: rendermime;
|
|
452
|
+
|
|
453
|
+
if (!activeRendermime) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const id = `jp-debugger-variable-mime-${name}-${service.session?.connection?.path.replace(
|
|
458
|
+
'/',
|
|
459
|
+
'-'
|
|
460
|
+
)}`;
|
|
461
|
+
if (
|
|
462
|
+
!name || // Name is mandatory
|
|
463
|
+
trackerMime.find(widget => widget.id === id) || // Widget already exists
|
|
464
|
+
(!frameId && service.hasStoppedThreads()) // frame id missing on breakpoint
|
|
465
|
+
) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const variablesModel = service.model.variables;
|
|
470
|
+
|
|
471
|
+
const widget = new Debugger.VariableRenderer({
|
|
472
|
+
dataLoader: () => service.inspectRichVariable(name!, frameId),
|
|
473
|
+
rendermime: activeRendermime,
|
|
474
|
+
translator
|
|
475
|
+
});
|
|
476
|
+
widget.addClass('jp-DebuggerRichVariable');
|
|
477
|
+
widget.id = id;
|
|
478
|
+
widget.title.icon = Debugger.Icons.variableIcon;
|
|
479
|
+
widget.title.label = `${name} - ${service.session?.connection?.name}`;
|
|
480
|
+
widget.title.caption = `${name} - ${service.session?.connection?.path}`;
|
|
481
|
+
void trackerMime.add(widget);
|
|
482
|
+
const disposeWidget = () => {
|
|
483
|
+
widget.dispose();
|
|
484
|
+
variablesModel.changed.disconnect(refreshWidget);
|
|
485
|
+
activeWidget?.disposed.disconnect(disposeWidget);
|
|
486
|
+
};
|
|
487
|
+
const refreshWidget = () => {
|
|
488
|
+
// Refresh the widget only if the active element is the same.
|
|
489
|
+
if (handler.activeWidget === activeWidget) {
|
|
490
|
+
void widget.refresh();
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
widget.disposed.connect(disposeWidget);
|
|
494
|
+
variablesModel.changed.connect(refreshWidget);
|
|
495
|
+
activeWidget?.disposed.connect(disposeWidget);
|
|
496
|
+
|
|
497
|
+
shell.add(widget, 'main', {
|
|
498
|
+
mode: trackerMime.currentWidget ? 'split-right' : 'split-bottom',
|
|
499
|
+
activate: false,
|
|
500
|
+
type: 'Debugger Variables'
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
commands.addCommand(CommandIDs.copyToClipboard, {
|
|
506
|
+
label: trans.__('Copy to Clipboard'),
|
|
507
|
+
caption: trans.__('Copy text representation of the value to clipboard'),
|
|
508
|
+
isEnabled: () => {
|
|
509
|
+
return (
|
|
510
|
+
!!service.session?.isStarted &&
|
|
511
|
+
!!service.model.variables.selectedVariable?.value
|
|
512
|
+
);
|
|
513
|
+
},
|
|
514
|
+
isVisible: () => handler.activeWidget instanceof NotebookPanel,
|
|
515
|
+
execute: async () => {
|
|
516
|
+
const value = service.model.variables.selectedVariable!.value;
|
|
517
|
+
if (value) {
|
|
518
|
+
Clipboard.copyToSystem(value);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Debugger sidebar provider plugin.
|
|
527
|
+
*/
|
|
528
|
+
const sidebar: JupyterFrontEndPlugin<IDebugger.ISidebar> = {
|
|
529
|
+
id: '@jupyterlab/debugger-extension:sidebar',
|
|
530
|
+
provides: IDebuggerSidebar,
|
|
531
|
+
requires: [IDebugger, IEditorServices, ITranslator],
|
|
532
|
+
optional: [IThemeManager, ISettingRegistry],
|
|
533
|
+
autoStart: true,
|
|
534
|
+
activate: async (
|
|
535
|
+
app: JupyterFrontEnd,
|
|
536
|
+
service: IDebugger,
|
|
537
|
+
editorServices: IEditorServices,
|
|
538
|
+
translator: ITranslator,
|
|
539
|
+
themeManager: IThemeManager | null,
|
|
540
|
+
settingRegistry: ISettingRegistry | null
|
|
541
|
+
): Promise<IDebugger.ISidebar> => {
|
|
542
|
+
const { commands } = app;
|
|
543
|
+
const CommandIDs = Debugger.CommandIDs;
|
|
544
|
+
|
|
545
|
+
const callstackCommands = {
|
|
546
|
+
registry: commands,
|
|
547
|
+
continue: CommandIDs.debugContinue,
|
|
548
|
+
terminate: CommandIDs.terminate,
|
|
549
|
+
next: CommandIDs.next,
|
|
550
|
+
stepIn: CommandIDs.stepIn,
|
|
551
|
+
stepOut: CommandIDs.stepOut,
|
|
552
|
+
evaluate: CommandIDs.evaluate
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const breakpointsCommands = {
|
|
556
|
+
registry: commands,
|
|
557
|
+
pauseOnExceptions: CommandIDs.pauseOnExceptions
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const sidebar = new Debugger.Sidebar({
|
|
561
|
+
service,
|
|
562
|
+
callstackCommands,
|
|
563
|
+
breakpointsCommands,
|
|
564
|
+
editorServices,
|
|
565
|
+
themeManager,
|
|
566
|
+
translator
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
if (settingRegistry) {
|
|
570
|
+
const setting = await settingRegistry.load(main.id);
|
|
571
|
+
const updateSettings = (): void => {
|
|
572
|
+
const filters = setting.get('variableFilters').composite as {
|
|
573
|
+
[key: string]: string[];
|
|
574
|
+
};
|
|
575
|
+
const kernel = service.session?.connection?.kernel?.name ?? '';
|
|
576
|
+
if (kernel && filters[kernel]) {
|
|
577
|
+
sidebar.variables.filter = new Set<string>(filters[kernel]);
|
|
578
|
+
}
|
|
579
|
+
const kernelSourcesFilter = setting.get('defaultKernelSourcesFilter')
|
|
580
|
+
.composite as string;
|
|
581
|
+
sidebar.kernelSources.filter = kernelSourcesFilter;
|
|
582
|
+
};
|
|
583
|
+
updateSettings();
|
|
584
|
+
setting.changed.connect(updateSettings);
|
|
585
|
+
service.sessionChanged.connect(updateSettings);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return sidebar;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* The main debugger UI plugin.
|
|
594
|
+
*/
|
|
595
|
+
const main: JupyterFrontEndPlugin<void> = {
|
|
596
|
+
id: '@jupyterlab/debugger-extension:main',
|
|
597
|
+
requires: [IDebugger, IDebuggerSidebar, IEditorServices, ITranslator],
|
|
598
|
+
optional: [
|
|
599
|
+
ICommandPalette,
|
|
600
|
+
IDebuggerSources,
|
|
601
|
+
ILabShell,
|
|
602
|
+
ILayoutRestorer,
|
|
603
|
+
ILoggerRegistry,
|
|
604
|
+
ISettingRegistry
|
|
605
|
+
],
|
|
606
|
+
autoStart: true,
|
|
607
|
+
activate: async (
|
|
608
|
+
app: JupyterFrontEnd,
|
|
609
|
+
service: IDebugger,
|
|
610
|
+
sidebar: IDebugger.ISidebar,
|
|
611
|
+
editorServices: IEditorServices,
|
|
612
|
+
translator: ITranslator,
|
|
613
|
+
palette: ICommandPalette | null,
|
|
614
|
+
debuggerSources: IDebugger.ISources | null,
|
|
615
|
+
labShell: ILabShell | null,
|
|
616
|
+
restorer: ILayoutRestorer | null,
|
|
617
|
+
loggerRegistry: ILoggerRegistry | null,
|
|
618
|
+
settingRegistry: ISettingRegistry | null
|
|
619
|
+
): Promise<void> => {
|
|
620
|
+
const trans = translator.load('jupyterlab');
|
|
621
|
+
const { commands, shell, serviceManager } = app;
|
|
622
|
+
const { kernelspecs } = serviceManager;
|
|
623
|
+
const CommandIDs = Debugger.CommandIDs;
|
|
624
|
+
|
|
625
|
+
// First check if there is a PageConfig override for the extension visibility
|
|
626
|
+
const alwaysShowDebuggerExtension =
|
|
627
|
+
PageConfig.getOption('alwaysShowDebuggerExtension').toLowerCase() ===
|
|
628
|
+
'true';
|
|
629
|
+
if (!alwaysShowDebuggerExtension) {
|
|
630
|
+
// hide the debugger sidebar if no kernel with support for debugging is available
|
|
631
|
+
await kernelspecs.ready;
|
|
632
|
+
const specs = kernelspecs.specs?.kernelspecs;
|
|
633
|
+
if (!specs) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const enabled = Object.keys(specs).some(
|
|
637
|
+
name => !!(specs[name]?.metadata?.['debugger'] ?? false)
|
|
638
|
+
);
|
|
639
|
+
if (!enabled) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// get the mime type of the kernel language for the current debug session
|
|
645
|
+
const getMimeType = async (): Promise<string> => {
|
|
646
|
+
const kernel = service.session?.connection?.kernel;
|
|
647
|
+
if (!kernel) {
|
|
648
|
+
return '';
|
|
649
|
+
}
|
|
650
|
+
const info = (await kernel.info).language_info;
|
|
651
|
+
const name = info.name;
|
|
652
|
+
const mimeType =
|
|
653
|
+
editorServices.mimeTypeService.getMimeTypeByLanguage({ name }) ?? '';
|
|
654
|
+
return mimeType;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const rendermime = new RenderMimeRegistry({ initialFactories });
|
|
658
|
+
|
|
659
|
+
commands.addCommand(CommandIDs.evaluate, {
|
|
660
|
+
label: trans.__('Evaluate Code'),
|
|
661
|
+
caption: trans.__('Evaluate Code'),
|
|
662
|
+
icon: Debugger.Icons.evaluateIcon,
|
|
663
|
+
isEnabled: () => service.hasStoppedThreads(),
|
|
664
|
+
execute: async () => {
|
|
665
|
+
const mimeType = await getMimeType();
|
|
666
|
+
const result = await Debugger.Dialogs.getCode({
|
|
667
|
+
title: trans.__('Evaluate Code'),
|
|
668
|
+
okLabel: trans.__('Evaluate'),
|
|
669
|
+
cancelLabel: trans.__('Cancel'),
|
|
670
|
+
mimeType,
|
|
671
|
+
contentFactory: new CodeCell.ContentFactory({
|
|
672
|
+
editorFactory: options =>
|
|
673
|
+
editorServices.factoryService.newInlineEditor(options)
|
|
674
|
+
}),
|
|
675
|
+
rendermime
|
|
676
|
+
});
|
|
677
|
+
const code = result.value;
|
|
678
|
+
if (!result.button.accept || !code) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const reply = await service.evaluate(code);
|
|
682
|
+
if (reply) {
|
|
683
|
+
const data = reply.result;
|
|
684
|
+
const path = service?.session?.connection?.path;
|
|
685
|
+
const logger = path ? loggerRegistry?.getLogger?.(path) : undefined;
|
|
686
|
+
|
|
687
|
+
if (logger) {
|
|
688
|
+
// print to log console of the notebook currently being debugged
|
|
689
|
+
logger.log({ type: 'text', data, level: logger.level });
|
|
690
|
+
} else {
|
|
691
|
+
// fallback to printing to devtools console
|
|
692
|
+
console.debug(data);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
commands.addCommand(CommandIDs.debugContinue, {
|
|
699
|
+
label: () => {
|
|
700
|
+
return service.hasStoppedThreads()
|
|
701
|
+
? trans.__('Continue')
|
|
702
|
+
: trans.__('Pause');
|
|
703
|
+
},
|
|
704
|
+
caption: () => {
|
|
705
|
+
return service.hasStoppedThreads()
|
|
706
|
+
? trans.__('Continue')
|
|
707
|
+
: trans.__('Pause');
|
|
708
|
+
},
|
|
709
|
+
icon: () => {
|
|
710
|
+
return service.hasStoppedThreads()
|
|
711
|
+
? Debugger.Icons.continueIcon
|
|
712
|
+
: Debugger.Icons.pauseIcon;
|
|
713
|
+
},
|
|
714
|
+
isEnabled: () => service.session?.isStarted ?? false,
|
|
715
|
+
execute: async () => {
|
|
716
|
+
if (service.hasStoppedThreads()) {
|
|
717
|
+
await service.continue();
|
|
718
|
+
} else {
|
|
719
|
+
await service.pause();
|
|
720
|
+
}
|
|
721
|
+
commands.notifyCommandChanged(CommandIDs.debugContinue);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
commands.addCommand(CommandIDs.terminate, {
|
|
726
|
+
label: trans.__('Terminate'),
|
|
727
|
+
caption: trans.__('Terminate'),
|
|
728
|
+
icon: Debugger.Icons.terminateIcon,
|
|
729
|
+
isEnabled: () => service.hasStoppedThreads(),
|
|
730
|
+
execute: async () => {
|
|
731
|
+
await service.restart();
|
|
732
|
+
notifyCommands(app);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
commands.addCommand(CommandIDs.next, {
|
|
737
|
+
label: trans.__('Next'),
|
|
738
|
+
caption: trans.__('Next'),
|
|
739
|
+
icon: Debugger.Icons.stepOverIcon,
|
|
740
|
+
isEnabled: () => service.hasStoppedThreads(),
|
|
741
|
+
execute: async () => {
|
|
742
|
+
await service.next();
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
commands.addCommand(CommandIDs.stepIn, {
|
|
747
|
+
label: trans.__('Step In'),
|
|
748
|
+
caption: trans.__('Step In'),
|
|
749
|
+
icon: Debugger.Icons.stepIntoIcon,
|
|
750
|
+
isEnabled: () => service.hasStoppedThreads(),
|
|
751
|
+
execute: async () => {
|
|
752
|
+
await service.stepIn();
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
commands.addCommand(CommandIDs.stepOut, {
|
|
757
|
+
label: trans.__('Step Out'),
|
|
758
|
+
caption: trans.__('Step Out'),
|
|
759
|
+
icon: Debugger.Icons.stepOutIcon,
|
|
760
|
+
isEnabled: () => service.hasStoppedThreads(),
|
|
761
|
+
execute: async () => {
|
|
762
|
+
await service.stepOut();
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
commands.addCommand(CommandIDs.pauseOnExceptions, {
|
|
767
|
+
label: args => (args.filter as string) || 'Breakpoints on exception',
|
|
768
|
+
caption: args => args.description as string,
|
|
769
|
+
isToggled: args =>
|
|
770
|
+
service.session?.isPausingOnException(args.filter as string) || false,
|
|
771
|
+
isEnabled: () => service.pauseOnExceptionsIsValid(),
|
|
772
|
+
execute: async args => {
|
|
773
|
+
if (args?.filter) {
|
|
774
|
+
let filter = args.filter as string;
|
|
775
|
+
await service.pauseOnExceptionsFilter(filter as string);
|
|
776
|
+
} else {
|
|
777
|
+
let items: string[] = [];
|
|
778
|
+
service.session?.exceptionBreakpointFilters?.forEach(
|
|
779
|
+
availableFilter => {
|
|
780
|
+
items.push(availableFilter.filter);
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
const result = await InputDialog.getMultipleItems({
|
|
784
|
+
title: trans.__('Select a filter for breakpoints on exception'),
|
|
785
|
+
items: items,
|
|
786
|
+
defaults: service.session?.currentExceptionFilters || []
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
let filters = result.button.accept ? result.value : null;
|
|
790
|
+
if (filters !== null) {
|
|
791
|
+
await service.pauseOnExceptions(filters);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
let autoCollapseSidebar = false;
|
|
798
|
+
|
|
799
|
+
if (settingRegistry) {
|
|
800
|
+
const setting = await settingRegistry.load(main.id);
|
|
801
|
+
const updateSettings = (): void => {
|
|
802
|
+
autoCollapseSidebar = setting.get('autoCollapseDebuggerSidebar')
|
|
803
|
+
.composite as boolean;
|
|
804
|
+
};
|
|
805
|
+
updateSettings();
|
|
806
|
+
setting.changed.connect(updateSettings);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
service.eventMessage.connect((_, event): void => {
|
|
810
|
+
notifyCommands(app);
|
|
811
|
+
if (labShell && event.event === 'initialized') {
|
|
812
|
+
labShell.activateById(sidebar.id);
|
|
813
|
+
} else if (
|
|
814
|
+
labShell &&
|
|
815
|
+
sidebar.isVisible &&
|
|
816
|
+
event.event === 'terminated' &&
|
|
817
|
+
autoCollapseSidebar
|
|
818
|
+
) {
|
|
819
|
+
labShell.collapseRight();
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
service.sessionChanged.connect(_ => {
|
|
824
|
+
notifyCommands(app);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
if (restorer) {
|
|
828
|
+
restorer.add(sidebar, 'debugger-sidebar');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
sidebar.node.setAttribute('role', 'region');
|
|
832
|
+
sidebar.node.setAttribute('aria-label', trans.__('Debugger section'));
|
|
833
|
+
|
|
834
|
+
sidebar.title.caption = trans.__('Debugger');
|
|
835
|
+
|
|
836
|
+
shell.add(sidebar, 'right', { type: 'Debugger' });
|
|
837
|
+
|
|
838
|
+
commands.addCommand(CommandIDs.showPanel, {
|
|
839
|
+
label: translator.load('jupyterlab').__('Debugger Panel'),
|
|
840
|
+
execute: () => {
|
|
841
|
+
shell.activateById(sidebar.id);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (palette) {
|
|
846
|
+
const category = trans.__('Debugger');
|
|
847
|
+
[
|
|
848
|
+
CommandIDs.debugContinue,
|
|
849
|
+
CommandIDs.terminate,
|
|
850
|
+
CommandIDs.next,
|
|
851
|
+
CommandIDs.stepIn,
|
|
852
|
+
CommandIDs.stepOut,
|
|
853
|
+
CommandIDs.evaluate,
|
|
854
|
+
CommandIDs.pauseOnExceptions
|
|
855
|
+
].forEach(command => {
|
|
856
|
+
palette.addItem({ command, category });
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (debuggerSources) {
|
|
861
|
+
const { model } = service;
|
|
862
|
+
const readOnlyEditorFactory = new Debugger.ReadOnlyEditorFactory({
|
|
863
|
+
editorServices
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
const onCurrentFrameChanged = (
|
|
867
|
+
_: IDebugger.Model.ICallstack,
|
|
868
|
+
frame: IDebugger.IStackFrame
|
|
869
|
+
): void => {
|
|
870
|
+
debuggerSources
|
|
871
|
+
.find({
|
|
872
|
+
focus: true,
|
|
873
|
+
kernel: service.session?.connection?.kernel?.name ?? '',
|
|
874
|
+
path: service.session?.connection?.path ?? '',
|
|
875
|
+
source: frame?.source?.path ?? ''
|
|
876
|
+
})
|
|
877
|
+
.forEach(editor => {
|
|
878
|
+
requestAnimationFrame(() => {
|
|
879
|
+
void editor.reveal().then(() => {
|
|
880
|
+
const edit = editor.get();
|
|
881
|
+
if (edit) {
|
|
882
|
+
Debugger.EditorHandler.showCurrentLine(edit, frame.line);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
const onSourceOpened = (
|
|
890
|
+
_: IDebugger.Model.ISources | null,
|
|
891
|
+
source: IDebugger.Source,
|
|
892
|
+
breakpoint?: IDebugger.IBreakpoint
|
|
893
|
+
): void => {
|
|
894
|
+
if (!source) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const { content, mimeType, path } = source;
|
|
898
|
+
const results = debuggerSources.find({
|
|
899
|
+
focus: true,
|
|
900
|
+
kernel: service.session?.connection?.kernel?.name ?? '',
|
|
901
|
+
path: service.session?.connection?.path ?? '',
|
|
902
|
+
source: path
|
|
903
|
+
});
|
|
904
|
+
if (results.length > 0) {
|
|
905
|
+
if (breakpoint && typeof breakpoint.line !== 'undefined') {
|
|
906
|
+
results.forEach(editor => {
|
|
907
|
+
void editor.reveal().then(() => {
|
|
908
|
+
editor.get()?.revealPosition({
|
|
909
|
+
line: (breakpoint.line as number) - 1,
|
|
910
|
+
column: breakpoint.column || 0
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const editorWrapper = readOnlyEditorFactory.createNewEditor({
|
|
918
|
+
content,
|
|
919
|
+
mimeType,
|
|
920
|
+
path
|
|
921
|
+
});
|
|
922
|
+
const editor = editorWrapper.editor;
|
|
923
|
+
const editorHandler = new Debugger.EditorHandler({
|
|
924
|
+
debuggerService: service,
|
|
925
|
+
editorReady: () => Promise.resolve(editor),
|
|
926
|
+
getEditor: () => editor,
|
|
927
|
+
path,
|
|
928
|
+
src: editor.model.sharedModel
|
|
929
|
+
});
|
|
930
|
+
editorWrapper.disposed.connect(() => editorHandler.dispose());
|
|
931
|
+
|
|
932
|
+
debuggerSources.open({
|
|
933
|
+
label: PathExt.basename(path),
|
|
934
|
+
caption: path,
|
|
935
|
+
editorWrapper
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
const frame = service.model.callstack.frame;
|
|
939
|
+
if (frame) {
|
|
940
|
+
Debugger.EditorHandler.showCurrentLine(editor, frame.line);
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const onKernelSourceOpened = (
|
|
945
|
+
_: IDebugger.Model.IKernelSources | null,
|
|
946
|
+
source: IDebugger.Source,
|
|
947
|
+
breakpoint?: IDebugger.IBreakpoint
|
|
948
|
+
): void => {
|
|
949
|
+
if (!source) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
onSourceOpened(null, source, breakpoint);
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
model.callstack.currentFrameChanged.connect(onCurrentFrameChanged);
|
|
956
|
+
model.sources.currentSourceOpened.connect(onSourceOpened);
|
|
957
|
+
model.kernelSources.kernelSourceOpened.connect(onKernelSourceOpened);
|
|
958
|
+
model.breakpoints.clicked.connect(async (_, breakpoint) => {
|
|
959
|
+
const path = breakpoint.source?.path;
|
|
960
|
+
const source = await service.getSource({
|
|
961
|
+
sourceReference: 0,
|
|
962
|
+
path
|
|
963
|
+
});
|
|
964
|
+
onSourceOpened(null, source, breakpoint);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Export the plugins as default.
|
|
972
|
+
*/
|
|
973
|
+
const plugins: JupyterFrontEndPlugin<any>[] = [
|
|
974
|
+
service,
|
|
975
|
+
consoles,
|
|
976
|
+
files,
|
|
977
|
+
notebooks,
|
|
978
|
+
variables,
|
|
979
|
+
sidebar,
|
|
980
|
+
main,
|
|
981
|
+
sources,
|
|
982
|
+
configuration
|
|
983
|
+
];
|
|
984
|
+
|
|
985
|
+
export default plugins;
|