@theia/debug 1.70.0-next.81 → 1.71.0-next.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.
Files changed (100) hide show
  1. package/lib/browser/breakpoint/breakpoint-manager.d.ts +80 -45
  2. package/lib/browser/breakpoint/breakpoint-manager.d.ts.map +1 -1
  3. package/lib/browser/breakpoint/breakpoint-manager.js +553 -170
  4. package/lib/browser/breakpoint/breakpoint-manager.js.map +1 -1
  5. package/lib/browser/breakpoint/breakpoint-manager.spec.d.ts +2 -0
  6. package/lib/browser/breakpoint/breakpoint-manager.spec.d.ts.map +1 -0
  7. package/lib/browser/breakpoint/breakpoint-manager.spec.js +861 -0
  8. package/lib/browser/breakpoint/breakpoint-manager.spec.js.map +1 -0
  9. package/lib/browser/breakpoint/breakpoint-marker.d.ts +7 -10
  10. package/lib/browser/breakpoint/breakpoint-marker.d.ts.map +1 -1
  11. package/lib/browser/breakpoint/breakpoint-marker.js +14 -11
  12. package/lib/browser/breakpoint/breakpoint-marker.js.map +1 -1
  13. package/lib/browser/breakpoint/debug-data-breakpoint-actions.js +1 -1
  14. package/lib/browser/breakpoint/debug-data-breakpoint-actions.js.map +1 -1
  15. package/lib/browser/debug-frontend-application-contribution.d.ts +1 -2
  16. package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
  17. package/lib/browser/debug-frontend-application-contribution.js +13 -21
  18. package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
  19. package/lib/browser/debug-frontend-module.d.ts.map +1 -1
  20. package/lib/browser/debug-frontend-module.js +3 -0
  21. package/lib/browser/debug-frontend-module.js.map +1 -1
  22. package/lib/browser/debug-session-manager.d.ts +8 -27
  23. package/lib/browser/debug-session-manager.d.ts.map +1 -1
  24. package/lib/browser/debug-session-manager.js +14 -132
  25. package/lib/browser/debug-session-manager.js.map +1 -1
  26. package/lib/browser/debug-session.d.ts +1 -21
  27. package/lib/browser/debug-session.d.ts.map +1 -1
  28. package/lib/browser/debug-session.js +72 -203
  29. package/lib/browser/debug-session.js.map +1 -1
  30. package/lib/browser/disassembly-view/disassembly-view-breakpoint-renderer.js +1 -1
  31. package/lib/browser/disassembly-view/disassembly-view-breakpoint-renderer.js.map +1 -1
  32. package/lib/browser/disassembly-view/disassembly-view-widget.d.ts.map +1 -1
  33. package/lib/browser/disassembly-view/disassembly-view-widget.js +17 -24
  34. package/lib/browser/disassembly-view/disassembly-view-widget.js.map +1 -1
  35. package/lib/browser/editor/debug-editor-model.d.ts +15 -5
  36. package/lib/browser/editor/debug-editor-model.d.ts.map +1 -1
  37. package/lib/browser/editor/debug-editor-model.js +56 -32
  38. package/lib/browser/editor/debug-editor-model.js.map +1 -1
  39. package/lib/browser/model/debug-breakpoint-opener.d.ts +14 -0
  40. package/lib/browser/model/debug-breakpoint-opener.d.ts.map +1 -0
  41. package/lib/browser/model/debug-breakpoint-opener.js +67 -0
  42. package/lib/browser/model/debug-breakpoint-opener.js.map +1 -0
  43. package/lib/browser/model/debug-breakpoint.d.ts +32 -13
  44. package/lib/browser/model/debug-breakpoint.d.ts.map +1 -1
  45. package/lib/browser/model/debug-breakpoint.js +76 -16
  46. package/lib/browser/model/debug-breakpoint.js.map +1 -1
  47. package/lib/browser/model/debug-data-breakpoint.d.ts +1 -0
  48. package/lib/browser/model/debug-data-breakpoint.d.ts.map +1 -1
  49. package/lib/browser/model/debug-data-breakpoint.js +6 -5
  50. package/lib/browser/model/debug-data-breakpoint.js.map +1 -1
  51. package/lib/browser/model/debug-function-breakpoint.d.ts +4 -1
  52. package/lib/browser/model/debug-function-breakpoint.d.ts.map +1 -1
  53. package/lib/browser/model/debug-function-breakpoint.js +20 -29
  54. package/lib/browser/model/debug-function-breakpoint.js.map +1 -1
  55. package/lib/browser/model/debug-instruction-breakpoint.d.ts +2 -1
  56. package/lib/browser/model/debug-instruction-breakpoint.d.ts.map +1 -1
  57. package/lib/browser/model/debug-instruction-breakpoint.js +8 -8
  58. package/lib/browser/model/debug-instruction-breakpoint.js.map +1 -1
  59. package/lib/browser/model/debug-source-breakpoint.d.ts +6 -15
  60. package/lib/browser/model/debug-source-breakpoint.d.ts.map +1 -1
  61. package/lib/browser/model/debug-source-breakpoint.js +16 -90
  62. package/lib/browser/model/debug-source-breakpoint.js.map +1 -1
  63. package/lib/browser/view/debug-breakpoints-source.d.ts +0 -2
  64. package/lib/browser/view/debug-breakpoints-source.d.ts.map +1 -1
  65. package/lib/browser/view/debug-breakpoints-source.js +2 -10
  66. package/lib/browser/view/debug-breakpoints-source.js.map +1 -1
  67. package/lib/browser/view/debug-breakpoints-widget.d.ts +2 -0
  68. package/lib/browser/view/debug-breakpoints-widget.d.ts.map +1 -1
  69. package/lib/browser/view/debug-breakpoints-widget.js +3 -0
  70. package/lib/browser/view/debug-breakpoints-widget.js.map +1 -1
  71. package/lib/browser/view/debug-exception-breakpoint.d.ts +18 -11
  72. package/lib/browser/view/debug-exception-breakpoint.d.ts.map +1 -1
  73. package/lib/browser/view/debug-exception-breakpoint.js +58 -24
  74. package/lib/browser/view/debug-exception-breakpoint.js.map +1 -1
  75. package/lib/browser/view/debug-view-model.d.ts +8 -4
  76. package/lib/browser/view/debug-view-model.d.ts.map +1 -1
  77. package/lib/browser/view/debug-view-model.js +16 -9
  78. package/lib/browser/view/debug-view-model.js.map +1 -1
  79. package/package.json +16 -16
  80. package/src/browser/breakpoint/breakpoint-manager.spec.ts +1106 -0
  81. package/src/browser/breakpoint/breakpoint-manager.ts +583 -194
  82. package/src/browser/breakpoint/breakpoint-marker.ts +21 -15
  83. package/src/browser/breakpoint/debug-data-breakpoint-actions.ts +1 -1
  84. package/src/browser/debug-frontend-application-contribution.ts +18 -23
  85. package/src/browser/debug-frontend-module.ts +5 -1
  86. package/src/browser/debug-session-manager.ts +15 -147
  87. package/src/browser/debug-session.tsx +71 -221
  88. package/src/browser/disassembly-view/disassembly-view-breakpoint-renderer.ts +1 -1
  89. package/src/browser/disassembly-view/disassembly-view-widget.ts +17 -23
  90. package/src/browser/editor/debug-editor-model.ts +58 -35
  91. package/src/browser/model/debug-breakpoint-opener.ts +51 -0
  92. package/src/browser/model/debug-breakpoint.tsx +101 -20
  93. package/src/browser/model/debug-data-breakpoint.tsx +8 -5
  94. package/src/browser/model/debug-function-breakpoint.tsx +18 -29
  95. package/src/browser/model/debug-instruction-breakpoint.tsx +10 -8
  96. package/src/browser/model/debug-source-breakpoint.tsx +23 -101
  97. package/src/browser/view/debug-breakpoints-source.tsx +2 -9
  98. package/src/browser/view/debug-breakpoints-widget.ts +6 -0
  99. package/src/browser/view/debug-exception-breakpoint.tsx +66 -27
  100. package/src/browser/view/debug-view-model.ts +21 -13
@@ -14,29 +14,35 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import * as deepEqual from 'fast-deep-equal';
18
- import { injectable, inject } from '@theia/core/shared/inversify';
19
- import { Emitter } from '@theia/core/lib/common';
20
- import { StorageService } from '@theia/core/lib/browser';
21
- import { Marker } from '@theia/markers/lib/common/marker';
22
- import { MarkerManager } from '@theia/markers/lib/browser/marker-manager';
17
+ import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
18
+ import { CommandService, Emitter, Event, MapUtils } from '@theia/core/lib/common';
19
+ import { LabelProvider, OpenerService, StorageService } from '@theia/core/lib/browser';
23
20
  import URI from '@theia/core/lib/common/uri';
24
- import { SourceBreakpoint, BREAKPOINT_KIND, ExceptionBreakpoint, FunctionBreakpoint, BaseBreakpoint, InstructionBreakpoint, DataBreakpoint } from './breakpoint-marker';
21
+ import { SourceBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, BaseBreakpoint, InstructionBreakpoint, DataBreakpoint } from './breakpoint-marker';
22
+ import { DebugSourceBreakpoint } from '../model/debug-source-breakpoint';
23
+ import { DebugFunctionBreakpoint } from '../model/debug-function-breakpoint';
24
+ import { DebugInstructionBreakpoint } from '../model/debug-instruction-breakpoint';
25
+ import { DebugExceptionBreakpoint } from '../view/debug-exception-breakpoint';
26
+ import { DebugDataBreakpoint } from '../model/debug-data-breakpoint';
27
+ import { BPCapabilities, DebugBreakpoint, DebugBreakpointOptions } from '../model/debug-breakpoint';
25
28
  import { DebugProtocol } from '@vscode/debugprotocol';
29
+ import { FileService } from '@theia/filesystem/lib/browser/file-service';
30
+ import { FileChangeType } from '@theia/filesystem/lib/common/files';
26
31
 
27
- export interface BreakpointsChangeEvent<T extends BaseBreakpoint> {
32
+ export interface BreakpointsChangeEvent<T extends object> {
28
33
  uri: URI
29
34
  added: T[]
30
35
  removed: T[]
31
36
  changed: T[]
32
37
  }
33
- export type SourceBreakpointsChangeEvent = BreakpointsChangeEvent<SourceBreakpoint>;
34
- export type FunctionBreakpointsChangeEvent = BreakpointsChangeEvent<FunctionBreakpoint>;
35
- export type InstructionBreakpointsChangeEvent = BreakpointsChangeEvent<InstructionBreakpoint>;
36
- export type DataBreakpointsChangeEvent = BreakpointsChangeEvent<DataBreakpoint>;
38
+
39
+ export type SourceBreakpointsChangeEvent = BreakpointsChangeEvent<DebugSourceBreakpoint>;
40
+ export type FunctionBreakpointsChangeEvent = BreakpointsChangeEvent<DebugFunctionBreakpoint>;
41
+ export type InstructionBreakpointsChangeEvent = BreakpointsChangeEvent<DebugInstructionBreakpoint>;
42
+ export type DataBreakpointsChangeEvent = BreakpointsChangeEvent<DebugDataBreakpoint>;
37
43
 
38
44
  @injectable()
39
- export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
45
+ export class BreakpointManager {
40
46
 
41
47
  static EXCEPTION_URI = new URI('debug:exception://');
42
48
 
@@ -46,13 +52,31 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
46
52
 
47
53
  static DATA_URI = new URI('debug:data://');
48
54
 
49
- protected readonly owner = 'breakpoint';
55
+ // ── Source breakpoints, keyed by URI string ──
56
+
57
+ protected readonly sourceBreakpoints = new Map<string, DebugSourceBreakpoint[]>();
58
+
59
+ // ── Injected services ──
50
60
 
51
61
  @inject(StorageService)
52
62
  protected readonly storage: StorageService;
63
+ @inject(LabelProvider)
64
+ protected readonly labelProvider: LabelProvider;
65
+ @inject(OpenerService)
66
+ protected readonly openerService: OpenerService;
67
+ @inject(CommandService)
68
+ protected readonly commandService: CommandService;
69
+ @inject(FileService)
70
+ protected readonly fileService: FileService;
53
71
 
54
- getKind(): string {
55
- return BREAKPOINT_KIND;
72
+ // ── Events ──
73
+
74
+ protected readonly onDidChangeMarkersEmitter = new Emitter<URI>();
75
+ get onDidChangeMarkers(): Event<URI> {
76
+ return this.onDidChangeMarkersEmitter.event;
77
+ }
78
+ protected fireOnDidChangeMarkers(uri: URI): void {
79
+ this.onDidChangeMarkersEmitter.fire(uri);
56
80
  }
57
81
 
58
82
  protected readonly onDidChangeBreakpointsEmitter = new Emitter<SourceBreakpointsChangeEvent>();
@@ -67,174 +91,482 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
67
91
  protected readonly onDidChangeDataBreakpointsEmitter = new Emitter<DataBreakpointsChangeEvent>();
68
92
  readonly onDidChangeDataBreakpoints = this.onDidChangeDataBreakpointsEmitter.event;
69
93
 
70
- override setMarkers(uri: URI, owner: string, newMarkers: SourceBreakpoint[]): Marker<SourceBreakpoint>[] {
71
- const result = this.findMarkers({ uri, owner });
72
- const added: SourceBreakpoint[] = [];
73
- const removed: SourceBreakpoint[] = [];
74
- const changed: SourceBreakpoint[] = [];
75
- const oldMarkers = new Map(result.map(({ data }) => [data.id, data]));
76
- const ids = new Set<string>();
77
- let didChangeMarkers = false;
78
- for (const newMarker of newMarkers) {
79
- ids.add(newMarker.id);
80
- const oldMarker = oldMarkers.get(newMarker.id);
81
- if (!oldMarker) {
82
- added.push(newMarker);
94
+ // ── Cached options object ──
95
+
96
+ protected _breakpointOptions: DebugBreakpointOptions | undefined;
97
+
98
+ // ── Initialization ──
99
+
100
+ @postConstruct()
101
+ protected init(): void {
102
+ this.fileService.onDidFilesChange(event => {
103
+ if (event.gotDeleted()) {
104
+ for (const uriString of this.sourceBreakpoints.keys()) {
105
+ const uri = new URI(uriString);
106
+ if (event.contains(uri, FileChangeType.DELETED)) {
107
+ this.sourceBreakpoints.delete(uriString);
108
+ this.fireOnDidChangeMarkers(uri);
109
+ }
110
+ }
111
+ }
112
+ });
113
+ }
114
+
115
+ // ── Source breakpoint storage ──
116
+
117
+ getBreakpoints(uri?: URI): readonly DebugSourceBreakpoint[] {
118
+ if (uri) {
119
+ return this.sourceBreakpoints.get(uri.toString()) ?? [];
120
+ }
121
+ const result: DebugSourceBreakpoint[] = [];
122
+ for (const bps of this.sourceBreakpoints.values()) {
123
+ result.push(...bps);
124
+ }
125
+ return result;
126
+ }
127
+
128
+ getUris(): IterableIterator<string> {
129
+ return this.sourceBreakpoints.keys();
130
+ }
131
+
132
+ hasBreakpoints(): boolean {
133
+ return this.sourceBreakpoints.size > 0 || this.functionBreakpoints.length > 0 || this.instructionBreakpoints.length > 0;
134
+ }
135
+
136
+ /**
137
+ * Replace the source breakpoints for a URI. Incoming `breakpoints` are
138
+ * plain `SourceBreakpoint` data; existing `DebugSourceBreakpoint` wrappers
139
+ * are preserved by ID so that session data survives position changes.
140
+ */
141
+ setBreakpoints(uri: URI, breakpoints: SourceBreakpoint[]): void {
142
+ const current = this.getBreakpoints(uri);
143
+ const currentById = new Map(current.map(bp => [bp.id, bp]));
144
+
145
+ const sorted = breakpoints
146
+ .slice()
147
+ .sort((a, b) => (a.raw.line - b.raw.line) || ((a.raw.column || 0) - (b.raw.column || 0)));
148
+
149
+ const seen = new Set<string>();
150
+ const newBps: DebugSourceBreakpoint[] = [];
151
+ for (const bp of sorted) {
152
+ const posKey = `${bp.raw.line}:${bp.raw.column ?? 0}`;
153
+ if (seen.has(posKey)) { continue; }
154
+ seen.add(posKey);
155
+
156
+ // Prefer matching by ID (preserves identity across position changes).
157
+ const existing = currentById.get(bp.id);
158
+ if (existing) {
159
+ existing.origin.raw = bp.raw;
160
+ existing.origin.enabled = bp.enabled;
161
+ newBps.push(existing);
83
162
  } else {
84
- // We emit all existing markers as 'changed', but we only fire an event if something really did change.
85
- // We also fire an event if oldMarker === newMarker, as we cannot actually detect a change in this case
86
- // (https://github.com/eclipse-theia/theia/issues/12546).
87
- didChangeMarkers ||= !!added.length || oldMarker === newMarker || !deepEqual(oldMarker, newMarker);
88
- changed.push(newMarker);
163
+ newBps.push(this.toDebugSourceBreakpoint(bp));
89
164
  }
90
165
  }
91
- for (const [id, data] of oldMarkers.entries()) {
92
- if (!ids.has(id)) {
93
- removed.push(data);
166
+
167
+ this.applySourceBreakpoints(uri, newBps, current);
168
+ }
169
+
170
+ addBreakpoint(breakpoint: SourceBreakpoint): DebugSourceBreakpoint {
171
+ const uri = new URI(breakpoint.uri);
172
+ const current = this.getBreakpoints(uri);
173
+
174
+ // Check for positional duplicate.
175
+ const duplicate = current.find(
176
+ c => c.line === breakpoint.raw.line && c.column === breakpoint.raw.column
177
+ );
178
+ if (duplicate) { return duplicate; }
179
+
180
+ const bp = this.toDebugSourceBreakpoint(breakpoint);
181
+ const newBps = [...current, bp];
182
+ this.applySourceBreakpoints(uri, newBps, current);
183
+ return bp;
184
+ }
185
+
186
+ removeBreakpoint(breakpoint: DebugSourceBreakpoint): void {
187
+ const current = this.getBreakpoints(breakpoint.uri);
188
+ const index = current.indexOf(breakpoint);
189
+ if (index === -1) { return; }
190
+ const retained = [...current.slice(0, index), ...current.slice(index + 1)];
191
+ this.applySourceBreakpoints(breakpoint.uri, retained, current);
192
+ }
193
+
194
+ /**
195
+ * Diff `oldBps` → `newBps`, store, fire markers and typed events.
196
+ * Both arrays must be for the same URI.
197
+ */
198
+ protected applySourceBreakpoints(uri: URI, newBps: readonly DebugSourceBreakpoint[], oldBps: readonly DebugSourceBreakpoint[]): void {
199
+ const oldById = new Map(oldBps.map(bp => [bp.id, bp]));
200
+ const added: DebugSourceBreakpoint[] = [];
201
+ const changed: DebugSourceBreakpoint[] = [];
202
+ let didChange = false;
203
+ for (const bp of newBps) {
204
+ const old = oldById.get(bp.id);
205
+ if (!old) {
206
+ added.push(bp);
207
+ didChange = true;
208
+ } else {
209
+ changed.push(bp);
210
+ // Identity match: the wrapper was mutated in place, or the same
211
+ // object was passed back (editor model position update).
212
+ // Either way we must re-fire to keep decorations in sync.
213
+ didChange = true;
214
+ oldById.delete(bp.id);
94
215
  }
95
216
  }
96
- if (added.length || removed.length || didChangeMarkers) {
97
- super.setMarkers(uri, owner, newMarkers);
98
- this.onDidChangeBreakpointsEmitter.fire({ uri, added, removed, changed });
217
+ const removed = Array.from(oldById.values());
218
+ didChange ||= removed.length > 0;
219
+
220
+ if (!didChange) { return; }
221
+
222
+ if (newBps.length > 0) {
223
+ this.sourceBreakpoints.set(uri.toString(), [...newBps]);
224
+ } else {
225
+ this.sourceBreakpoints.delete(uri.toString());
99
226
  }
100
- return result;
227
+ this.fireOnDidChangeMarkers(uri);
228
+ this.onDidChangeBreakpointsEmitter.fire({ uri, added, removed, changed });
101
229
  }
102
230
 
103
- getLineBreakpoints(uri: URI, line: number): SourceBreakpoint[] {
104
- return this.findMarkers({
105
- uri,
106
- dataFilter: breakpoint => breakpoint.raw.line === line
107
- }).map(({ data }) => data);
231
+ removeBreakpoints(): void {
232
+ for (const uriString of [...this.sourceBreakpoints.keys()]) {
233
+ const uri = new URI(uriString);
234
+ const old = this.sourceBreakpoints.get(uriString) ?? [];
235
+ this.sourceBreakpoints.delete(uriString);
236
+ this.fireOnDidChangeMarkers(uri);
237
+ if (old.length) {
238
+ this.onDidChangeBreakpointsEmitter.fire({ uri, added: [], removed: old, changed: [] });
239
+ }
240
+ }
241
+ this.setFunctionBreakpoints([]);
242
+ this.setInstructionBreakpoints([]);
243
+ this.setDataBreakpoints([]);
108
244
  }
109
245
 
110
- getInlineBreakpoint(uri: URI, line: number, column: number): SourceBreakpoint | undefined {
111
- const marker = this.findMarkers({
112
- uri,
113
- dataFilter: breakpoint => breakpoint.raw.line === line && breakpoint.raw.column === column
114
- })[0];
115
- return marker && marker.data;
246
+ // ── Query helpers ──
247
+
248
+ getLineBreakpoints(uri: URI, line: number): DebugSourceBreakpoint[] {
249
+ return this.getBreakpoints(uri).filter(bp => bp.line === line);
116
250
  }
117
251
 
118
- getBreakpoints(uri?: URI): SourceBreakpoint[] {
119
- return this.findMarkers({ uri }).map(marker => marker.data);
252
+ getInlineBreakpoint(uri: URI, line: number, column: number): DebugSourceBreakpoint | undefined {
253
+ return this.getBreakpoints(uri).find(bp => bp.line === line && bp.column === column);
120
254
  }
121
255
 
122
- setBreakpoints(uri: URI, breakpoints: SourceBreakpoint[]): void {
123
- this.setMarkers(uri, this.owner, breakpoints.sort((a, b) => (a.raw.line - b.raw.line) || ((a.raw.column || 0) - (b.raw.column || 0))));
256
+ getBreakpointById(id: string): DebugBreakpoint | undefined {
257
+ for (const bp of this.allBreakpoints()) {
258
+ if (bp.id === id) { return bp; }
259
+ }
124
260
  }
125
261
 
126
- addBreakpoint(breakpoint: SourceBreakpoint): boolean {
127
- const uri = new URI(breakpoint.uri);
128
- const breakpoints = this.getBreakpoints(uri);
129
- const newBreakpoints = breakpoints.filter(({ raw }) => !(raw.line === breakpoint.raw.line && raw.column === breakpoint.raw.column));
130
- if (breakpoints.length === newBreakpoints.length) {
131
- newBreakpoints.push(breakpoint);
132
- this.setBreakpoints(uri, newBreakpoints);
133
- return true;
262
+ * allBreakpoints(): IterableIterator<DebugBreakpoint> {
263
+ for (const bps of this.sourceBreakpoints.values()) {
264
+ yield* bps;
265
+ }
266
+ yield* this.functionBreakpoints;
267
+ yield* this.instructionBreakpoints;
268
+ yield* this.exceptionBreakpoints;
269
+ yield* this.dataBreakpoints;
270
+ }
271
+
272
+ // ── Session data ──
273
+
274
+ updateSessionData(sessionId: string, sessionCapabilities: DebugProtocol.Capabilities, bps?: Map<string, DebugProtocol.Breakpoint>): void {
275
+ const bpCapabilities = this.toBpCapabilities(sessionCapabilities);
276
+ const updatedUris = new Map<string, DebugBreakpoint[]>();
277
+ for (const bp of this.allBreakpoints()) {
278
+ if (!bps) {
279
+ if (bp.update(sessionId, undefined)) {
280
+ MapUtils.addOrInsertWith(updatedUris, bp.uri.toString(), bp);
281
+ }
282
+ } else {
283
+ const dataForBp = bps.get(bp.id);
284
+ if (!dataForBp) { continue; }
285
+ bp.update(sessionId, { ...bpCapabilities, ...dataForBp });
286
+ MapUtils.addOrInsertWith(updatedUris, bp.uri.toString(), bp);
287
+ }
288
+ }
289
+ for (const changed of updatedUris.values()) {
290
+ this.fireTypedBreakpointEvent(changed[0].uri, [], changed, []);
291
+ }
292
+ }
293
+
294
+ protected toBpCapabilities(capabilities: DebugProtocol.Capabilities): BPCapabilities {
295
+ return {
296
+ supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints,
297
+ supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints,
298
+ supportsLogPoints: !!capabilities.supportsLogPoints,
299
+ supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints,
300
+ supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints,
301
+ supportsInstructionBreakpoints: !!capabilities.supportsInstructionBreakpoints,
302
+ };
303
+ }
304
+
305
+ // ── Breakpoint construction ──
306
+
307
+ protected toDebugSourceBreakpoint(source: SourceBreakpoint): DebugSourceBreakpoint {
308
+ return DebugSourceBreakpoint.create(source, this.getBreakpointOptions());
309
+ }
310
+
311
+ getBreakpointOptions(): DebugBreakpointOptions {
312
+ if (!this._breakpointOptions) {
313
+ this._breakpointOptions = {
314
+ labelProvider: this.labelProvider,
315
+ openerService: this.openerService,
316
+ commandService: this.commandService,
317
+ breakpoints: this
318
+ };
319
+ }
320
+ return this._breakpointOptions;
321
+ }
322
+
323
+ // ── Enable / disable ──
324
+
325
+ protected _breakpointsEnabled = true;
326
+
327
+ get breakpointsEnabled(): boolean {
328
+ return this._breakpointsEnabled;
329
+ }
330
+
331
+ set breakpointsEnabled(breakpointsEnabled: boolean) {
332
+ if (this._breakpointsEnabled !== breakpointsEnabled) {
333
+ this._breakpointsEnabled = breakpointsEnabled;
334
+ for (const uri of this.getUris()) {
335
+ this.fireOnDidChangeMarkers(new URI(uri));
336
+ }
337
+ this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
338
+ this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
339
+ this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
340
+ this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
134
341
  }
135
- return false;
136
342
  }
137
343
 
138
344
  enableAllBreakpoints(enabled: boolean): void {
139
345
  for (const uriString of this.getUris()) {
140
346
  let didChange = false;
141
347
  const uri = new URI(uriString);
142
- const markers = this.findMarkers({ uri });
143
- for (const marker of markers) {
144
- if (marker.data.enabled !== enabled) {
145
- marker.data.enabled = enabled;
146
- didChange = true;
147
- }
348
+ const bps = this.getBreakpoints(uri);
349
+ for (const bp of bps) {
350
+ didChange ||= this.doEnableBreakpoint(bp, enabled);
148
351
  }
149
352
  if (didChange) {
150
353
  this.fireOnDidChangeMarkers(uri);
354
+ this.onDidChangeBreakpointsEmitter.fire({ uri, added: [], removed: [], changed: [...bps] });
151
355
  }
152
356
  }
153
357
  let didChangeFunction = false;
154
- for (const breakpoint of (this.getFunctionBreakpoints() as BaseBreakpoint[]).concat(this.getInstructionBreakpoints())) {
155
- if (breakpoint.enabled !== enabled) {
156
- breakpoint.enabled = enabled;
358
+ for (const breakpoint of this.functionBreakpoints) {
359
+ if (breakpoint.origin.enabled !== enabled) {
360
+ breakpoint.origin.enabled = enabled;
157
361
  didChangeFunction = true;
158
-
159
362
  }
160
363
  }
161
364
  if (didChangeFunction) {
162
365
  this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
366
+ this.onDidChangeFunctionBreakpointsEmitter.fire({
367
+ uri: BreakpointManager.FUNCTION_URI, added: [], removed: [], changed: [...this.functionBreakpoints]
368
+ });
369
+ }
370
+ let didChangeInstruction = false;
371
+ for (const breakpoint of this.instructionBreakpoints) {
372
+ if (breakpoint.origin.enabled !== enabled) {
373
+ breakpoint.origin.enabled = enabled;
374
+ didChangeInstruction = true;
375
+ }
376
+ }
377
+ if (didChangeInstruction) {
378
+ this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
379
+ this.onDidChangeInstructionBreakpointsEmitter.fire({
380
+ uri: BreakpointManager.INSTRUCTION_URI, added: [], removed: [], changed: [...this.instructionBreakpoints]
381
+ });
382
+ }
383
+ let didChangeData = false;
384
+ for (const breakpoint of this.dataBreakpoints) {
385
+ if (breakpoint.origin.enabled !== enabled) {
386
+ breakpoint.origin.enabled = enabled;
387
+ didChangeData = true;
388
+ }
389
+ }
390
+ if (didChangeData) {
391
+ this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
392
+ this.onDidChangeDataBreakpointsEmitter.fire({
393
+ uri: BreakpointManager.DATA_URI, added: [], removed: [], changed: [...this.dataBreakpoints]
394
+ });
163
395
  }
164
396
  }
165
397
 
166
- protected _breakpointsEnabled = true;
167
- get breakpointsEnabled(): boolean {
168
- return this._breakpointsEnabled;
398
+ enableBreakpoint<T extends DebugBreakpoint>(breakpoint: T, enabled: boolean): void {
399
+ const didChange = this.doEnableBreakpoint(breakpoint, enabled);
400
+ if (didChange) {
401
+ this.fireBreakpointChanged(breakpoint);
402
+ }
169
403
  }
170
- set breakpointsEnabled(breakpointsEnabled: boolean) {
171
- if (this._breakpointsEnabled !== breakpointsEnabled) {
172
- this._breakpointsEnabled = breakpointsEnabled;
173
- for (const uri of this.getUris()) {
174
- this.fireOnDidChangeMarkers(new URI(uri));
175
- }
176
- this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
404
+
405
+ protected doEnableBreakpoint(breakpoint: DebugBreakpoint, enabled: boolean): boolean {
406
+ if (breakpoint.origin.enabled !== enabled) {
407
+ breakpoint.origin.enabled = enabled;
408
+ return true;
177
409
  }
410
+ return false;
178
411
  }
179
412
 
180
- protected readonly exceptionBreakpoints = new Map<string, ExceptionBreakpoint>();
413
+ // ── Generic update / fire ──
181
414
 
182
- getExceptionBreakpoint(filter: string): ExceptionBreakpoint | undefined {
183
- return this.exceptionBreakpoints.get(filter);
415
+ updateBreakpoint<U extends BaseBreakpoint, T extends DebugBreakpoint<U>>(bp: T, update: Partial<U['raw']>): void {
416
+ bp.origin.raw = { ...bp.origin.raw, ...update };
417
+ this.fireBreakpointChanged(bp);
184
418
  }
185
419
 
186
- getExceptionBreakpoints(): IterableIterator<ExceptionBreakpoint> {
187
- return this.exceptionBreakpoints.values();
420
+ fireBreakpointChanged(breakpoint: DebugBreakpoint): void {
421
+ this.fireOnDidChangeMarkers(breakpoint.uri);
422
+ this.fireTypedBreakpointEvent(breakpoint.uri, [], [breakpoint], []);
188
423
  }
189
424
 
190
- setExceptionBreakpoints(exceptionBreakpoints: ExceptionBreakpoint[]): void {
191
- const toRemove = new Set(this.exceptionBreakpoints.keys());
192
- for (const exceptionBreakpoint of exceptionBreakpoints) {
193
- const filter = exceptionBreakpoint.raw.filter;
194
- toRemove.delete(filter);
195
- this.exceptionBreakpoints.set(filter, exceptionBreakpoint);
425
+ protected fireTypedBreakpointEvent(uri: URI, added: DebugBreakpoint[], changed: DebugBreakpoint[], removed: DebugBreakpoint[]): void {
426
+ // All breakpoints in a single call are the same type (grouped by URI).
427
+ const sample = added[0] ?? changed[0] ?? removed[0];
428
+ if (!sample) { return; }
429
+ if (sample instanceof DebugSourceBreakpoint) {
430
+ this.onDidChangeBreakpointsEmitter.fire({ uri, added, changed, removed } as SourceBreakpointsChangeEvent);
431
+ } else if (sample instanceof DebugFunctionBreakpoint) {
432
+ this.onDidChangeFunctionBreakpointsEmitter.fire({ uri, added, changed, removed } as FunctionBreakpointsChangeEvent);
433
+ } else if (sample instanceof DebugInstructionBreakpoint) {
434
+ this.onDidChangeInstructionBreakpointsEmitter.fire({ uri, added, changed, removed } as InstructionBreakpointsChangeEvent);
435
+ } else if (sample instanceof DebugDataBreakpoint) {
436
+ this.onDidChangeDataBreakpointsEmitter.fire({ uri, added, changed, removed } as DataBreakpointsChangeEvent);
196
437
  }
197
- for (const filter of toRemove) {
198
- this.exceptionBreakpoints.delete(filter);
438
+ }
439
+
440
+ // ── Bulk remove by ID (plugin API) ──
441
+
442
+ removeBreakpointsById(ids: string[]): void {
443
+ const toRemove = new Set(ids);
444
+
445
+ // Source breakpoints
446
+ for (const [uriString, bps] of this.sourceBreakpoints.entries()) {
447
+ const retained = bps.filter(bp => !toRemove.has(bp.id));
448
+ if (retained.length !== bps.length) {
449
+ const removed = bps.filter(bp => toRemove.has(bp.id));
450
+ const uri = new URI(uriString);
451
+ if (retained.length > 0) {
452
+ this.sourceBreakpoints.set(uriString, retained);
453
+ } else {
454
+ this.sourceBreakpoints.delete(uriString);
455
+ }
456
+ this.fireOnDidChangeMarkers(uri);
457
+ this.onDidChangeBreakpointsEmitter.fire({ uri, removed, added: [], changed: [] });
458
+ }
199
459
  }
200
- if (toRemove.size || exceptionBreakpoints.length) {
201
- this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
460
+
461
+ // Function breakpoints
462
+ const functionRemoved: DebugFunctionBreakpoint[] = [];
463
+ this.functionBreakpoints = this.functionBreakpoints.filter(bp => {
464
+ if (toRemove.has(bp.id)) {
465
+ functionRemoved.push(bp);
466
+ return false;
467
+ }
468
+ return true;
469
+ });
470
+ if (functionRemoved.length) {
471
+ this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
472
+ this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: BreakpointManager.FUNCTION_URI, removed: functionRemoved, added: [], changed: [] });
473
+ }
474
+
475
+ // Instruction breakpoints
476
+ const instructionRemoved: DebugInstructionBreakpoint[] = [];
477
+ this.instructionBreakpoints = this.instructionBreakpoints.filter(bp => {
478
+ if (toRemove.has(bp.id)) {
479
+ instructionRemoved.push(bp);
480
+ return false;
481
+ }
482
+ return true;
483
+ });
484
+ if (instructionRemoved.length) {
485
+ this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
486
+ this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, removed: instructionRemoved, added: [], changed: [] });
487
+ }
488
+
489
+ // Data breakpoints
490
+ const dataRemoved: DebugDataBreakpoint[] = [];
491
+ this.dataBreakpoints = this.dataBreakpoints.filter(bp => {
492
+ if (toRemove.has(bp.id)) {
493
+ dataRemoved.push(bp);
494
+ return false;
495
+ }
496
+ return true;
497
+ });
498
+ if (dataRemoved.length) {
499
+ this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
500
+ this.onDidChangeDataBreakpointsEmitter.fire({ uri: BreakpointManager.DATA_URI, removed: dataRemoved, added: [], changed: [] });
202
501
  }
203
502
  }
204
503
 
205
- toggleExceptionBreakpoint(filter: string): void {
206
- const breakpoint = this.getExceptionBreakpoint(filter);
207
- if (breakpoint) {
208
- breakpoint.enabled = !breakpoint.enabled;
209
- this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
504
+ // ── Exception breakpoints ──
505
+
506
+ protected exceptionBreakpoints = new Array<DebugExceptionBreakpoint>();
507
+
508
+ getExceptionBreakpoint(filter: DebugProtocol.ExceptionBreakpointsFilter): DebugExceptionBreakpoint | undefined {
509
+ return this.exceptionBreakpoints.find(candidate => ExceptionBreakpoint.matches(candidate.origin.raw, filter));
510
+ }
511
+
512
+ getExceptionBreakpoints(): readonly DebugExceptionBreakpoint[] {
513
+ return this.exceptionBreakpoints;
514
+ }
515
+
516
+ addExceptionBreakpoints(filters: DebugProtocol.ExceptionBreakpointsFilter[], sessionId: string): void {
517
+ for (const filter of filters) {
518
+ let bp = this.exceptionBreakpoints.find(candidate => ExceptionBreakpoint.matches(candidate.origin.raw, filter));
519
+ if (!bp) {
520
+ bp = DebugExceptionBreakpoint.create(ExceptionBreakpoint.create(filter), this.getBreakpointOptions());
521
+ this.exceptionBreakpoints.push(bp);
522
+ }
523
+ bp.setSessionEnablement(sessionId, true);
524
+ this.doUpdateExceptionBreakpointVisibility(sessionId);
210
525
  }
526
+ this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
211
527
  }
212
528
 
213
- updateExceptionBreakpoint(filter: string, options: Partial<Pick<ExceptionBreakpoint, 'condition' | 'enabled'>>): void {
214
- const breakpoint = this.getExceptionBreakpoint(filter);
215
- if (breakpoint) {
216
- Object.assign(breakpoint, options);
217
- this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
529
+ updateExceptionBreakpointVisibility(sessionId: string): void {
530
+ this.doUpdateExceptionBreakpointVisibility(sessionId);
531
+ this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
532
+ }
533
+
534
+ clearExceptionSessionEnablement(sessionId: string): void {
535
+ for (const bp of this.exceptionBreakpoints) {
536
+ bp.setSessionEnablement(sessionId, false);
537
+ }
538
+ this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
539
+ }
540
+
541
+ protected doUpdateExceptionBreakpointVisibility(sessionId: string): void {
542
+ for (const bp of this.exceptionBreakpoints) {
543
+ bp.setPersistentVisibility(bp.isEnabledForSession(sessionId));
218
544
  }
219
545
  }
220
546
 
221
- protected functionBreakpoints: FunctionBreakpoint[] = [];
547
+ // ── Function breakpoints ──
222
548
 
223
- getFunctionBreakpoints(): FunctionBreakpoint[] {
549
+ protected functionBreakpoints: DebugFunctionBreakpoint[] = [];
550
+
551
+ getFunctionBreakpoints(): readonly DebugFunctionBreakpoint[] {
224
552
  return this.functionBreakpoints;
225
553
  }
226
554
 
227
555
  setFunctionBreakpoints(functionBreakpoints: FunctionBreakpoint[]): void {
228
- const oldBreakpoints = new Map(this.functionBreakpoints.map(b => [b.id, b] as [string, FunctionBreakpoint]));
556
+ const oldBreakpoints = new Map(this.functionBreakpoints.map(b => [b.id, b]));
229
557
 
230
- this.functionBreakpoints = functionBreakpoints;
558
+ this.functionBreakpoints = functionBreakpoints.map(bp => {
559
+ const existing = oldBreakpoints.get(bp.id);
560
+ if (existing) { return existing; }
561
+ return DebugFunctionBreakpoint.create(bp, this.getBreakpointOptions());
562
+ });
231
563
  this.fireOnDidChangeMarkers(BreakpointManager.FUNCTION_URI);
232
564
 
233
- const added: FunctionBreakpoint[] = [];
234
- const removed: FunctionBreakpoint[] = [];
235
- const changed: FunctionBreakpoint[] = [];
565
+ const added: DebugFunctionBreakpoint[] = [];
566
+ const removed: DebugFunctionBreakpoint[] = [];
567
+ const changed: DebugFunctionBreakpoint[] = [];
236
568
  const ids = new Set<string>();
237
- for (const newBreakpoint of functionBreakpoints) {
569
+ for (const newBreakpoint of this.functionBreakpoints) {
238
570
  ids.add(newBreakpoint.id);
239
571
  if (oldBreakpoints.has(newBreakpoint.id)) {
240
572
  changed.push(newBreakpoint);
@@ -250,50 +582,102 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
250
582
  this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: BreakpointManager.FUNCTION_URI, added, removed, changed });
251
583
  }
252
584
 
253
- protected instructionBreakpoints: InstructionBreakpoint[] = [];
585
+ addFunctionBreakpoint(bp: FunctionBreakpoint): void {
586
+ const duplicate = this.functionBreakpoints.find(c => c.origin.raw.name === bp.raw.name);
587
+ if (duplicate) { return; }
588
+ const newBp = DebugFunctionBreakpoint.create(bp, this.getBreakpointOptions());
589
+ this.functionBreakpoints = [...this.functionBreakpoints, newBp];
590
+ this.fireOnDidChangeMarkers(newBp.uri);
591
+ this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: newBp.uri, added: [newBp], changed: [], removed: [] });
592
+ }
593
+
594
+ updateFunctionBreakpoint(bp: DebugFunctionBreakpoint, update: Partial<DebugProtocol.FunctionBreakpoint>): void {
595
+ if (!this.functionBreakpoints.includes(bp)) { return; }
596
+ const removed: DebugFunctionBreakpoint[] = [];
597
+ if ('name' in update && !update.name) {
598
+ throw new Error('Name field of function breakpoint must be populated.');
599
+ } else if ('name' in update) {
600
+ this.functionBreakpoints = this.functionBreakpoints.filter(candidate => {
601
+ if (candidate !== bp && candidate.origin.raw.name === update.name) {
602
+ removed.push(candidate);
603
+ return false;
604
+ }
605
+ return true;
606
+ });
607
+ }
608
+ bp.origin.raw = { ...bp.origin.raw, ...update };
609
+ this.fireOnDidChangeMarkers(bp.uri);
610
+ this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: bp.uri, changed: [bp], removed, added: [] });
611
+ }
254
612
 
255
- getInstructionBreakpoints(): ReadonlyArray<InstructionBreakpoint> {
256
- return Object.freeze(this.instructionBreakpoints.slice());
613
+ removeFunctionBreakpoint(breakpoint: DebugFunctionBreakpoint): void {
614
+ const index = this.functionBreakpoints.indexOf(breakpoint);
615
+ if (index === -1) { return; }
616
+ const removed = this.functionBreakpoints.splice(index, 1);
617
+ this.fireOnDidChangeMarkers(breakpoint.uri);
618
+ this.onDidChangeFunctionBreakpointsEmitter.fire({ uri: breakpoint.uri, removed, added: [], changed: [] });
257
619
  }
258
620
 
259
- hasBreakpoints(): boolean {
260
- return Boolean(this.getUris().next().value || this.functionBreakpoints.length || this.instructionBreakpoints.length);
621
+ // ── Instruction breakpoints ──
622
+
623
+ protected instructionBreakpoints: DebugInstructionBreakpoint[] = [];
624
+
625
+ getInstructionBreakpoints(): ReadonlyArray<DebugInstructionBreakpoint> {
626
+ return this.instructionBreakpoints;
261
627
  }
262
628
 
263
629
  protected setInstructionBreakpoints(newBreakpoints: InstructionBreakpoint[]): void {
264
- const { added, removed, changed } = diff(this.instructionBreakpoints, newBreakpoints, bp => bp.id);
265
- this.instructionBreakpoints = newBreakpoints.slice();
630
+ const oldBreakpoints = new Map(this.instructionBreakpoints.map(bp => [bp.id, bp]));
631
+ const currentBreakpoints = newBreakpoints.map(bp => {
632
+ const existing = oldBreakpoints.get(bp.id);
633
+ if (existing) { return existing; }
634
+ return DebugInstructionBreakpoint.create(bp, this.getBreakpointOptions());
635
+ });
636
+ const added: DebugInstructionBreakpoint[] = [];
637
+ const changed: DebugInstructionBreakpoint[] = [];
638
+ for (const breakpoint of currentBreakpoints) {
639
+ const old = oldBreakpoints.get(breakpoint.id);
640
+ if (old) {
641
+ changed.push(old);
642
+ } else {
643
+ added.push(breakpoint);
644
+ }
645
+ oldBreakpoints.delete(breakpoint.id);
646
+ }
647
+ const removed = Array.from(oldBreakpoints.values());
648
+ this.instructionBreakpoints = currentBreakpoints;
266
649
  this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
267
650
  this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, added, removed, changed });
268
651
  }
269
652
 
270
653
  addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): void {
271
- this.setInstructionBreakpoints(this.instructionBreakpoints.concat(InstructionBreakpoint.create({
654
+ const duplicate = this.instructionBreakpoints.find(
655
+ c => c.origin.raw.instructionReference === address && (c.origin.raw.offset ?? 0) === (offset ?? 0)
656
+ );
657
+ if (duplicate) { return; }
658
+ const newBp = DebugInstructionBreakpoint.create(InstructionBreakpoint.create({
272
659
  instructionReference: address,
273
660
  offset,
274
661
  condition,
275
662
  hitCondition,
276
- })));
663
+ }), this.getBreakpointOptions());
664
+ this.instructionBreakpoints = [...this.instructionBreakpoints, newBp];
665
+ this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
666
+ this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, added: [newBp], removed: [], changed: [] });
277
667
  }
278
668
 
279
- updateInstructionBreakpoint(id: string, options: Partial<Pick<InstructionBreakpoint, 'condition' | 'hitCondition' | 'enabled'>>): void {
280
- const breakpoint = this.instructionBreakpoints.find(candidate => id === candidate.id);
281
- if (breakpoint) {
282
- Object.assign(breakpoint, options);
283
- this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
284
- this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, changed: [breakpoint], added: [], removed: [] });
285
- }
669
+ removeInstructionBreakpoint(breakpoint: DebugInstructionBreakpoint): void {
670
+ const index = this.instructionBreakpoints.indexOf(breakpoint);
671
+ if (index === -1) { return; }
672
+ const removed = this.instructionBreakpoints.splice(index, 1);
673
+ this.fireOnDidChangeMarkers(breakpoint.uri);
674
+ this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: breakpoint.uri, removed, added: [], changed: [] });
286
675
  }
287
676
 
288
- removeInstructionBreakpoint(address?: string): void {
289
- if (!address) {
290
- this.clearInstructionBreakpoints();
291
- }
292
- const breakpointIndex = this.instructionBreakpoints.findIndex(breakpoint => breakpoint.instructionReference === address);
293
- if (breakpointIndex !== -1) {
294
- const removed = this.instructionBreakpoints.splice(breakpointIndex, 1);
295
- this.fireOnDidChangeMarkers(BreakpointManager.INSTRUCTION_URI);
296
- this.onDidChangeInstructionBreakpointsEmitter.fire({ uri: BreakpointManager.INSTRUCTION_URI, added: [], changed: [], removed });
677
+ removeInstructionBreakpointAt(address: string): void {
678
+ const match = this.instructionBreakpoints.find(candidate => candidate.origin.raw.instructionReference === address);
679
+ if (match) {
680
+ this.removeInstructionBreakpoint(match);
297
681
  }
298
682
  }
299
683
 
@@ -301,46 +685,66 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
301
685
  this.setInstructionBreakpoints([]);
302
686
  }
303
687
 
304
- protected dataBreakpoints: DataBreakpoint[] = [];
688
+ // ── Data breakpoints ──
305
689
 
306
- getDataBreakpoints(): readonly DataBreakpoint[] {
307
- return Object.freeze(this.dataBreakpoints.slice());
690
+ protected dataBreakpoints: DebugDataBreakpoint[] = [];
691
+
692
+ getDataBreakpoints(): readonly DebugDataBreakpoint[] {
693
+ return this.dataBreakpoints;
308
694
  }
309
695
 
310
696
  setDataBreakpoints(breakpoints: DataBreakpoint[]): void {
311
- const { added, removed, changed } = diff(this.dataBreakpoints, breakpoints, ({ id }) => id);
312
- this.dataBreakpoints = breakpoints.slice();
697
+ const oldBreakpoints = new Map(this.dataBreakpoints.map(bp => [bp.id, bp]));
698
+ const newBreakpoints = breakpoints.map(bp => {
699
+ const existing = oldBreakpoints.get(bp.id);
700
+ if (existing) { return existing; }
701
+ return DebugDataBreakpoint.create(bp, this.getBreakpointOptions());
702
+ });
703
+ const added: DebugDataBreakpoint[] = [];
704
+ const changed: DebugDataBreakpoint[] = [];
705
+ for (const bp of newBreakpoints) {
706
+ if (oldBreakpoints.has(bp.id)) {
707
+ changed.push(bp);
708
+ } else {
709
+ added.push(bp);
710
+ }
711
+ oldBreakpoints.delete(bp.id);
712
+ }
713
+ const removed = Array.from(oldBreakpoints.values());
714
+ this.dataBreakpoints = newBreakpoints;
313
715
  this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
314
716
  this.onDidChangeDataBreakpointsEmitter.fire({ uri: BreakpointManager.DATA_URI, added, removed, changed });
315
717
  }
316
718
 
317
719
  addDataBreakpoint(breakpoint: DataBreakpoint): void {
318
- this.setDataBreakpoints(this.dataBreakpoints.concat(breakpoint));
720
+ const duplicate = this.dataBreakpoints.find(c => c.origin.raw.dataId === breakpoint.raw.dataId);
721
+ if (duplicate) { return; }
722
+ const newBp = DebugDataBreakpoint.create(breakpoint, this.getBreakpointOptions());
723
+ this.dataBreakpoints = [...this.dataBreakpoints, newBp];
724
+ this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
725
+ this.onDidChangeDataBreakpointsEmitter.fire({ uri: BreakpointManager.DATA_URI, added: [newBp], removed: [], changed: [] });
319
726
  }
320
727
 
321
- updateDataBreakpoint(id: string, options: { enabled?: boolean; raw?: Partial<Omit<DebugProtocol.DataBreakpoint, 'dataId'>> }): void {
322
- const breakpoint = this.dataBreakpoints.find(bp => bp.id === id);
323
- if (!breakpoint) { return; }
324
- Object.assign(breakpoint.raw, options);
325
- breakpoint.enabled = options.enabled ?? breakpoint.enabled;
326
- this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
327
- this.onDidChangeDataBreakpointsEmitter.fire({ uri: BreakpointManager.DATA_URI, added: [], removed: [], changed: [breakpoint] });
728
+ updateDataBreakpoint(bp: DebugDataBreakpoint, options: { enabled?: boolean; raw?: Partial<Omit<DebugProtocol.DataBreakpoint, 'dataId'>> }): void {
729
+ if (!this.dataBreakpoints.includes(bp)) { return; }
730
+ if (options.raw) {
731
+ Object.assign(bp.origin.raw, options.raw);
732
+ }
733
+ if (options.enabled !== undefined) {
734
+ bp.origin.enabled = options.enabled;
735
+ }
736
+ this.fireBreakpointChanged(bp);
328
737
  }
329
738
 
330
- removeDataBreakpoint(id: string): void {
331
- const index = this.dataBreakpoints.findIndex(bp => bp.id === id);
739
+ removeDataBreakpoint(bp: DebugDataBreakpoint): void {
740
+ const index = this.dataBreakpoints.indexOf(bp);
332
741
  if (index < 0) { return; }
333
- const removed = this.dataBreakpoints.splice(index);
742
+ const removed = this.dataBreakpoints.splice(index, 1);
334
743
  this.fireOnDidChangeMarkers(BreakpointManager.DATA_URI);
335
744
  this.onDidChangeDataBreakpointsEmitter.fire({ uri: BreakpointManager.DATA_URI, added: [], removed, changed: [] });
336
745
  }
337
746
 
338
- removeBreakpoints(): void {
339
- this.cleanAllMarkers();
340
- this.setFunctionBreakpoints([]);
341
- this.setInstructionBreakpoints([]);
342
- this.setDataBreakpoints([]);
343
- }
747
+ // ── Persistence ──
344
748
 
345
749
  async load(): Promise<void> {
346
750
  const data = await this.storage.getData<BreakpointManager.Data>('breakpoints', {
@@ -356,11 +760,15 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
356
760
  this.setFunctionBreakpoints(data.functionBreakpoints);
357
761
  }
358
762
  if (data.exceptionBreakpoints) {
359
- this.setExceptionBreakpoints(data.exceptionBreakpoints);
763
+ this.exceptionBreakpoints = data.exceptionBreakpoints.map(bp => DebugExceptionBreakpoint.create(bp, this.getBreakpointOptions()));
764
+ this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI);
360
765
  }
361
766
  if (data.instructionBreakpoints) {
362
767
  this.setInstructionBreakpoints(data.instructionBreakpoints);
363
768
  }
769
+ if (data.dataBreakpoints) {
770
+ this.setDataBreakpoints(data.dataBreakpoints);
771
+ }
364
772
  }
365
773
 
366
774
  save(): void {
@@ -368,28 +776,27 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
368
776
  breakpointsEnabled: this._breakpointsEnabled,
369
777
  breakpoints: {}
370
778
  };
371
- const uris = this.getUris();
372
- for (const uri of uris) {
373
- data.breakpoints[uri] = this.findMarkers({ uri: new URI(uri) }).map(marker => marker.data);
779
+ for (const uri of this.getUris()) {
780
+ data.breakpoints[uri] = (this.sourceBreakpoints.get(uri) ?? []).map(bp => bp.origin);
374
781
  }
375
782
  if (this.functionBreakpoints.length) {
376
- data.functionBreakpoints = this.functionBreakpoints;
783
+ data.functionBreakpoints = this.functionBreakpoints.map(({ origin }) => origin);
377
784
  }
378
- if (this.exceptionBreakpoints.size) {
379
- data.exceptionBreakpoints = [...this.exceptionBreakpoints.values()];
785
+ if (this.exceptionBreakpoints.length) {
786
+ data.exceptionBreakpoints = this.exceptionBreakpoints.filter(candidate => candidate.isPersistentlyVisible()).map(({ origin }) => origin);
380
787
  }
381
788
  if (this.instructionBreakpoints.length) {
382
- data.instructionBreakpoints = this.instructionBreakpoints;
789
+ data.instructionBreakpoints = this.instructionBreakpoints.map(({ origin }) => origin);
383
790
  }
384
- const dataBreakpointsToStore = this.dataBreakpoints.filter(candidate => candidate.info.canPersist);
791
+ const dataBreakpointsToStore = this.dataBreakpoints.filter(candidate => candidate.origin.info.canPersist);
385
792
  if (dataBreakpointsToStore.length) {
386
- data.dataBreakpoints = dataBreakpointsToStore;
793
+ data.dataBreakpoints = dataBreakpointsToStore.map(({ origin }) => origin);
387
794
  }
388
795
 
389
796
  this.storage.setData('breakpoints', data);
390
797
  }
391
-
392
798
  }
799
+
393
800
  export namespace BreakpointManager {
394
801
  export interface Data {
395
802
  breakpointsEnabled: boolean;
@@ -402,21 +809,3 @@ export namespace BreakpointManager {
402
809
  dataBreakpoints?: DataBreakpoint[];
403
810
  }
404
811
  }
405
-
406
- export function diff<T>(prevs: T[], nexts: T[], toKey: (member: T) => string): { added: T[], removed: T[], changed: T[] } {
407
- const old = new Map(prevs.map(item => [toKey(item), item]));
408
- const current = new Map(nexts.map(item => [toKey(item), item]));
409
- const added = [];
410
- const changed = [];
411
- for (const [id, next] of current.entries()) {
412
- const prev = old.get(id);
413
- if (prev) {
414
- changed.push(prev);
415
- } else {
416
- added.push(next);
417
- }
418
- old.delete(id);
419
- }
420
- const removed = Array.from(old.values());
421
- return { added, removed, changed };
422
- }