@jupyterlab/debugger-extension 4.0.0-alpha.2 → 4.0.0-alpha.20

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