@jupyterlab/debugger 4.0.0-alpha.9 → 4.0.0-beta.1

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.
Files changed (123) hide show
  1. package/lib/debugger.d.ts +3 -1
  2. package/lib/debugger.js +5 -3
  3. package/lib/debugger.js.map +1 -1
  4. package/lib/dialogs/evaluate.d.ts +5 -0
  5. package/lib/dialogs/evaluate.js +16 -6
  6. package/lib/dialogs/evaluate.js.map +1 -1
  7. package/lib/factory.d.ts +1 -1
  8. package/lib/factory.js +15 -9
  9. package/lib/factory.js.map +1 -1
  10. package/lib/handler.js +15 -7
  11. package/lib/handler.js.map +1 -1
  12. package/lib/handlers/console.js +9 -3
  13. package/lib/handlers/console.js.map +1 -1
  14. package/lib/handlers/editor.d.ts +26 -12
  15. package/lib/handlers/editor.js +166 -89
  16. package/lib/handlers/editor.js.map +1 -1
  17. package/lib/handlers/file.js +6 -2
  18. package/lib/handlers/file.js.map +1 -1
  19. package/lib/handlers/notebook.d.ts +0 -7
  20. package/lib/handlers/notebook.js +11 -17
  21. package/lib/handlers/notebook.js.map +1 -1
  22. package/lib/icons.d.ts +2 -1
  23. package/lib/icons.js +6 -1
  24. package/lib/icons.js.map +1 -1
  25. package/lib/model.js +1 -0
  26. package/lib/model.js.map +1 -1
  27. package/lib/panels/breakpoints/index.d.ts +2 -2
  28. package/lib/panels/breakpoints/index.js +9 -7
  29. package/lib/panels/breakpoints/index.js.map +1 -1
  30. package/lib/panels/breakpoints/pauseonexceptions.d.ts +53 -0
  31. package/lib/panels/breakpoints/pauseonexceptions.js +98 -0
  32. package/lib/panels/breakpoints/pauseonexceptions.js.map +1 -0
  33. package/lib/panels/callstack/index.d.ts +1 -1
  34. package/lib/panels/kernelSources/body.d.ts +5 -7
  35. package/lib/panels/kernelSources/body.js +37 -47
  36. package/lib/panels/kernelSources/body.js.map +1 -1
  37. package/lib/panels/kernelSources/filter.d.ts +1 -2
  38. package/lib/panels/kernelSources/filter.js +2 -2
  39. package/lib/panels/kernelSources/filter.js.map +1 -1
  40. package/lib/panels/kernelSources/index.js +1 -1
  41. package/lib/panels/kernelSources/index.js.map +1 -1
  42. package/lib/panels/kernelSources/model.d.ts +10 -1
  43. package/lib/panels/kernelSources/model.js +19 -1
  44. package/lib/panels/kernelSources/model.js.map +1 -1
  45. package/lib/panels/sources/body.js +5 -3
  46. package/lib/panels/sources/body.js.map +1 -1
  47. package/lib/panels/sources/sourcepath.js +1 -1
  48. package/lib/panels/sources/sourcepath.js.map +1 -1
  49. package/lib/panels/variables/grid.d.ts +23 -10
  50. package/lib/panels/variables/grid.js +42 -367
  51. package/lib/panels/variables/grid.js.map +1 -1
  52. package/lib/panels/variables/gridpanel.d.ts +138 -0
  53. package/lib/panels/variables/gridpanel.js +362 -0
  54. package/lib/panels/variables/gridpanel.js.map +1 -0
  55. package/lib/panels/variables/mimerenderer.js +9 -1
  56. package/lib/panels/variables/mimerenderer.js.map +1 -1
  57. package/lib/panels/variables/scope.js +4 -0
  58. package/lib/panels/variables/scope.js.map +1 -1
  59. package/lib/panels/variables/tree.d.ts +1 -0
  60. package/lib/panels/variables/tree.js +136 -47
  61. package/lib/panels/variables/tree.js.map +1 -1
  62. package/lib/service.d.ts +20 -6
  63. package/lib/service.js +84 -59
  64. package/lib/service.js.map +1 -1
  65. package/lib/session.d.ts +20 -6
  66. package/lib/session.js +59 -11
  67. package/lib/session.js.map +1 -1
  68. package/lib/sidebar.js +1 -1
  69. package/lib/sources.d.ts +2 -2
  70. package/lib/sources.js +32 -17
  71. package/lib/sources.js.map +1 -1
  72. package/lib/tokens.d.ts +57 -13
  73. package/lib/tokens.js.map +1 -1
  74. package/package.json +38 -46
  75. package/src/config.ts +78 -0
  76. package/src/debugger.ts +154 -0
  77. package/src/dialogs/evaluate.ts +144 -0
  78. package/src/factory.ts +72 -0
  79. package/src/handler.ts +528 -0
  80. package/src/handlers/console.ts +118 -0
  81. package/src/handlers/editor.ts +469 -0
  82. package/src/handlers/file.ts +86 -0
  83. package/src/handlers/notebook.ts +128 -0
  84. package/src/hash.ts +69 -0
  85. package/src/icons.ts +64 -0
  86. package/src/index.ts +16 -0
  87. package/src/model.ts +155 -0
  88. package/src/panels/breakpoints/body.tsx +145 -0
  89. package/src/panels/breakpoints/index.ts +116 -0
  90. package/src/panels/breakpoints/model.ts +74 -0
  91. package/src/panels/breakpoints/pauseonexceptions.tsx +141 -0
  92. package/src/panels/callstack/body.tsx +96 -0
  93. package/src/panels/callstack/index.ts +152 -0
  94. package/src/panels/callstack/model.ts +86 -0
  95. package/src/panels/kernelSources/body.tsx +139 -0
  96. package/src/panels/kernelSources/filter.tsx +44 -0
  97. package/src/panels/kernelSources/index.tsx +106 -0
  98. package/src/panels/kernelSources/model.ts +160 -0
  99. package/src/panels/sources/body.ts +161 -0
  100. package/src/panels/sources/index.tsx +85 -0
  101. package/src/panels/sources/model.ts +94 -0
  102. package/src/panels/sources/sourcepath.tsx +31 -0
  103. package/src/panels/variables/grid.ts +145 -0
  104. package/src/panels/variables/gridpanel.ts +453 -0
  105. package/src/panels/variables/index.ts +199 -0
  106. package/src/panels/variables/mimerenderer.ts +125 -0
  107. package/src/panels/variables/model.ts +61 -0
  108. package/src/panels/variables/scope.tsx +132 -0
  109. package/src/panels/variables/tree.tsx +521 -0
  110. package/src/service.ts +1009 -0
  111. package/src/session.ts +342 -0
  112. package/src/sidebar.ts +194 -0
  113. package/src/sources.ts +327 -0
  114. package/src/svg.d.ts +9 -0
  115. package/src/tokens.ts +1071 -0
  116. package/style/breakpoints.css +21 -12
  117. package/style/callstack.css +2 -2
  118. package/style/icons/exceptions.svg +10 -0
  119. package/style/index.css +1 -1
  120. package/style/index.js +1 -1
  121. package/style/kernelSources.css +3 -9
  122. package/style/sources.css +1 -1
  123. package/style/variables.css +68 -21
package/src/service.ts ADDED
@@ -0,0 +1,1009 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { KernelSpec, Session } from '@jupyterlab/services';
5
+
6
+ import {
7
+ ITranslator,
8
+ nullTranslator,
9
+ TranslationBundle
10
+ } from '@jupyterlab/translation';
11
+
12
+ import { IDisposable } from '@lumino/disposable';
13
+
14
+ import { ISignal, Signal } from '@lumino/signaling';
15
+
16
+ import { DebugProtocol } from '@vscode/debugprotocol';
17
+
18
+ import { Debugger } from './debugger';
19
+
20
+ import { VariablesModel } from './panels/variables/model';
21
+
22
+ import { IDebugger } from './tokens';
23
+
24
+ /**
25
+ * A concrete implementation of the IDebugger interface.
26
+ */
27
+ export class DebuggerService implements IDebugger, IDisposable {
28
+ /**
29
+ * Instantiate a new DebuggerService.
30
+ *
31
+ * @param options The instantiation options for a DebuggerService.
32
+ */
33
+ constructor(options: DebuggerService.IOptions) {
34
+ this._config = options.config;
35
+ // Avoids setting session with invalid client
36
+ // session should be set only when a notebook or
37
+ // a console get the focus.
38
+ // TODO: also checks that the notebook or console
39
+ // runs a kernel with debugging ability
40
+ this._session = null;
41
+ this._specsManager = options.specsManager ?? null;
42
+ this._model = new Debugger.Model();
43
+ this._debuggerSources = options.debuggerSources ?? null;
44
+ this._trans = (options.translator || nullTranslator).load('jupyterlab');
45
+ }
46
+
47
+ /**
48
+ * Signal emitted for debug event messages.
49
+ */
50
+ get eventMessage(): ISignal<IDebugger, IDebugger.ISession.Event> {
51
+ return this._eventMessage;
52
+ }
53
+
54
+ /**
55
+ * Get debugger config.
56
+ */
57
+ get config(): IDebugger.IConfig {
58
+ return this._config;
59
+ }
60
+
61
+ /**
62
+ * Whether the debug service is disposed.
63
+ */
64
+ get isDisposed(): boolean {
65
+ return this._isDisposed;
66
+ }
67
+
68
+ /**
69
+ * Whether the current debugger is started.
70
+ */
71
+ get isStarted(): boolean {
72
+ return this._session?.isStarted ?? false;
73
+ }
74
+
75
+ /**
76
+ * A signal emitted when the pause on exception filter changes.
77
+ */
78
+ get pauseOnExceptionChanged(): Signal<IDebugger, void> {
79
+ return this._pauseOnExceptionChanged;
80
+ }
81
+
82
+ /**
83
+ * Returns the debugger service's model.
84
+ */
85
+ get model(): IDebugger.Model.IService {
86
+ return this._model;
87
+ }
88
+
89
+ /**
90
+ * Returns the current debug session.
91
+ */
92
+ get session(): IDebugger.ISession | null {
93
+ return this._session;
94
+ }
95
+
96
+ /**
97
+ * Sets the current debug session to the given parameter.
98
+ *
99
+ * @param session - the new debugger session.
100
+ */
101
+ set session(session: IDebugger.ISession | null) {
102
+ if (this._session === session) {
103
+ return;
104
+ }
105
+ if (this._session) {
106
+ this._session.dispose();
107
+ }
108
+ this._session = session;
109
+
110
+ this._session?.eventMessage.connect((_, event) => {
111
+ if (event.event === 'stopped') {
112
+ this._model.stoppedThreads.add(event.body.threadId);
113
+ void this._getAllFrames();
114
+ } else if (event.event === 'continued') {
115
+ this._model.stoppedThreads.delete(event.body.threadId);
116
+ this._clearModel();
117
+ this._clearSignals();
118
+ }
119
+ this._eventMessage.emit(event);
120
+ });
121
+
122
+ this._sessionChanged.emit(session);
123
+ }
124
+
125
+ /**
126
+ * Signal emitted upon session changed.
127
+ */
128
+ get sessionChanged(): ISignal<IDebugger, IDebugger.ISession | null> {
129
+ return this._sessionChanged;
130
+ }
131
+
132
+ /**
133
+ * Dispose the debug service.
134
+ */
135
+ dispose(): void {
136
+ if (this.isDisposed) {
137
+ return;
138
+ }
139
+ this._isDisposed = true;
140
+ Signal.clearData(this);
141
+ }
142
+
143
+ /**
144
+ * Computes an id based on the given code.
145
+ *
146
+ * @param code The source code.
147
+ */
148
+ getCodeId(code: string): string {
149
+ try {
150
+ return this._config.getCodeId(
151
+ code,
152
+ this.session?.connection?.kernel?.name ?? ''
153
+ );
154
+ } catch {
155
+ return '';
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Whether there exists a thread in stopped state.
161
+ */
162
+ hasStoppedThreads(): boolean {
163
+ return this._model?.stoppedThreads.size > 0 ?? false;
164
+ }
165
+
166
+ /**
167
+ * Request whether debugging is available for the session connection.
168
+ *
169
+ * @param connection The session connection.
170
+ */
171
+ async isAvailable(connection: Session.ISessionConnection): Promise<boolean> {
172
+ if (!this._specsManager) {
173
+ return true;
174
+ }
175
+ await this._specsManager.ready;
176
+ const kernel = connection?.kernel;
177
+ if (!kernel) {
178
+ return false;
179
+ }
180
+ const name = kernel.name;
181
+ if (!this._specsManager.specs?.kernelspecs[name]) {
182
+ return true;
183
+ }
184
+ return !!(
185
+ this._specsManager.specs.kernelspecs[name]?.metadata?.['debugger'] ??
186
+ false
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Clear all the breakpoints for the current session.
192
+ */
193
+ async clearBreakpoints(): Promise<void> {
194
+ if (this.session?.isStarted !== true) {
195
+ return;
196
+ }
197
+
198
+ this._model.breakpoints.breakpoints.forEach((_, path, map) => {
199
+ void this._setBreakpoints([], path);
200
+ });
201
+
202
+ let bpMap = new Map<string, IDebugger.IBreakpoint[]>();
203
+ this._model.breakpoints.restoreBreakpoints(bpMap);
204
+ }
205
+
206
+ /**
207
+ * Continues the execution of the current thread.
208
+ */
209
+ async continue(): Promise<void> {
210
+ try {
211
+ if (!this.session) {
212
+ throw new Error('No active debugger session');
213
+ }
214
+ await this.session.sendRequest('continue', {
215
+ threadId: this._currentThread()
216
+ });
217
+ this._model.stoppedThreads.delete(this._currentThread());
218
+ this._clearModel();
219
+ this._clearSignals();
220
+ } catch (err) {
221
+ console.error('Error:', err.message);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Retrieve the content of a source file.
227
+ *
228
+ * @param source The source object containing the path to the file.
229
+ */
230
+ async getSource(source: DebugProtocol.Source): Promise<IDebugger.Source> {
231
+ if (!this.session) {
232
+ throw new Error('No active debugger session');
233
+ }
234
+ const reply = await this.session.sendRequest('source', {
235
+ source,
236
+ sourceReference: source.sourceReference ?? 0
237
+ });
238
+ return { ...reply.body, path: source.path ?? '' };
239
+ }
240
+
241
+ /**
242
+ * Evaluate an expression.
243
+ *
244
+ * @param expression The expression to evaluate as a string.
245
+ */
246
+ async evaluate(
247
+ expression: string
248
+ ): Promise<DebugProtocol.EvaluateResponse['body'] | null> {
249
+ if (!this.session) {
250
+ throw new Error('No active debugger session');
251
+ }
252
+ const frameId = this.model.callstack.frame?.id;
253
+ const reply = await this.session.sendRequest('evaluate', {
254
+ context: 'repl',
255
+ expression,
256
+ frameId
257
+ });
258
+ if (!reply.success) {
259
+ return null;
260
+ }
261
+ // get the frames to retrieve the latest state of the variables
262
+ this._clearModel();
263
+ await this._getAllFrames();
264
+
265
+ return reply.body;
266
+ }
267
+
268
+ /**
269
+ * Makes the current thread run again for one step.
270
+ */
271
+ async next(): Promise<void> {
272
+ try {
273
+ if (!this.session) {
274
+ throw new Error('No active debugger session');
275
+ }
276
+ await this.session.sendRequest('next', {
277
+ threadId: this._currentThread()
278
+ });
279
+ } catch (err) {
280
+ console.error('Error:', err.message);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Request rich representation of a variable.
286
+ *
287
+ * @param variableName The variable name to request
288
+ * @param frameId The current frame id in which to request the variable
289
+ * @returns The mime renderer data model
290
+ */
291
+ async inspectRichVariable(
292
+ variableName: string,
293
+ frameId?: number
294
+ ): Promise<IDebugger.IRichVariable> {
295
+ if (!this.session) {
296
+ throw new Error('No active debugger session');
297
+ }
298
+ const reply = await this.session.sendRequest('richInspectVariables', {
299
+ variableName,
300
+ frameId
301
+ });
302
+ if (reply.success) {
303
+ return reply.body;
304
+ } else {
305
+ throw new Error(reply.message);
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Request variables for a given variable reference.
311
+ *
312
+ * @param variablesReference The variable reference to request.
313
+ */
314
+ async inspectVariable(
315
+ variablesReference: number
316
+ ): Promise<DebugProtocol.Variable[]> {
317
+ if (!this.session) {
318
+ throw new Error('No active debugger session');
319
+ }
320
+ const reply = await this.session.sendRequest('variables', {
321
+ variablesReference
322
+ });
323
+ if (reply.success) {
324
+ return reply.body.variables;
325
+ } else {
326
+ throw new Error(reply.message);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Requests all the defined variables and display them in the
332
+ * table view.
333
+ */
334
+ async displayDefinedVariables(): Promise<void> {
335
+ if (!this.session) {
336
+ throw new Error('No active debugger session');
337
+ }
338
+ const inspectReply = await this.session.sendRequest('inspectVariables', {});
339
+ const variables = inspectReply.body.variables;
340
+
341
+ const variableScopes = [
342
+ {
343
+ name: this._trans.__('Globals'),
344
+ variables: variables
345
+ }
346
+ ];
347
+ this._model.variables.scopes = variableScopes;
348
+ }
349
+
350
+ async displayModules(): Promise<void> {
351
+ if (!this.session) {
352
+ throw new Error('No active debugger session');
353
+ }
354
+
355
+ const modules = await this.session.sendRequest('modules', {});
356
+ this._model.kernelSources.kernelSources = modules.body.modules.map(
357
+ module => {
358
+ return {
359
+ name: module.name as string,
360
+ path: module.path as string
361
+ };
362
+ }
363
+ );
364
+ }
365
+
366
+ /**
367
+ * Restart the debugger.
368
+ */
369
+ async restart(): Promise<void> {
370
+ const { breakpoints } = this._model.breakpoints;
371
+ await this.stop();
372
+ await this.start();
373
+ await this._restoreBreakpoints(breakpoints);
374
+ }
375
+
376
+ /**
377
+ * Restore the state of a debug session.
378
+ *
379
+ * @param autoStart - If true, starts the debugger if it has not been started.
380
+ */
381
+ async restoreState(autoStart: boolean): Promise<void> {
382
+ if (!this.model || !this.session) {
383
+ return;
384
+ }
385
+
386
+ const reply = await this.session.restoreState();
387
+ const { body } = reply;
388
+ const breakpoints = this._mapBreakpoints(body.breakpoints);
389
+ const stoppedThreads = new Set(body.stoppedThreads);
390
+
391
+ this._model.hasRichVariableRendering = body.richRendering === true;
392
+ this._config.setHashParams({
393
+ kernel: this.session?.connection?.kernel?.name ?? '',
394
+ method: body.hashMethod,
395
+ seed: body.hashSeed
396
+ });
397
+ this._config.setTmpFileParams({
398
+ kernel: this.session?.connection?.kernel?.name ?? '',
399
+ prefix: body.tmpFilePrefix,
400
+ suffix: body.tmpFileSuffix
401
+ });
402
+
403
+ this._model.stoppedThreads = stoppedThreads;
404
+
405
+ if (!this.isStarted && (autoStart || stoppedThreads.size !== 0)) {
406
+ await this.start();
407
+ }
408
+
409
+ if (this.isStarted || autoStart) {
410
+ this._model.title = this.isStarted
411
+ ? this.session?.connection?.name || '-'
412
+ : '-';
413
+ }
414
+
415
+ if (this._debuggerSources) {
416
+ const filtered = this._filterBreakpoints(breakpoints);
417
+ this._model.breakpoints.restoreBreakpoints(filtered);
418
+ } else {
419
+ this._model.breakpoints.restoreBreakpoints(breakpoints);
420
+ }
421
+
422
+ if (stoppedThreads.size !== 0) {
423
+ await this._getAllFrames();
424
+ } else if (this.isStarted) {
425
+ this._clearModel();
426
+ this._clearSignals();
427
+ }
428
+
429
+ // Send the currentExceptionFilters to debugger.
430
+ if (this.session.currentExceptionFilters) {
431
+ await this.pauseOnExceptions(this.session.currentExceptionFilters);
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Starts a debugger.
437
+ * Precondition: !isStarted
438
+ */
439
+ start(): Promise<void> {
440
+ if (!this.session) {
441
+ throw new Error('No active debugger session');
442
+ }
443
+ return this.session.start();
444
+ }
445
+
446
+ /**
447
+ * Makes the current thread pause if possible.
448
+ */
449
+ async pause(): Promise<void> {
450
+ try {
451
+ if (!this.session) {
452
+ throw new Error('No active debugger session');
453
+ }
454
+ await this.session.sendRequest('pause', {
455
+ threadId: this._currentThread()
456
+ });
457
+ } catch (err) {
458
+ console.error('Error:', err.message);
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Makes the current thread step in a function / method if possible.
464
+ */
465
+ async stepIn(): Promise<void> {
466
+ try {
467
+ if (!this.session) {
468
+ throw new Error('No active debugger session');
469
+ }
470
+ await this.session.sendRequest('stepIn', {
471
+ threadId: this._currentThread()
472
+ });
473
+ } catch (err) {
474
+ console.error('Error:', err.message);
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Makes the current thread step out a function / method if possible.
480
+ */
481
+ async stepOut(): Promise<void> {
482
+ try {
483
+ if (!this.session) {
484
+ throw new Error('No active debugger session');
485
+ }
486
+ await this.session.sendRequest('stepOut', {
487
+ threadId: this._currentThread()
488
+ });
489
+ } catch (err) {
490
+ console.error('Error:', err.message);
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Stops the debugger.
496
+ * Precondition: isStarted
497
+ */
498
+ async stop(): Promise<void> {
499
+ if (!this.session) {
500
+ throw new Error('No active debugger session');
501
+ }
502
+ await this.session.stop();
503
+ if (this._model) {
504
+ this._model.clear();
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Update all breakpoints at once.
510
+ *
511
+ * @param code - The code in the cell where the breakpoints are set.
512
+ * @param breakpoints - The list of breakpoints to set.
513
+ * @param path - Optional path to the file where to set the breakpoints.
514
+ */
515
+ async updateBreakpoints(
516
+ code: string,
517
+ breakpoints: IDebugger.IBreakpoint[],
518
+ path?: string
519
+ ): Promise<void> {
520
+ if (!this.session?.isStarted) {
521
+ return;
522
+ }
523
+
524
+ if (!path) {
525
+ path = (await this._dumpCell(code)).body.sourcePath;
526
+ }
527
+
528
+ const state = await this.session.restoreState();
529
+ const localBreakpoints = breakpoints
530
+ .filter(({ line }) => typeof line === 'number')
531
+ .map(({ line }) => ({ line: line! }));
532
+ const remoteBreakpoints = this._mapBreakpoints(state.body.breakpoints);
533
+
534
+ // Set the local copy of breakpoints to reflect only editors that exist.
535
+ if (this._debuggerSources) {
536
+ const filtered = this._filterBreakpoints(remoteBreakpoints);
537
+ this._model.breakpoints.restoreBreakpoints(filtered);
538
+ } else {
539
+ this._model.breakpoints.restoreBreakpoints(remoteBreakpoints);
540
+ }
541
+
542
+ // Removes duplicated breakpoints. It is better to do it here than
543
+ // in the editor, because the kernel can change the line of a
544
+ // breakpoint (when you attemp to set a breakpoint on an empty
545
+ // line for instance).
546
+ let addedLines = new Set<number>();
547
+ // Set the kernel's breakpoints for this path.
548
+ const reply = await this._setBreakpoints(localBreakpoints, path);
549
+ const updatedBreakpoints = reply.body.breakpoints.filter((val, _, arr) => {
550
+ const cond1 = arr.findIndex(el => el.line === val.line) > -1;
551
+ const cond2 = !addedLines.has(val.line!);
552
+ addedLines.add(val.line!);
553
+ return cond1 && cond2;
554
+ });
555
+
556
+ // Update the local model and finish kernel configuration.
557
+ this._model.breakpoints.setBreakpoints(path, updatedBreakpoints);
558
+ await this.session.sendRequest('configurationDone', {});
559
+ }
560
+
561
+ /**
562
+ * Determines if pausing on exceptions is supported by the kernel
563
+ */
564
+ pauseOnExceptionsIsValid(): boolean {
565
+ if (this.isStarted) {
566
+ if (this.session?.exceptionBreakpointFilters?.length !== 0) {
567
+ return true;
568
+ }
569
+ }
570
+ return false;
571
+ }
572
+
573
+ /**
574
+ * Add or remove a filter from the current used filters.
575
+ *
576
+ * @param exceptionFilter - The filter to add or remove from current filters.
577
+ */
578
+ async pauseOnExceptionsFilter(exceptionFilter: string): Promise<void> {
579
+ if (!this.session?.isStarted) {
580
+ return;
581
+ }
582
+ let exceptionFilters = this.session.currentExceptionFilters;
583
+ if (this.session.isPausingOnException(exceptionFilter)) {
584
+ const index = exceptionFilters.indexOf(exceptionFilter);
585
+ exceptionFilters.splice(index, 1);
586
+ } else {
587
+ exceptionFilters?.push(exceptionFilter);
588
+ }
589
+ await this.pauseOnExceptions(exceptionFilters);
590
+ }
591
+
592
+ /**
593
+ * Enable or disable pausing on exceptions.
594
+ *
595
+ * @param exceptionFilters - The filters to use for the current debugging session.
596
+ */
597
+ async pauseOnExceptions(exceptionFilters: string[]): Promise<void> {
598
+ if (!this.session?.isStarted) {
599
+ return;
600
+ }
601
+ const exceptionBreakpointFilters =
602
+ this.session.exceptionBreakpointFilters?.map(e => e.filter) || [];
603
+ let options: DebugProtocol.SetExceptionBreakpointsArguments = {
604
+ filters: []
605
+ };
606
+ exceptionFilters.forEach(filter => {
607
+ if (exceptionBreakpointFilters.includes(filter)) {
608
+ options.filters.push(filter);
609
+ }
610
+ });
611
+ this.session.currentExceptionFilters = options.filters;
612
+ await this.session.sendRequest('setExceptionBreakpoints', options);
613
+ this._pauseOnExceptionChanged.emit();
614
+ }
615
+
616
+ /**
617
+ * Get the debugger state
618
+ *
619
+ * @returns Debugger state
620
+ */
621
+ getDebuggerState(): IDebugger.State {
622
+ const breakpoints = this._model.breakpoints.breakpoints;
623
+ let cells: string[] = [];
624
+ if (this._debuggerSources) {
625
+ for (const id of breakpoints.keys()) {
626
+ const editorList = this._debuggerSources.find({
627
+ focus: false,
628
+ kernel: this.session?.connection?.kernel?.name ?? '',
629
+ path: this._session?.connection?.path ?? '',
630
+ source: id
631
+ });
632
+ const tmpCells = editorList.map(e => e.src.getSource());
633
+ cells = cells.concat(tmpCells);
634
+ }
635
+ }
636
+ return { cells, breakpoints };
637
+ }
638
+
639
+ /**
640
+ * Restore the debugger state
641
+ *
642
+ * @param state Debugger state
643
+ * @returns Whether the state has been restored successfully or not
644
+ */
645
+ async restoreDebuggerState(state: IDebugger.State): Promise<boolean> {
646
+ await this.start();
647
+
648
+ for (const cell of state.cells) {
649
+ await this._dumpCell(cell);
650
+ }
651
+
652
+ const breakpoints = new Map<string, IDebugger.IBreakpoint[]>();
653
+ const kernel = this.session?.connection?.kernel?.name ?? '';
654
+ const { prefix, suffix } = this._config.getTmpFileParams(kernel);
655
+ for (const item of state.breakpoints) {
656
+ const [id, list] = item;
657
+ const unsuffixedId = id.substr(0, id.length - suffix.length);
658
+ const codeHash = unsuffixedId.substr(unsuffixedId.lastIndexOf('/') + 1);
659
+ const newId = prefix.concat(codeHash).concat(suffix);
660
+ breakpoints.set(newId, list);
661
+ }
662
+
663
+ await this._restoreBreakpoints(breakpoints);
664
+ const config = await this.session!.sendRequest('configurationDone', {});
665
+ await this.restoreState(false);
666
+ return config.success;
667
+ }
668
+
669
+ /**
670
+ * Clear the current model.
671
+ */
672
+ private _clearModel(): void {
673
+ this._model.callstack.frames = [];
674
+ this._model.variables.scopes = [];
675
+ }
676
+
677
+ /**
678
+ * Clear the signals set on the model.
679
+ */
680
+ private _clearSignals(): void {
681
+ this._model.callstack.currentFrameChanged.disconnect(
682
+ this._onCurrentFrameChanged,
683
+ this
684
+ );
685
+ this._model.variables.variableExpanded.disconnect(
686
+ this._onVariableExpanded,
687
+ this
688
+ );
689
+ }
690
+
691
+ /**
692
+ * Map a list of scopes to a list of variables.
693
+ *
694
+ * @param scopes The list of scopes.
695
+ * @param variables The list of variables.
696
+ */
697
+ private _convertScopes(
698
+ scopes: DebugProtocol.Scope[],
699
+ variables: DebugProtocol.Variable[][]
700
+ ): IDebugger.IScope[] {
701
+ if (!variables || !scopes) {
702
+ return [];
703
+ }
704
+ return scopes.map((scope, i) => {
705
+ return {
706
+ name: scope.name,
707
+ variables: variables[i].map(variable => {
708
+ return { ...variable };
709
+ })
710
+ };
711
+ });
712
+ }
713
+
714
+ /**
715
+ * Get the current thread from the model.
716
+ */
717
+ private _currentThread(): number {
718
+ // TODO: ask the model for the current thread ID
719
+ return 1;
720
+ }
721
+
722
+ /**
723
+ * Dump the content of a cell.
724
+ *
725
+ * @param code The source code to dump.
726
+ */
727
+ private async _dumpCell(
728
+ code: string
729
+ ): Promise<IDebugger.ISession.IDumpCellResponse> {
730
+ if (!this.session) {
731
+ throw new Error('No active debugger session');
732
+ }
733
+ return this.session.sendRequest('dumpCell', { code });
734
+ }
735
+
736
+ /**
737
+ * Filter breakpoints and only return those associated with a known editor.
738
+ *
739
+ * @param breakpoints - Map of breakpoints.
740
+ *
741
+ */
742
+ private _filterBreakpoints(
743
+ breakpoints: Map<string, IDebugger.IBreakpoint[]>
744
+ ): Map<string, IDebugger.IBreakpoint[]> {
745
+ if (!this._debuggerSources) {
746
+ return breakpoints;
747
+ }
748
+ let bpMapForRestore = new Map<string, IDebugger.IBreakpoint[]>();
749
+ for (const collection of breakpoints) {
750
+ const [id, list] = collection;
751
+ list.forEach(() => {
752
+ this._debuggerSources!.find({
753
+ focus: false,
754
+ kernel: this.session?.connection?.kernel?.name ?? '',
755
+ path: this._session?.connection?.path ?? '',
756
+ source: id
757
+ }).forEach(() => {
758
+ if (list.length > 0) {
759
+ bpMapForRestore.set(id, list);
760
+ }
761
+ });
762
+ });
763
+ }
764
+ return bpMapForRestore;
765
+ }
766
+
767
+ /**
768
+ * Get all the frames from the kernel.
769
+ */
770
+ private async _getAllFrames(): Promise<void> {
771
+ this._model.callstack.currentFrameChanged.connect(
772
+ this._onCurrentFrameChanged,
773
+ this
774
+ );
775
+ this._model.variables.variableExpanded.connect(
776
+ this._onVariableExpanded,
777
+ this
778
+ );
779
+
780
+ const stackFrames = await this._getFrames(this._currentThread());
781
+ this._model.callstack.frames = stackFrames;
782
+ }
783
+
784
+ /**
785
+ * Get all the frames for the given thread id.
786
+ *
787
+ * @param threadId The thread id.
788
+ */
789
+ private async _getFrames(
790
+ threadId: number
791
+ ): Promise<DebugProtocol.StackFrame[]> {
792
+ if (!this.session) {
793
+ throw new Error('No active debugger session');
794
+ }
795
+ const reply = await this.session.sendRequest('stackTrace', {
796
+ threadId
797
+ });
798
+ const stackFrames = reply.body.stackFrames;
799
+ return stackFrames;
800
+ }
801
+
802
+ /**
803
+ * Get all the scopes for the given frame.
804
+ *
805
+ * @param frame The frame.
806
+ */
807
+ private async _getScopes(
808
+ frame: DebugProtocol.StackFrame
809
+ ): Promise<DebugProtocol.Scope[]> {
810
+ if (!this.session) {
811
+ throw new Error('No active debugger session');
812
+ }
813
+ if (!frame) {
814
+ return [];
815
+ }
816
+ const reply = await this.session.sendRequest('scopes', {
817
+ frameId: frame.id
818
+ });
819
+ return reply.body.scopes;
820
+ }
821
+
822
+ /**
823
+ * Get the variables for a given scope.
824
+ *
825
+ * @param scope The scope to get variables for.
826
+ */
827
+ private async _getVariables(
828
+ scope: DebugProtocol.Scope
829
+ ): Promise<DebugProtocol.Variable[]> {
830
+ if (!this.session) {
831
+ throw new Error('No active debugger session');
832
+ }
833
+ if (!scope) {
834
+ return [];
835
+ }
836
+ const reply = await this.session.sendRequest('variables', {
837
+ variablesReference: scope.variablesReference
838
+ });
839
+ return reply.body.variables;
840
+ }
841
+
842
+ /**
843
+ * Process the list of breakpoints from the server and return as a map.
844
+ *
845
+ * @param breakpoints - The list of breakpoints from the kernel.
846
+ *
847
+ */
848
+ private _mapBreakpoints(
849
+ breakpoints: IDebugger.ISession.IDebugInfoBreakpoints[]
850
+ ): Map<string, IDebugger.IBreakpoint[]> {
851
+ if (!breakpoints.length) {
852
+ return new Map<string, IDebugger.IBreakpoint[]>();
853
+ }
854
+ return breakpoints.reduce(
855
+ (
856
+ map: Map<string, IDebugger.IBreakpoint[]>,
857
+ val: IDebugger.ISession.IDebugInfoBreakpoints
858
+ ) => {
859
+ const { breakpoints, source } = val;
860
+ map.set(
861
+ source,
862
+ breakpoints.map(point => ({
863
+ ...point,
864
+ source: { path: source },
865
+ verified: true
866
+ }))
867
+ );
868
+ return map;
869
+ },
870
+ new Map<string, IDebugger.IBreakpoint[]>()
871
+ );
872
+ }
873
+
874
+ /**
875
+ * Handle a change of the current active frame.
876
+ *
877
+ * @param _ The callstack model
878
+ * @param frame The frame.
879
+ */
880
+ private async _onCurrentFrameChanged(
881
+ _: IDebugger.Model.ICallstack,
882
+ frame: IDebugger.IStackFrame
883
+ ): Promise<void> {
884
+ if (!frame) {
885
+ return;
886
+ }
887
+ const scopes = await this._getScopes(frame);
888
+ const variables = await Promise.all(
889
+ scopes.map(scope => this._getVariables(scope))
890
+ );
891
+ const variableScopes = this._convertScopes(scopes, variables);
892
+ this._model.variables.scopes = variableScopes;
893
+ }
894
+
895
+ /**
896
+ * Handle a variable expanded event and request variables from the kernel.
897
+ *
898
+ * @param _ The variables model.
899
+ * @param variable The expanded variable.
900
+ */
901
+ private async _onVariableExpanded(
902
+ _: VariablesModel,
903
+ variable: DebugProtocol.Variable
904
+ ): Promise<DebugProtocol.Variable[]> {
905
+ if (!this.session) {
906
+ throw new Error('No active debugger session');
907
+ }
908
+ const reply = await this.session.sendRequest('variables', {
909
+ variablesReference: variable.variablesReference
910
+ });
911
+ let newVariable = { ...variable, expanded: true };
912
+
913
+ reply.body.variables.forEach((variable: DebugProtocol.Variable) => {
914
+ newVariable = { [variable.name]: variable, ...newVariable };
915
+ });
916
+ const newScopes = this._model.variables.scopes.map(scope => {
917
+ const findIndex = scope.variables.findIndex(
918
+ ele => ele.variablesReference === variable.variablesReference
919
+ );
920
+ scope.variables[findIndex] = newVariable;
921
+ return { ...scope };
922
+ });
923
+
924
+ this._model.variables.scopes = [...newScopes];
925
+ return reply.body.variables;
926
+ }
927
+
928
+ /**
929
+ * Set the breakpoints for a given file.
930
+ *
931
+ * @param breakpoints The list of breakpoints to set.
932
+ * @param path The path to where to set the breakpoints.
933
+ */
934
+ private async _setBreakpoints(
935
+ breakpoints: DebugProtocol.SourceBreakpoint[],
936
+ path: string
937
+ ): Promise<DebugProtocol.SetBreakpointsResponse> {
938
+ if (!this.session) {
939
+ throw new Error('No active debugger session');
940
+ }
941
+ return await this.session.sendRequest('setBreakpoints', {
942
+ breakpoints: breakpoints,
943
+ source: { path },
944
+ sourceModified: false
945
+ });
946
+ }
947
+
948
+ /**
949
+ * Re-send the breakpoints to the kernel and update the model.
950
+ *
951
+ * @param breakpoints The map of breakpoints to send
952
+ */
953
+ private async _restoreBreakpoints(
954
+ breakpoints: Map<string, IDebugger.IBreakpoint[]>
955
+ ): Promise<void> {
956
+ for (const [source, points] of breakpoints) {
957
+ await this._setBreakpoints(
958
+ points
959
+ .filter(({ line }) => typeof line === 'number')
960
+ .map(({ line }) => ({ line: line! })),
961
+ source
962
+ );
963
+ }
964
+ this._model.breakpoints.restoreBreakpoints(breakpoints);
965
+ }
966
+
967
+ private _config: IDebugger.IConfig;
968
+ private _debuggerSources: IDebugger.ISources | null;
969
+ private _eventMessage = new Signal<IDebugger, IDebugger.ISession.Event>(this);
970
+ private _isDisposed = false;
971
+ private _model: IDebugger.Model.IService;
972
+ private _session: IDebugger.ISession | null;
973
+ private _sessionChanged = new Signal<IDebugger, IDebugger.ISession | null>(
974
+ this
975
+ );
976
+ private _specsManager: KernelSpec.IManager | null;
977
+ private _trans: TranslationBundle;
978
+ private _pauseOnExceptionChanged = new Signal<IDebugger, void>(this);
979
+ }
980
+
981
+ /**
982
+ * A namespace for `DebuggerService` statics.
983
+ */
984
+ export namespace DebuggerService {
985
+ /**
986
+ * Instantiation options for a `DebuggerService`.
987
+ */
988
+ export interface IOptions {
989
+ /**
990
+ * The configuration instance with hash method.
991
+ */
992
+ config: IDebugger.IConfig;
993
+
994
+ /**
995
+ * The optional debugger sources instance.
996
+ */
997
+ debuggerSources?: IDebugger.ISources | null;
998
+
999
+ /**
1000
+ * The optional kernel specs manager.
1001
+ */
1002
+ specsManager?: KernelSpec.IManager | null;
1003
+
1004
+ /**
1005
+ * The application language translator.
1006
+ */
1007
+ translator?: ITranslator | null;
1008
+ }
1009
+ }