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