@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/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;