@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
@@ -58,13 +58,24 @@ export class DebugEditorModel implements Disposable {
58
58
  protected uri: URI;
59
59
 
60
60
  protected breakpointDecorations: string[] = [];
61
- protected breakpointRanges = new Map<string, [monaco.Range, SourceBreakpoint]>();
61
+ protected breakpointRanges = new Map<string, [monaco.Range, DebugSourceBreakpoint]>();
62
62
 
63
63
  protected currentBreakpointDecorations: string[] = [];
64
64
 
65
65
  protected editorDecorations: string[] = [];
66
66
 
67
- protected updatingDecorations = false;
67
+ /**
68
+ * Set during `render()` to prevent `onDidChangeDecorations` → `updateBreakpoints()`
69
+ * from reading back the decoration positions we just wrote, which would produce
70
+ * a spurious or circular `setBreakpoints` call.
71
+ */
72
+ protected ignoreDecorationsChangedEvent = false;
73
+
74
+ /**
75
+ * Set during `updateBreakpoints()` to prevent the resulting
76
+ * `onDidChangeBreakpoints` from re-entering `render()`.
77
+ */
78
+ protected ignoreBreakpointsChangeEvent = false;
68
79
  protected toDisposeOnModelChange = new DisposableCollection();
69
80
 
70
81
  @inject(DebugHoverWidget)
@@ -111,12 +122,12 @@ export class DebugEditorModel implements Disposable {
111
122
  this.sessions.onDidChange(() => this.update()),
112
123
  this.toDisposeOnUpdate,
113
124
  Disposable.create(() => this.toDisposeOnModelChange.dispose()),
114
- this.sessionManager.onDidChangeBreakpoints(({ session, uri }) => {
115
- if ((!session || session === this.sessionManager.currentSession) && uri.isEqual(this.uri)) {
125
+ this.breakpoints.onDidChangeBreakpoints(event => {
126
+ if (!this.ignoreBreakpointsChangeEvent) {
116
127
  this.render();
117
128
  }
129
+ this.closeBreakpointIfAffected(event);
118
130
  }),
119
- this.breakpoints.onDidChangeBreakpoints(event => this.closeBreakpointIfAffected(event)),
120
131
  ]);
121
132
  this.updateModel();
122
133
  }
@@ -237,8 +248,13 @@ export class DebugEditorModel implements Disposable {
237
248
  }
238
249
 
239
250
  render(): void {
240
- this.renderBreakpoints();
241
- this.renderCurrentBreakpoints();
251
+ this.ignoreDecorationsChangedEvent = true;
252
+ try {
253
+ this.renderBreakpoints();
254
+ this.renderCurrentBreakpoints();
255
+ } finally {
256
+ this.ignoreDecorationsChangedEvent = false;
257
+ }
242
258
  }
243
259
  protected renderBreakpoints(): void {
244
260
  const breakpoints = this.breakpoints.getBreakpoints(this.uri);
@@ -246,12 +262,12 @@ export class DebugEditorModel implements Disposable {
246
262
  this.breakpointDecorations = this.deltaDecorations(this.breakpointDecorations, decorations);
247
263
  this.updateBreakpointRanges(breakpoints);
248
264
  }
249
- protected createBreakpointDecorations(breakpoints: SourceBreakpoint[]): monaco.editor.IModelDeltaDecoration[] {
265
+ protected createBreakpointDecorations(breakpoints: readonly DebugSourceBreakpoint[]): monaco.editor.IModelDeltaDecoration[] {
250
266
  return breakpoints.map(breakpoint => this.createBreakpointDecoration(breakpoint));
251
267
  }
252
- protected createBreakpointDecoration(breakpoint: SourceBreakpoint): monaco.editor.IModelDeltaDecoration {
253
- const lineNumber = breakpoint.raw.line;
254
- const column = breakpoint.raw.column || this.editor.getControl().getModel()?.getLineFirstNonWhitespaceColumn(lineNumber) || 1;
268
+ protected createBreakpointDecoration(breakpoint: DebugSourceBreakpoint): monaco.editor.IModelDeltaDecoration {
269
+ const lineNumber = breakpoint.line;
270
+ const column = breakpoint.column || this.editor.getControl().getModel()?.getLineFirstNonWhitespaceColumn(lineNumber) || 1;
255
271
  const range = new monaco.Range(lineNumber, column, lineNumber, column + 1);
256
272
  return {
257
273
  range,
@@ -261,7 +277,7 @@ export class DebugEditorModel implements Disposable {
261
277
  };
262
278
  }
263
279
 
264
- protected updateBreakpointRanges(breakpoints: SourceBreakpoint[]): void {
280
+ protected updateBreakpointRanges(breakpoints: readonly DebugSourceBreakpoint[]): void {
265
281
  this.breakpointRanges.clear();
266
282
  for (let i = 0; i < this.breakpointDecorations.length; i++) {
267
283
  const decoration = this.breakpointDecorations[i];
@@ -278,8 +294,19 @@ export class DebugEditorModel implements Disposable {
278
294
  this.currentBreakpointDecorations = this.deltaDecorations(this.currentBreakpointDecorations, decorations);
279
295
  }
280
296
  protected createCurrentBreakpointDecorations(): monaco.editor.IModelDeltaDecoration[] {
281
- const breakpoints = this.sessions.getBreakpoints(this.uri);
282
- return breakpoints.map(breakpoint => this.createCurrentBreakpointDecoration(breakpoint));
297
+ const breakpoints = this.breakpoints.getBreakpoints(this.uri);
298
+ // Deduplicate by rendered position: when multiple breakpoints resolve
299
+ // to the same (line, column) — e.g. via source-map collapsing — only
300
+ // create one decoration to avoid double dots in the editor.
301
+ const seen = new Set<string>();
302
+ const result: monaco.editor.IModelDeltaDecoration[] = [];
303
+ for (const bp of breakpoints) {
304
+ const key = `${bp.line}:${bp.column ?? 0}`;
305
+ if (seen.has(key)) { continue; }
306
+ seen.add(key);
307
+ result.push(this.createCurrentBreakpointDecoration(bp));
308
+ }
309
+ return result;
283
310
  }
284
311
  protected createCurrentBreakpointDecoration(breakpoint: DebugSourceBreakpoint): monaco.editor.IModelDeltaDecoration {
285
312
  const lineNumber = breakpoint.line;
@@ -301,11 +328,16 @@ export class DebugEditorModel implements Disposable {
301
328
  protected updateBreakpoints(): void {
302
329
  if (this.areBreakpointsAffected()) {
303
330
  const breakpoints = this.createBreakpoints();
304
- this.breakpoints.setBreakpoints(this.uri, breakpoints);
331
+ this.ignoreBreakpointsChangeEvent = true;
332
+ try {
333
+ this.breakpoints.setBreakpoints(this.uri, breakpoints);
334
+ } finally {
335
+ this.ignoreBreakpointsChangeEvent = false;
336
+ }
305
337
  }
306
338
  }
307
339
  protected areBreakpointsAffected(): boolean {
308
- if (this.updatingDecorations || !this.editor.getControl().getModel()) {
340
+ if (this.ignoreDecorationsChangedEvent || !this.editor.getControl().getModel()) {
309
341
  return false;
310
342
  }
311
343
  for (const decoration of this.breakpointDecorations) {
@@ -328,11 +360,11 @@ export class DebugEditorModel implements Disposable {
328
360
  const column = range.startColumn;
329
361
  const oldBreakpoint = this.breakpointRanges.get(decoration)?.[1];
330
362
  if (oldBreakpoint) {
331
- const isLineBreakpoint = oldBreakpoint.raw.line !== undefined && oldBreakpoint.raw.column === undefined;
363
+ const isLineBreakpoint = oldBreakpoint.origin.raw.line !== undefined && oldBreakpoint.origin.raw.column === undefined;
332
364
  const position = isLineBreakpoint ? `${line}` : `${line}:${column}`;
333
365
  if (!positions.has(position)) {
334
366
  const change = isLineBreakpoint ? { line } : { line, column };
335
- const breakpoint = SourceBreakpoint.create(uri, change, oldBreakpoint);
367
+ const breakpoint = SourceBreakpoint.create(uri, change, oldBreakpoint.origin);
336
368
  breakpoints.push(breakpoint);
337
369
  positions.add(position);
338
370
  }
@@ -345,16 +377,17 @@ export class DebugEditorModel implements Disposable {
345
377
  get position(): monaco.Position {
346
378
  return this.editor.getControl().getPosition()!;
347
379
  }
380
+
348
381
  getBreakpoint(position: monaco.Position = this.position): DebugSourceBreakpoint | undefined {
349
382
  return this.getInlineBreakpoint(position) || this.getLineBreakpoints(position)[0];
350
383
  }
351
384
 
352
385
  getInlineBreakpoint(position: monaco.Position = this.position): DebugSourceBreakpoint | undefined {
353
- return this.sessions.getInlineBreakpoint(this.uri, position.lineNumber, position.column);
386
+ return this.breakpoints.getBreakpoints(this.uri).find(candidate => candidate.line === position.lineNumber && candidate.column === position.column);
354
387
  }
355
388
 
356
389
  protected getLineBreakpoints(position: monaco.Position = this.position): DebugSourceBreakpoint[] {
357
- return this.sessions.getLineBreakpoints(this.uri, position.lineNumber);
390
+ return this.breakpoints.getBreakpoints(this.uri).filter(candidate => candidate.line === position.lineNumber);
358
391
  }
359
392
 
360
393
  protected addBreakpoint(raw: DebugProtocol.SourceBreakpoint): void {
@@ -374,13 +407,8 @@ export class DebugEditorModel implements Disposable {
374
407
  }
375
408
 
376
409
  addInlineBreakpoint(): void {
377
- const { position } = this;
378
- const { lineNumber, column } = position;
379
- const breakpoint = this.getInlineBreakpoint(position);
380
- if (breakpoint) {
381
- return;
382
- }
383
- this.addBreakpoint({ line: lineNumber, column });
410
+ const { position: { lineNumber: line, column } } = this;
411
+ this.addBreakpoint({ line, column });
384
412
  }
385
413
 
386
414
  acceptBreakpoint(): void {
@@ -388,7 +416,7 @@ export class DebugEditorModel implements Disposable {
388
416
  if (position && values) {
389
417
  const breakpoint = position.column > 0 ? this.getInlineBreakpoint(position) : this.getLineBreakpoints(position)[0];
390
418
  if (breakpoint) {
391
- breakpoint.updateOrigins(values);
419
+ this.breakpoints.updateBreakpoint(breakpoint, values);
392
420
  } else {
393
421
  const { lineNumber } = position;
394
422
  const column = position.column > 0 ? position.column : undefined;
@@ -446,7 +474,7 @@ export class DebugEditorModel implements Disposable {
446
474
  return;
447
475
  }
448
476
  for (const breakpoint of removed) {
449
- if (breakpoint.raw.line === position.lineNumber) {
477
+ if (breakpoint.line === position.lineNumber) {
450
478
  this.breakpointWidget.hide();
451
479
  break;
452
480
  }
@@ -479,12 +507,7 @@ export class DebugEditorModel implements Disposable {
479
507
  }
480
508
 
481
509
  protected deltaDecorations(oldDecorations: string[], newDecorations: monaco.editor.IModelDeltaDecoration[]): string[] {
482
- this.updatingDecorations = true;
483
- try {
484
- return this.editor.getControl().deltaDecorations(oldDecorations, newDecorations);
485
- } finally {
486
- this.updatingDecorations = false;
487
- }
510
+ return this.editor.getControl().deltaDecorations(oldDecorations, newDecorations);
488
511
  }
489
512
 
490
513
  static STICKINESS = monaco.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
@@ -0,0 +1,51 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { MaybePromise, URI } from '@theia/core';
19
+ import { OpenHandler, OpenerOptions } from '@theia/core/lib/browser';
20
+ import { EditorManager } from '@theia/editor/lib/browser';
21
+ import { BreakpointManager } from '../breakpoint/breakpoint-manager';
22
+ import { DebugSessionManager } from '../debug-session-manager';
23
+ import { DEBUG_BREAKPOINT_SCHEME } from '../breakpoint/breakpoint-marker';
24
+
25
+ @injectable()
26
+ export class DebugBreakpointOpener implements OpenHandler {
27
+ @inject(BreakpointManager) protected readonly breakpointManager: BreakpointManager;
28
+ @inject(DebugSessionManager) protected readonly sessionManager: DebugSessionManager;
29
+ @inject(EditorManager) protected readonly editorManager: EditorManager;
30
+
31
+ readonly id = 'debug-breakpoint-opener';
32
+
33
+ canHandle(uri: URI, options?: OpenerOptions | undefined): MaybePromise<number> {
34
+ return uri.scheme === DEBUG_BREAKPOINT_SCHEME ? 150 : -1;
35
+ }
36
+
37
+ open(uri: URI, options?: OpenerOptions | undefined): MaybePromise<object | undefined> {
38
+ if (uri.scheme !== DEBUG_BREAKPOINT_SCHEME) { throw new Error(`Unexpected scheme. Expected '${DEBUG_BREAKPOINT_SCHEME}' but got '${uri.scheme}'.`); }
39
+ const bpId = uri.authority;
40
+ const bp = this.breakpointManager.getBreakpointById(bpId);
41
+ if (!bp) { return; }
42
+ if (bp.raw?.source) {
43
+ const session = this.sessionManager.getSession(bp.raw.sessionId);
44
+ const source = session?.getSource(bp.raw.source);
45
+ if (source) {
46
+ return source.open(options);
47
+ }
48
+ }
49
+ return this.editorManager.open(bp.uri, options);
50
+ }
51
+ }
@@ -17,24 +17,19 @@
17
17
  import * as React from '@theia/core/shared/react';
18
18
  import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol';
19
19
  import URI from '@theia/core/lib/common/uri';
20
- import { EditorManager } from '@theia/editor/lib/browser';
21
- import { LabelProvider, DISABLED_CLASS, TreeWidget } from '@theia/core/lib/browser';
20
+ import { CommandService } from '@theia/core/lib/common';
21
+ import { LabelProvider, DISABLED_CLASS, OpenerService, TreeWidget } from '@theia/core/lib/browser';
22
22
  import { TreeElement } from '@theia/core/lib/browser/source-tree';
23
23
  import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection';
24
- import { DebugSession } from '../debug-session';
25
24
  import { BaseBreakpoint } from '../breakpoint/breakpoint-marker';
26
25
  import { BreakpointManager } from '../breakpoint/breakpoint-manager';
27
26
  import { nls } from '@theia/core';
28
27
 
29
- export class DebugBreakpointData {
30
- readonly raw?: DebugProtocol.Breakpoint;
31
- }
32
-
33
- export class DebugBreakpointOptions {
28
+ export interface DebugBreakpointOptions {
34
29
  readonly labelProvider: LabelProvider;
35
30
  readonly breakpoints: BreakpointManager;
36
- readonly editorManager: EditorManager;
37
- readonly session?: DebugSession;
31
+ readonly openerService: OpenerService;
32
+ readonly commandService: CommandService;
38
33
  }
39
34
 
40
35
  export class DebugBreakpointDecoration {
@@ -42,27 +37,102 @@ export class DebugBreakpointDecoration {
42
37
  readonly message: string[];
43
38
  }
44
39
 
45
- export abstract class DebugBreakpoint<T extends BaseBreakpoint = BaseBreakpoint> extends DebugBreakpointOptions implements TreeElement {
40
+ export type BPCapabilities = Required<Pick<
41
+ DebugProtocol.Capabilities,
42
+ | 'supportsConditionalBreakpoints'
43
+ | 'supportsHitConditionalBreakpoints'
44
+ | 'supportsLogPoints'
45
+ | 'supportsFunctionBreakpoints'
46
+ | 'supportsDataBreakpoints'
47
+ | 'supportsInstructionBreakpoints'
48
+ >>;
49
+
50
+ export interface BPSessionData extends BPCapabilities, DebugProtocol.Breakpoint {
51
+ sessionId: string;
52
+ }
53
+
54
+ export abstract class DebugBreakpoint<T extends BaseBreakpoint = BaseBreakpoint> implements TreeElement {
46
55
 
47
- readonly raw?: DebugProtocol.Breakpoint;
56
+ readonly labelProvider: LabelProvider;
57
+ readonly breakpoints: BreakpointManager;
58
+ readonly openerService: OpenerService;
59
+ readonly commandService: CommandService;
60
+
61
+ protected _raw?: BPSessionData;
62
+ protected readonly sessionData = new Map<string, BPSessionData>();
48
63
  protected treeWidget?: TreeWidget;
49
64
 
50
65
  constructor(
51
66
  readonly uri: URI,
52
67
  options: DebugBreakpointOptions
53
68
  ) {
54
- super();
55
- Object.assign(this, options);
69
+ this.labelProvider = options.labelProvider;
70
+ this.breakpoints = options.breakpoints;
71
+ this.openerService = options.openerService;
72
+ this.commandService = options.commandService;
56
73
  }
57
74
 
58
75
  abstract get origin(): T;
59
76
 
60
- update(data: DebugBreakpointData): void {
61
- Object.assign(this, data);
77
+ get raw(): BPSessionData | undefined {
78
+ return this._raw;
62
79
  }
63
80
 
64
- get idFromAdapter(): number | undefined {
65
- return this.raw && this.raw.id;
81
+ update(sessionId: string, data?: Omit<BPSessionData, 'sessionId'>): boolean {
82
+ if (!data) {
83
+ if (!this.sessionData.has(sessionId)) {
84
+ return false;
85
+ }
86
+ this.sessionData.delete(sessionId);
87
+ } else {
88
+ const toSet = { ...data, sessionId };
89
+ this.sessionData.set(sessionId, toSet);
90
+ }
91
+ const verifiedLocations = new Map<string, BPSessionData>();
92
+ this.sessionData.forEach(bp => bp.verified && verifiedLocations.set(`${bp.line}:${bp.column}:${bp.instructionReference}`, bp));
93
+ if (verifiedLocations.size === 1) {
94
+ // Exactly one verified location across all sessions — use it.
95
+ this._raw = verifiedLocations.values().next().value;
96
+ } else if (verifiedLocations.size === 0) {
97
+ // No session has verified; pick the first session's data (if any) so
98
+ // that capability flags and unverified messages are still available.
99
+ this._raw = this.sessionData.values().next().value;
100
+ } else {
101
+ // Multiple sessions verified at different locations. Following
102
+ // VSCode, we set _raw to undefined so that the breakpoint falls
103
+ // back to its user-set position and shows as verified (the
104
+ // default when no resolved data exists). Callers that need a
105
+ // specific session's view can use getDebugProtocolBreakpoint().
106
+ this._raw = undefined;
107
+ }
108
+ return true;
109
+ }
110
+
111
+ getIdForSession(sessionId: string): number | undefined {
112
+ return this.sessionData.get(sessionId)?.id;
113
+ }
114
+
115
+ /** Copied from https://github.com/microsoft/vscode/blob/8934b59d4aa696b6f51ac9bf2eeae8bbac5dac03/src/vs/workbench/contrib/debug/common/debugModel.ts#L953-L971 */
116
+ getDebugProtocolBreakpoint(
117
+ sessionId: string,
118
+ ): DebugProtocol.Breakpoint | undefined {
119
+ const data = this.sessionData.get(sessionId);
120
+ if (data) {
121
+ const bp: DebugProtocol.Breakpoint = {
122
+ id: data.id,
123
+ verified: data.verified,
124
+ message: data.message,
125
+ source: data.source,
126
+ line: data.line,
127
+ column: data.column,
128
+ endLine: data.endLine,
129
+ endColumn: data.endColumn,
130
+ instructionReference: data.instructionReference,
131
+ offset: data.offset,
132
+ };
133
+ return bp;
134
+ }
135
+ return undefined;
66
136
  }
67
137
 
68
138
  get id(): string {
@@ -73,12 +143,23 @@ export abstract class DebugBreakpoint<T extends BaseBreakpoint = BaseBreakpoint>
73
143
  return this.breakpoints.breakpointsEnabled && this.origin.enabled;
74
144
  }
75
145
 
146
+ /**
147
+ * True when at least one session has sent data for this breakpoint
148
+ * (regardless of whether it was verified).
149
+ */
76
150
  get installed(): boolean {
77
- return !!this.raw;
151
+ return this.sessionData.size > 0;
78
152
  }
79
153
 
154
+ /**
155
+ * When resolved session data exists, reflects the adapter's answer.
156
+ * Otherwise returns `true`: either no session has weighed in yet (we
157
+ * haven't been told otherwise) or multiple sessions verified at
158
+ * different locations (`_raw` was cleared to fall back to the user-set
159
+ * position). Matches VSCode's default-true semantics.
160
+ */
80
161
  get verified(): boolean {
81
- return !!this.raw ? this.raw.verified : false;
162
+ return this._raw ? this._raw.verified : true;
82
163
  }
83
164
 
84
165
  get message(): string {
@@ -22,14 +22,17 @@ import { DataBreakpoint } from '../breakpoint/breakpoint-marker';
22
22
  import { DebugBreakpoint, DebugBreakpointDecoration, DebugBreakpointOptions } from './debug-breakpoint';
23
23
 
24
24
  export class DebugDataBreakpoint extends DebugBreakpoint<DataBreakpoint> {
25
+
26
+ static create(origin: DataBreakpoint, options: DebugBreakpointOptions): DebugDataBreakpoint {
27
+ return new this(origin, options);
28
+ }
29
+
25
30
  constructor(readonly origin: DataBreakpoint, options: DebugBreakpointOptions) {
26
31
  super(BreakpointManager.DATA_URI, options);
27
32
  }
28
33
 
29
34
  setEnabled(enabled: boolean): void {
30
- if (enabled !== this.origin.enabled) {
31
- this.breakpoints.updateDataBreakpoint(this.origin.id, { enabled });
32
- }
35
+ this.breakpoints.enableBreakpoint(this, enabled);
33
36
  }
34
37
 
35
38
  protected override isEnabled(): boolean {
@@ -37,11 +40,11 @@ export class DebugDataBreakpoint extends DebugBreakpoint<DataBreakpoint> {
37
40
  }
38
41
 
39
42
  protected isSupported(): boolean {
40
- return Boolean(this.session?.capabilities.supportsDataBreakpoints);
43
+ return this.raw ? !!this.raw.supportsDataBreakpoints : true;
41
44
  }
42
45
 
43
46
  remove(): void {
44
- this.breakpoints.removeDataBreakpoint(this.origin.id);
47
+ this.breakpoints.removeDataBreakpoint(this);
45
48
  }
46
49
 
47
50
  protected doRender(): React.ReactNode {
@@ -25,17 +25,16 @@ import { codicon } from '@theia/core/lib/browser';
25
25
 
26
26
  export class DebugFunctionBreakpoint extends DebugBreakpoint<FunctionBreakpoint> implements TreeElement {
27
27
 
28
- constructor(readonly origin: FunctionBreakpoint, options: DebugBreakpointOptions) {
28
+ static create(origin: FunctionBreakpoint, options: DebugBreakpointOptions): DebugFunctionBreakpoint {
29
+ return new this(origin, options);
30
+ }
31
+
32
+ private constructor(readonly origin: FunctionBreakpoint, options: DebugBreakpointOptions) {
29
33
  super(BreakpointManager.FUNCTION_URI, options);
30
34
  }
31
35
 
32
36
  setEnabled(enabled: boolean): void {
33
- const breakpoints = this.breakpoints.getFunctionBreakpoints();
34
- const breakpoint = breakpoints.find(b => b.id === this.id);
35
- if (breakpoint && breakpoint.enabled !== enabled) {
36
- breakpoint.enabled = enabled;
37
- this.breakpoints.setFunctionBreakpoints(breakpoints);
38
- }
37
+ this.breakpoints.enableBreakpoint(this, enabled);
39
38
  }
40
39
 
41
40
  protected override isEnabled(): boolean {
@@ -43,16 +42,11 @@ export class DebugFunctionBreakpoint extends DebugBreakpoint<FunctionBreakpoint>
43
42
  }
44
43
 
45
44
  protected isSupported(): boolean {
46
- const { session } = this;
47
- return !session || !!session.capabilities.supportsFunctionBreakpoints;
45
+ return this.raw ? !!this.raw.supportsFunctionBreakpoints : true;
48
46
  }
49
47
 
50
48
  remove(): void {
51
- const breakpoints = this.breakpoints.getFunctionBreakpoints();
52
- const newBreakpoints = breakpoints.filter(b => b.id !== this.id);
53
- if (breakpoints.length !== newBreakpoints.length) {
54
- this.breakpoints.setFunctionBreakpoints(newBreakpoints);
55
- }
49
+ this.breakpoints.removeFunctionBreakpoint(this);
56
50
  }
57
51
 
58
52
  get name(): string {
@@ -97,26 +91,21 @@ export class DebugFunctionBreakpoint extends DebugBreakpoint<FunctionBreakpoint>
97
91
  };
98
92
  }
99
93
 
100
- async open(): Promise<void> {
94
+ static async editOrCreate(breakpoints: BreakpointManager, existing?: DebugFunctionBreakpoint): Promise<void> {
101
95
  const input = new SingleTextInputDialog({
102
96
  title: nls.localizeByDefault('Add Function Breakpoint'),
103
- initialValue: this.name
97
+ initialValue: existing?.name ?? ''
104
98
  });
105
99
  const newValue = await input.open();
106
- if (newValue !== undefined && newValue !== this.name) {
107
- const breakpoints = this.breakpoints.getFunctionBreakpoints();
108
- const breakpoint = breakpoints.find(b => b.id === this.id);
109
- if (breakpoint) {
110
- if (breakpoint.raw.name !== newValue) {
111
- breakpoint.raw.name = newValue;
112
- this.breakpoints.setFunctionBreakpoints(breakpoints);
113
- }
114
- } else {
115
- this.origin.raw.name = newValue;
116
- breakpoints.push(this.origin);
117
- this.breakpoints.setFunctionBreakpoints(breakpoints);
118
- }
100
+ if (!newValue) { return; }
101
+ if (existing) {
102
+ breakpoints.updateFunctionBreakpoint(existing, { name: newValue });
103
+ } else {
104
+ breakpoints.addFunctionBreakpoint(FunctionBreakpoint.create({ name: newValue }));
119
105
  }
120
106
  }
121
107
 
108
+ async open(): Promise<void> {
109
+ DebugFunctionBreakpoint.editOrCreate(this.breakpoints, this);
110
+ }
122
111
  }
@@ -22,14 +22,16 @@ import { InstructionBreakpoint } from '../breakpoint/breakpoint-marker';
22
22
  import { DebugBreakpoint, DebugBreakpointDecoration, DebugBreakpointOptions } from './debug-breakpoint';
23
23
 
24
24
  export class DebugInstructionBreakpoint extends DebugBreakpoint<InstructionBreakpoint> {
25
- constructor(readonly origin: InstructionBreakpoint, options: DebugBreakpointOptions) {
25
+ static create(origin: InstructionBreakpoint, options: DebugBreakpointOptions): DebugInstructionBreakpoint {
26
+ return new this(origin, options);
27
+ }
28
+
29
+ private constructor(readonly origin: InstructionBreakpoint, options: DebugBreakpointOptions) {
26
30
  super(BreakpointManager.INSTRUCTION_URI, options);
27
31
  }
28
32
 
29
33
  setEnabled(enabled: boolean): void {
30
- if (enabled !== this.origin.enabled) {
31
- this.breakpoints.updateInstructionBreakpoint(this.origin.id, { enabled });
32
- }
34
+ this.breakpoints.enableBreakpoint(this, enabled);
33
35
  }
34
36
 
35
37
  protected override isEnabled(): boolean {
@@ -37,16 +39,16 @@ export class DebugInstructionBreakpoint extends DebugBreakpoint<InstructionBreak
37
39
  }
38
40
 
39
41
  protected isSupported(): boolean {
40
- return Boolean(this.session?.capabilities.supportsInstructionBreakpoints);
42
+ return this.raw ? !!this.raw.supportsInstructionBreakpoints : true;
41
43
  }
42
44
 
43
45
  remove(): void {
44
- this.breakpoints.removeInstructionBreakpoint(this.origin.instructionReference);
46
+ this.breakpoints.removeInstructionBreakpoint(this);
45
47
  }
46
48
 
47
49
  protected doRender(): React.ReactNode {
48
50
  return <React.Fragment>
49
- <span className="line-info">{this.origin.instructionReference}</span>;
51
+ <span className="line-info">{this.origin.raw.instructionReference}</span>
50
52
  {this.renderActions()}
51
53
  </React.Fragment>;
52
54
  }
@@ -69,7 +71,7 @@ export class DebugInstructionBreakpoint extends DebugBreakpoint<InstructionBreak
69
71
  message: message ?? [nls.localize('theia/debug/instruction-breakpoint', 'Instruction Breakpoint')],
70
72
  };
71
73
  }
72
- if (this.origin.condition || this.origin.hitCondition) {
74
+ if (this.origin.raw.condition || this.origin.raw.hitCondition) {
73
75
  return {
74
76
  className: 'codicon-debug-breakpoint-conditional',
75
77
  message: message || [nls.localize('theia/debug/conditionalBreakpoint', 'Conditional Breakpoint')]