@jupyterlab/debugger 4.5.0-beta.1 → 4.5.0-rc.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.
@@ -2,19 +2,16 @@
2
2
  // Distributed under the terms of the Modified BSD License.
3
3
 
4
4
  import { ISignal, Signal } from '@lumino/signaling';
5
- import { IDebugger } from '../../tokens';
6
- import { INotebookTracker } from '@jupyterlab/notebook';
7
- import { isCodeCellModel } from '@jupyterlab/cells';
8
- import { IConsoleTracker } from '@jupyterlab/console';
5
+ import { IDebugger, IDebuggerDisplayRegistry } from '../../tokens';
6
+ import { DebuggerDisplayRegistry } from '../../displayregistry';
9
7
 
10
8
  /**
11
9
  * A model for a list of breakpoints.
12
10
  */
13
11
  export class BreakpointsModel implements IDebugger.Model.IBreakpoints {
14
- constructor(options: BreakpointsModel.IOptions) {
15
- this._config = options.config;
16
- this._notebookTracker = options.notebookTracker;
17
- this._consoleTracker = options.consoleTracker;
12
+ constructor(options: { displayRegistry?: IDebuggerDisplayRegistry }) {
13
+ this._displayRegistry =
14
+ options.displayRegistry ?? new DebuggerDisplayRegistry();
18
15
  }
19
16
 
20
17
  /**
@@ -102,70 +99,18 @@ export class BreakpointsModel implements IDebugger.Model.IBreakpoints {
102
99
  * Shows execution count if notebook cell, [*] if running, [ ] if never executed.
103
100
  */
104
101
  getDisplayName(breakpoint: IDebugger.IBreakpoint): string {
105
- if (!this._notebookTracker || !this._config) {
106
- return breakpoint.source?.path ?? '';
107
- }
108
-
109
- let display = breakpoint.source?.path ?? '';
110
-
111
- this._notebookTracker.forEach(panel => {
112
- const kernelName = panel.sessionContext.session?.kernel?.name ?? '';
113
- panel.content.widgets.forEach(cell => {
114
- if (cell.model.type !== 'code') return;
115
-
116
- const code = cell.model.sharedModel.getSource();
117
- const codeId = this._config?.getCodeId(code, kernelName);
118
-
119
- if (codeId && codeId === breakpoint.source?.path) {
120
- if (isCodeCellModel(cell.model)) {
121
- if (cell.model.executionState === 'running') {
122
- display = `Cell [*]`;
123
- } else if (cell.model.executionCount === null) {
124
- display = `Cell [ ]`;
125
- } else {
126
- display = `Cell [${cell.model.executionCount}]`;
127
- }
128
- }
129
- }
130
- });
131
- });
132
-
133
- this._consoleTracker?.forEach(panel => {
134
- const kernelName = panel.sessionContext.session?.kernel?.name ?? '';
135
-
136
- Array.from(panel.console.cells).forEach(cell => {
137
- const code = cell.model.sharedModel.getSource();
138
- const codeId = this._config?.getCodeId(code, kernelName);
139
-
140
- if (codeId && codeId === breakpoint.source?.path) {
141
- if (isCodeCellModel(cell.model)) {
142
- const executionCount = cell.model.executionCount ?? null;
143
- const executionState = cell.model.executionState ?? null;
144
-
145
- if (executionState === 'running') {
146
- display = `In [*]`;
147
- } else if (executionCount === null) {
148
- display = `In [ ]`;
149
- } else {
150
- display = `In [${executionCount}]`;
151
- }
152
- }
153
- }
154
- });
155
- });
156
-
157
- return display;
102
+ return this._displayRegistry.getDisplayName(
103
+ breakpoint.source as IDebugger.Source
104
+ );
158
105
  }
159
106
 
160
- private _config: IDebugger.IConfig;
161
- private _notebookTracker: INotebookTracker | null;
162
- private _consoleTracker: IConsoleTracker | null;
163
107
  private _breakpoints = new Map<string, IDebugger.IBreakpoint[]>();
164
108
  private _changed = new Signal<this, IDebugger.IBreakpoint[]>(this);
165
109
  private _restored = new Signal<this, void>(this);
166
110
  private _clicked = new Signal<this, IDebugger.IBreakpoint>(this);
167
111
  private _selectedBreakpoint: IDebugger.IBreakpoint;
168
112
  private _selectedChanged = new Signal<this, IDebugger.IBreakpoint>(this);
113
+ private _displayRegistry: IDebuggerDisplayRegistry;
169
114
  }
170
115
 
171
116
  /**
@@ -177,18 +122,8 @@ export namespace BreakpointsModel {
177
122
  */
178
123
  export interface IOptions {
179
124
  /**
180
- * Debugger configuration.
181
- */
182
- config: IDebugger.IConfig;
183
-
184
- /**
185
- * The notebook tracker.
186
- */
187
- notebookTracker: INotebookTracker | null;
188
-
189
- /**
190
- * The console tracker.
125
+ * The debugger display registry.
191
126
  */
192
- consoleTracker: IConsoleTracker | null;
127
+ displayRegistry: IDebuggerDisplayRegistry;
193
128
  }
194
129
  }
@@ -3,19 +3,17 @@
3
3
 
4
4
  import { ISignal, Signal } from '@lumino/signaling';
5
5
 
6
- import { IDebugger } from '../../tokens';
7
- import { isCodeCellModel } from '@jupyterlab/cells';
6
+ import { DebuggerDisplayRegistry } from '../../displayregistry';
7
+ import { IDebugger, IDebuggerDisplayRegistry } from '../../tokens';
8
8
  import { INotebookTracker } from '@jupyterlab/notebook';
9
9
  import { IConsoleTracker } from '@jupyterlab/console';
10
-
11
10
  /**
12
11
  * A model for a callstack.
13
12
  */
14
13
  export class CallstackModel implements IDebugger.Model.ICallstack {
15
- constructor(options: CallstackModel.IOptions) {
16
- this._config = options.config;
17
- this._notebookTracker = options.notebookTracker ?? null;
18
- this._consoleTracker = options.consoleTracker ?? null;
14
+ constructor(options: { displayRegistry?: IDebuggerDisplayRegistry }) {
15
+ this._displayRegistry =
16
+ options.displayRegistry ?? new DebuggerDisplayRegistry();
19
17
  }
20
18
 
21
19
  /**
@@ -77,70 +75,22 @@ export class CallstackModel implements IDebugger.Model.ICallstack {
77
75
  * Returns a human-readable display for a frame.
78
76
  */
79
77
  getDisplayName(frame: IDebugger.IStackFrame): string {
80
- if (!this._notebookTracker || !this._config) {
81
- return frame.source?.path ?? '';
78
+ let name = this._displayRegistry.getDisplayName(
79
+ frame.source as IDebugger.Source
80
+ );
81
+ if (frame.line !== undefined) {
82
+ name += `:${frame.line}`;
82
83
  }
83
-
84
- let display = frame.source?.path ?? '';
85
-
86
- this._notebookTracker.forEach(panel => {
87
- const kernelName = panel.sessionContext.session?.kernel?.name ?? '';
88
- panel.content.widgets.forEach(cell => {
89
- if (cell.model.type !== 'code') return;
90
-
91
- const code = cell.model.sharedModel.getSource();
92
- const codeId = this._config.getCodeId(code, kernelName);
93
-
94
- if (codeId && codeId === frame.source?.path) {
95
- if (isCodeCellModel(cell.model)) {
96
- if (cell.model.executionState === 'running') {
97
- display = `Cell [*]`;
98
- } else if (cell.model.executionCount === null) {
99
- display = `Cell [ ]`;
100
- } else {
101
- display = `Cell [${cell.model.executionCount}]`;
102
- }
103
- }
104
- }
105
- });
106
- });
107
-
108
- this._consoleTracker?.forEach(panel => {
109
- const kernelName = panel.sessionContext.session?.kernel?.name ?? '';
110
-
111
- Array.from(panel.console.cells).forEach(cell => {
112
- const code = cell.model.sharedModel.getSource();
113
- const codeId = this._config?.getCodeId(code, kernelName);
114
-
115
- if (codeId && codeId === frame.source?.path) {
116
- if (isCodeCellModel(cell.model)) {
117
- const executionCount = cell.model.executionCount ?? null;
118
- const executionState = cell.model.executionState ?? null;
119
-
120
- if (executionState === 'running') {
121
- display = `In [*]`;
122
- } else if (executionCount === null) {
123
- display = `In [ ]`;
124
- } else {
125
- display = `In [${executionCount}]`;
126
- }
127
- }
128
- }
129
- });
130
- });
131
-
132
- return display;
84
+ return name;
133
85
  }
134
86
 
135
- private _config: IDebugger.IConfig;
136
- private _notebookTracker: INotebookTracker | null;
137
- private _consoleTracker: IConsoleTracker | null;
138
87
  private _state: IDebugger.IStackFrame[] = [];
139
88
  private _currentFrame: IDebugger.IStackFrame | null = null;
140
89
  private _framesChanged = new Signal<this, IDebugger.IStackFrame[]>(this);
141
90
  private _currentFrameChanged = new Signal<this, IDebugger.IStackFrame | null>(
142
91
  this
143
92
  );
93
+ private _displayRegistry: IDebuggerDisplayRegistry;
144
94
  }
145
95
 
146
96
  /**
@@ -3,9 +3,8 @@
3
3
 
4
4
  import { ISignal, Signal } from '@lumino/signaling';
5
5
 
6
- import { IDebugger } from '../../tokens';
7
- import { INotebookTracker } from '@jupyterlab/notebook';
8
- import { isCodeCellModel } from '@jupyterlab/cells';
6
+ import { DebuggerDisplayRegistry } from '../../displayregistry';
7
+ import { IDebugger, IDebuggerDisplayRegistry } from '../../tokens';
9
8
 
10
9
  /**
11
10
  * The model to keep track of the current source being displayed.
@@ -18,8 +17,8 @@ export class SourcesModel implements IDebugger.Model.ISources {
18
17
  */
19
18
  constructor(options: SourcesModel.IOptions) {
20
19
  this.currentFrameChanged = options.currentFrameChanged;
21
- this._notebookTracker = options.notebookTracker ?? undefined;
22
- this._config = options.config;
20
+ this._displayRegistry =
21
+ options.displayRegistry ?? new DebuggerDisplayRegistry();
23
22
  }
24
23
 
25
24
  /**
@@ -73,36 +72,17 @@ export class SourcesModel implements IDebugger.Model.ISources {
73
72
  *
74
73
  * For notebook cells, shows execution count if available, [*] if running, or [ ] if never executed.
75
74
  */
75
+ /**
76
+ * Returns a human-readable display for a frame.
77
+ */
76
78
  getDisplayName(frame: IDebugger.IStackFrame): string {
77
- let display = frame.source?.path ?? '';
78
-
79
- if (!this._notebookTracker || !this._config || !frame.source?.path) {
80
- return display;
79
+ let name = this._displayRegistry.getDisplayName(
80
+ frame.source as IDebugger.Source
81
+ );
82
+ if (frame.line !== undefined) {
83
+ name += `:${frame.line}`;
81
84
  }
82
-
83
- this._notebookTracker.forEach(panel => {
84
- const kernelName = panel.sessionContext.session?.kernel?.name ?? '';
85
- panel.content.widgets.forEach(cell => {
86
- if (cell.model.type !== 'code') return;
87
-
88
- const code = cell.model.sharedModel.getSource();
89
- const codeId = this._config!.getCodeId(code, kernelName);
90
-
91
- if (codeId === frame.source?.path) {
92
- if (isCodeCellModel(cell.model)) {
93
- if (cell.model.executionState === 'running') {
94
- display = `Cell [*]`;
95
- } else if (cell.model.executionCount === null) {
96
- display = `Cell [ ]`;
97
- } else {
98
- display = `Cell [${cell.model.executionCount}]`;
99
- }
100
- }
101
- }
102
- });
103
- });
104
-
105
- return display;
85
+ return name;
106
86
  }
107
87
 
108
88
  private _currentSource: IDebugger.Source | null;
@@ -114,8 +94,7 @@ export class SourcesModel implements IDebugger.Model.ISources {
114
94
  SourcesModel,
115
95
  IDebugger.Source | null
116
96
  >(this);
117
- private _notebookTracker?: INotebookTracker;
118
- private _config?: IDebugger.IConfig;
97
+ private _displayRegistry: IDebuggerDisplayRegistry;
119
98
  }
120
99
 
121
100
  /**
@@ -135,13 +114,8 @@ export namespace SourcesModel {
135
114
  >;
136
115
 
137
116
  /**
138
- * Optional notebook tracker to resolve cell execution numbers.
139
- */
140
- notebookTracker: INotebookTracker | null;
141
-
142
- /**
143
- * Debugger config used to get code ids.
117
+ * The display registry.
144
118
  */
145
- config: IDebugger.IConfig;
119
+ displayRegistry?: IDebuggerDisplayRegistry;
146
120
  }
147
121
  }
package/src/service.ts CHANGED
@@ -20,8 +20,7 @@ import { Debugger } from './debugger';
20
20
  import { VariablesModel } from './panels/variables/model';
21
21
 
22
22
  import { IDebugger } from './tokens';
23
- import { INotebookTracker } from '@jupyterlab/notebook';
24
- import { IConsoleTracker } from '@jupyterlab/console';
23
+ import { IDebuggerDisplayRegistry } from './tokens';
25
24
 
26
25
  /**
27
26
  * A concrete implementation of the IDebugger interface.
@@ -42,9 +41,7 @@ export class DebuggerService implements IDebugger, IDisposable {
42
41
  this._session = null;
43
42
  this._specsManager = options.specsManager ?? null;
44
43
  this._model = new Debugger.Model({
45
- config: options.config,
46
- notebookTracker: options.notebookTracker || null,
47
- consoleTracker: options.consoleTracker || null
44
+ displayRegistry: options.displayRegistry
48
45
  });
49
46
  this._debuggerSources = options.debuggerSources ?? null;
50
47
  this._trans = (options.translator || nullTranslator).load('jupyterlab');
@@ -431,7 +428,7 @@ export class DebuggerService implements IDebugger, IDisposable {
431
428
 
432
429
  const reply = await this.session.restoreState();
433
430
  const { body } = reply;
434
- const breakpoints = this._mapBreakpoints(body.breakpoints);
431
+ const kernelBreakpoints = this._mapBreakpoints(body.breakpoints);
435
432
  const stoppedThreads = new Set(body.stoppedThreads);
436
433
 
437
434
  this._model.hasRichVariableRendering = body.richRendering === true;
@@ -459,6 +456,28 @@ export class DebuggerService implements IDebugger, IDisposable {
459
456
  ? this.session?.connection?.name || '-'
460
457
  : '-';
461
458
  }
459
+ const breakpoints = this._migrateBreakpoints(
460
+ this._model.breakpoints.breakpoints
461
+ );
462
+
463
+ // Merge kernel breakpoints with existing breakpoints, avoiding duplicates
464
+ for (const [path, kernelBpList] of kernelBreakpoints) {
465
+ const existingBreakpoints = breakpoints.get(path) ?? [];
466
+
467
+ // Filter out kernel breakpoints that already exist at the same line
468
+ const newBreakpoints = kernelBpList.filter(
469
+ kernelBp =>
470
+ !existingBreakpoints.some(
471
+ existingBp => existingBp.line === kernelBp.line
472
+ )
473
+ );
474
+
475
+ // Merge existing and new breakpoints
476
+ breakpoints.set(path, [...existingBreakpoints, ...newBreakpoints]);
477
+ }
478
+
479
+ // restore in kernel AND model
480
+ await this._restoreBreakpoints(breakpoints);
462
481
 
463
482
  if (this._debuggerSources) {
464
483
  const filtered = this._filterBreakpoints(breakpoints);
@@ -577,6 +596,7 @@ export class DebuggerService implements IDebugger, IDisposable {
577
596
  const localBreakpoints = breakpoints
578
597
  .filter(({ line }) => typeof line === 'number')
579
598
  .map(({ line }) => ({ line: line! }));
599
+
580
600
  const remoteBreakpoints = this._mapBreakpoints(state.body.breakpoints);
581
601
 
582
602
  // Set the local copy of breakpoints to reflect only editors that exist.
@@ -696,21 +716,36 @@ export class DebuggerService implements IDebugger, IDisposable {
696
716
  await this._dumpCell(cell);
697
717
  }
698
718
 
699
- const breakpoints = new Map<string, IDebugger.IBreakpoint[]>();
719
+ const breakpoints = this._migrateBreakpoints(state.breakpoints);
720
+
721
+ await this._restoreBreakpoints(breakpoints);
722
+ const config = await this.session!.sendRequest('configurationDone', {});
723
+ await this.restoreState(false);
724
+ return config.success;
725
+ }
726
+
727
+ /**
728
+ * Migrate breakpoints from previous path to new path when restoring service state
729
+ * @param breakpoints
730
+ * @returns
731
+ */
732
+ private _migrateBreakpoints(
733
+ breakpoints: Map<string, IDebugger.IBreakpoint[]>
734
+ ) {
735
+ const migratedBreakpoints = new Map<string, IDebugger.IBreakpoint[]>();
700
736
  const kernel = this.session?.connection?.kernel?.name ?? '';
701
737
  const { prefix, suffix } = this._config.getTmpFileParams(kernel);
702
- for (const item of state.breakpoints) {
738
+ for (const item of breakpoints) {
703
739
  const [id, list] = item;
704
- const unsuffixedId = id.substr(0, id.length - suffix.length);
705
- const codeHash = unsuffixedId.substr(unsuffixedId.lastIndexOf('/') + 1);
740
+ const unSuffixedId = id.substring(0, id.length - suffix.length);
741
+ const codeHash = unSuffixedId.substring(
742
+ unSuffixedId.lastIndexOf('/') + 1
743
+ );
706
744
  const newId = prefix.concat(codeHash).concat(suffix);
707
- breakpoints.set(newId, list);
745
+ migratedBreakpoints.set(newId, list);
708
746
  }
709
747
 
710
- await this._restoreBreakpoints(breakpoints);
711
- const config = await this.session!.sendRequest('configurationDone', {});
712
- await this.restoreState(false);
713
- return config.success;
748
+ return migratedBreakpoints;
714
749
  }
715
750
 
716
751
  /**
@@ -1053,13 +1088,8 @@ export namespace DebuggerService {
1053
1088
  translator?: ITranslator | null;
1054
1089
 
1055
1090
  /**
1056
- * The notebook tracker.
1057
- */
1058
- notebookTracker?: INotebookTracker | null;
1059
-
1060
- /**
1061
- * The console tracker.
1091
+ * The display registry.
1062
1092
  */
1063
- consoleTracker?: IConsoleTracker | null;
1093
+ displayRegistry?: IDebuggerDisplayRegistry | null;
1064
1094
  }
1065
1095
  }
package/src/sidebar.ts CHANGED
@@ -11,8 +11,6 @@ import { bugIcon, SidePanel } from '@jupyterlab/ui-components';
11
11
 
12
12
  import { Widget } from '@lumino/widgets';
13
13
 
14
- import { INotebookTracker } from '@jupyterlab/notebook';
15
-
16
14
  import { Breakpoints as BreakpointsPanel } from './panels/breakpoints';
17
15
 
18
16
  import { Callstack as CallstackPanel } from './panels/callstack';
@@ -160,16 +158,6 @@ export namespace DebuggerSidebar {
160
158
  * An optional application language translator.
161
159
  */
162
160
  translator?: ITranslator;
163
-
164
- /**
165
- * The notebook tracker (required to resolve notebook breakpoints).
166
- */
167
- notebookTracker: INotebookTracker | null;
168
-
169
- /**
170
- * The debugger configuration.
171
- */
172
- config: IDebugger.IConfig;
173
161
  }
174
162
 
175
163
  /**
package/src/tokens.ts CHANGED
@@ -19,6 +19,8 @@ import { DebugProtocol } from '@vscode/debugprotocol';
19
19
 
20
20
  import { DebuggerHandler } from './handler';
21
21
 
22
+ import { IDebuggerSourceDisplayProvider } from './displayregistry';
23
+
22
24
  /**
23
25
  * An interface describing an application's visual debugger.
24
26
  */
@@ -1110,6 +1112,21 @@ export namespace IDebugger {
1110
1112
  }
1111
1113
  }
1112
1114
 
1115
+ /**
1116
+ * Interface token for the Debugger Display Registry service.
1117
+ */
1118
+ export interface IDebuggerDisplayRegistry {
1119
+ /**
1120
+ * Register a display provider.
1121
+ */
1122
+ register(provider: IDebuggerSourceDisplayProvider): void;
1123
+
1124
+ /**
1125
+ * Get a display name for a given source.
1126
+ */
1127
+ getDisplayName(source: IDebugger.Source): string;
1128
+ }
1129
+
1113
1130
  /**
1114
1131
  * The visual debugger token.
1115
1132
  */
@@ -1157,3 +1174,11 @@ export const IDebuggerSourceViewer = new Token<IDebugger.ISourceViewer>(
1157
1174
  '@jupyterlab/debugger:IDebuggerSourceViewer',
1158
1175
  'A debugger source viewer.'
1159
1176
  );
1177
+
1178
+ /**
1179
+ * Debugger display registry token.
1180
+ */
1181
+ export const IDebuggerDisplayRegistry = new Token<IDebuggerDisplayRegistry>(
1182
+ '@jupyterlab/debugger:IDebuggerDisplayRegistry',
1183
+ 'A service for registering display labels for cells/files in the debugger.'
1184
+ );