Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/breakpointsView.ts
5237 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../base/browser/dom.js';
7
import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
8
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
9
import { AriaRole } from '../../../../base/browser/ui/aria/aria.js';
10
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
11
import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js';
12
import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
13
import { Checkbox, TriStateCheckbox } from '../../../../base/browser/ui/toggle/toggle.js';
14
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
15
import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';
16
import { Orientation } from '../../../../base/browser/ui/splitview/splitview.js';
17
import { ICompressedTreeElement, ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
18
import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
19
import { ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
20
import { Action } from '../../../../base/common/actions.js';
21
import { RunOnceScheduler } from '../../../../base/common/async.js';
22
import { Codicon } from '../../../../base/common/codicons.js';
23
import { MarkdownString } from '../../../../base/common/htmlContent.js';
24
import { KeyCode } from '../../../../base/common/keyCodes.js';
25
import { DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js';
26
import * as resources from '../../../../base/common/resources.js';
27
import { ThemeIcon } from '../../../../base/common/themables.js';
28
import { URI } from '../../../../base/common/uri.js';
29
import { Constants } from '../../../../base/common/uint.js';
30
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
31
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
32
import { ILanguageService } from '../../../../editor/common/languages/language.js';
33
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
34
import { localize, localize2 } from '../../../../nls.js';
35
import { getActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
36
import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
37
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
38
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
39
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
40
import { TextEditorSelectionRevealType } from '../../../../platform/editor/common/editor.js';
41
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
42
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
43
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
44
import { ILabelService } from '../../../../platform/label/common/label.js';
45
import { WorkbenchCompressibleObjectTree } from '../../../../platform/list/browser/listService.js';
46
import { INotificationService } from '../../../../platform/notification/common/notification.js';
47
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
48
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
49
import { defaultCheckboxStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';
50
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
51
import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';
52
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
53
import { IEditorPane } from '../../../common/editor.js';
54
import { IViewDescriptorService } from '../../../common/views.js';
55
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js';
56
import { IViewsService } from '../../../services/views/common/viewsService.js';
57
import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, DEBUG_SCHEME, DataBreakpointSetType, DataBreakpointSource, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDataBreakpointInfoResponse, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from '../common/debug.js';
58
import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from '../common/debugModel.js';
59
import { DisassemblyViewInput } from '../common/disassemblyViewInput.js';
60
import * as icons from './debugIcons.js';
61
import { DisassemblyView } from './disassemblyView.js';
62
import { equals } from '../../../../base/common/arrays.js';
63
import { hasKey } from '../../../../base/common/types.js';
64
65
const $ = dom.$;
66
67
function createCheckbox(disposables: DisposableStore): Checkbox {
68
const checkbox = new Checkbox('', false, defaultCheckboxStyles);
69
checkbox.domNode.tabIndex = -1;
70
disposables.add(checkbox);
71
72
return checkbox;
73
}
74
75
const MAX_VISIBLE_BREAKPOINTS = 9;
76
export function getExpandedBodySize(model: IDebugModel, sessionId: string | undefined, countLimit: number): number {
77
const length = model.getBreakpoints().length + model.getExceptionBreakpointsForSession(sessionId).length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length + model.getInstructionBreakpoints().length;
78
return Math.min(countLimit, length) * 22;
79
}
80
type BreakpointItem = IBreakpoint | IFunctionBreakpoint | IDataBreakpoint | IExceptionBreakpoint | IInstructionBreakpoint;
81
82
/**
83
* Represents a file node in the breakpoints tree that groups breakpoints by file.
84
*/
85
export class BreakpointsFolderItem {
86
constructor(
87
readonly uri: URI,
88
readonly breakpoints: IBreakpoint[]
89
) { }
90
91
getId(): string {
92
return this.uri.toString();
93
}
94
95
get enabled(): boolean {
96
return this.breakpoints.every(bp => bp.enabled);
97
}
98
99
get indeterminate(): boolean {
100
const enabledCount = this.breakpoints.filter(bp => bp.enabled).length;
101
return enabledCount > 0 && enabledCount < this.breakpoints.length;
102
}
103
}
104
105
type BreakpointTreeElement = BreakpointsFolderItem | BreakpointItem;
106
107
interface InputBoxData {
108
breakpoint: IFunctionBreakpoint | IExceptionBreakpoint | IDataBreakpoint;
109
type: 'condition' | 'hitCount' | 'name';
110
}
111
112
function getModeKindForBreakpoint(breakpoint: IBreakpoint) {
113
const kind = breakpoint instanceof Breakpoint ? 'source' : breakpoint instanceof InstructionBreakpoint ? 'instruction' : 'exception';
114
return kind;
115
}
116
117
export class BreakpointsView extends ViewPane {
118
119
private tree!: WorkbenchCompressibleObjectTree<BreakpointTreeElement, void>;
120
private needsRefresh = false;
121
private needsStateChange = false;
122
private ignoreLayout = false;
123
private menu: IMenu;
124
private breakpointItemType: IContextKey<string | undefined>;
125
private breakpointIsDataBytes: IContextKey<boolean | undefined>;
126
private breakpointHasMultipleModes: IContextKey<boolean>;
127
private breakpointSupportsCondition: IContextKey<boolean>;
128
private _inputBoxData: InputBoxData | undefined;
129
breakpointInputFocused: IContextKey<boolean>;
130
private autoFocusedElement: BreakpointItem | undefined;
131
private collapsedState = new Set<string>();
132
133
private hintContainer: IconLabel | undefined;
134
private hintDelayer: RunOnceScheduler;
135
136
private getPresentation(): 'tree' | 'list' {
137
return this.configurationService.getValue<'tree' | 'list'>('debug.breakpointsView.presentation');
138
}
139
140
constructor(
141
options: IViewletViewOptions,
142
@IContextMenuService contextMenuService: IContextMenuService,
143
@IDebugService private readonly debugService: IDebugService,
144
@IKeybindingService keybindingService: IKeybindingService,
145
@IInstantiationService instantiationService: IInstantiationService,
146
@IThemeService themeService: IThemeService,
147
@IEditorService private readonly editorService: IEditorService,
148
@IContextViewService private readonly contextViewService: IContextViewService,
149
@IConfigurationService configurationService: IConfigurationService,
150
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
151
@IContextKeyService contextKeyService: IContextKeyService,
152
@IOpenerService openerService: IOpenerService,
153
@ILabelService private readonly labelService: ILabelService,
154
@IMenuService menuService: IMenuService,
155
@IHoverService hoverService: IHoverService,
156
@ILanguageService private readonly languageService: ILanguageService,
157
) {
158
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
159
160
this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService);
161
this._register(this.menu);
162
this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService);
163
this.breakpointIsDataBytes = CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES.bindTo(contextKeyService);
164
this.breakpointHasMultipleModes = CONTEXT_BREAKPOINT_HAS_MODES.bindTo(contextKeyService);
165
this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService);
166
this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService);
167
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
168
this._register(this.debugService.getViewModel().onDidFocusSession(() => this.onBreakpointsChange()));
169
this._register(this.debugService.onDidChangeState(() => this.onStateChange()));
170
this.hintDelayer = this._register(new RunOnceScheduler(() => this.updateBreakpointsHint(true), 4000));
171
}
172
173
protected override renderBody(container: HTMLElement): void {
174
super.renderBody(container);
175
176
this.element.classList.add('debug-pane');
177
container.classList.add('debug-breakpoints');
178
179
this.tree = this.instantiationService.createInstance(
180
WorkbenchCompressibleObjectTree<BreakpointTreeElement, void>,
181
'BreakpointsView',
182
container,
183
new BreakpointsDelegate(this),
184
[
185
this.instantiationService.createInstance(BreakpointsFolderRenderer),
186
this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType),
187
new ExceptionBreakpointsRenderer(this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService, this.hoverService),
188
new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService),
189
this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType),
190
new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.hoverService, this.labelService),
191
this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.breakpointIsDataBytes),
192
new DataBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.hoverService, this.labelService),
193
this.instantiationService.createInstance(InstructionBreakpointsRenderer),
194
],
195
{
196
compressionEnabled: this.getPresentation() === 'tree',
197
hideTwistiesOfChildlessElements: true,
198
identityProvider: {
199
getId: (element: BreakpointTreeElement) => element.getId()
200
},
201
keyboardNavigationLabelProvider: {
202
getKeyboardNavigationLabel: (element: BreakpointTreeElement) => {
203
if (element instanceof BreakpointsFolderItem) {
204
return resources.basenameOrAuthority(element.uri);
205
}
206
if (element instanceof Breakpoint) {
207
return `${resources.basenameOrAuthority(element.uri)}:${element.lineNumber}`;
208
}
209
if (element instanceof FunctionBreakpoint) {
210
return element.name;
211
}
212
if (element instanceof DataBreakpoint) {
213
return element.description;
214
}
215
if (element instanceof ExceptionBreakpoint) {
216
return element.label || element.filter;
217
}
218
if (element instanceof InstructionBreakpoint) {
219
return `0x${element.address.toString(16)}`;
220
}
221
return '';
222
},
223
getCompressedNodeKeyboardNavigationLabel: (elements: BreakpointTreeElement[]) => {
224
return elements.map(e => {
225
if (e instanceof BreakpointsFolderItem) {
226
return resources.basenameOrAuthority(e.uri);
227
}
228
return '';
229
}).join('/');
230
}
231
},
232
accessibilityProvider: new BreakpointsAccessibilityProvider(this.debugService, this.labelService),
233
multipleSelectionSupport: false,
234
overrideStyles: this.getLocationBasedColors().listOverrideStyles
235
}
236
);
237
this._register(this.tree);
238
239
CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.tree.contextKeyService);
240
241
this._register(this.tree.onContextMenu(this.onTreeContextMenu, this));
242
243
this._register(this.tree.onMouseMiddleClick(async ({ element }) => {
244
if (element instanceof Breakpoint) {
245
await this.debugService.removeBreakpoints(element.getId());
246
} else if (element instanceof FunctionBreakpoint) {
247
await this.debugService.removeFunctionBreakpoints(element.getId());
248
} else if (element instanceof DataBreakpoint) {
249
await this.debugService.removeDataBreakpoints(element.getId());
250
} else if (element instanceof InstructionBreakpoint) {
251
await this.debugService.removeInstructionBreakpoints(element.instructionReference, element.offset);
252
} else if (element instanceof BreakpointsFolderItem) {
253
await this.debugService.removeBreakpoints(element.breakpoints.map(bp => bp.getId()));
254
}
255
}));
256
257
this._register(this.tree.onDidOpen(async e => {
258
const element = e.element;
259
if (!element) {
260
return;
261
}
262
263
if (dom.isMouseEvent(e.browserEvent) && e.browserEvent.button === 1) { // middle click
264
return;
265
}
266
267
if (element instanceof Breakpoint) {
268
openBreakpointSource(element, e.sideBySide, e.editorOptions.preserveFocus || false, e.editorOptions.pinned || !e.editorOptions.preserveFocus, this.debugService, this.editorService);
269
}
270
if (element instanceof InstructionBreakpoint) {
271
const disassemblyView = await this.editorService.openEditor(DisassemblyViewInput.instance);
272
// Focus on double click
273
(disassemblyView as DisassemblyView).goToInstructionAndOffset(element.instructionReference, element.offset, dom.isMouseEvent(e.browserEvent) && e.browserEvent.detail === 2);
274
}
275
if (dom.isMouseEvent(e.browserEvent) && e.browserEvent.detail === 2 && element instanceof FunctionBreakpoint && element !== this.inputBoxData?.breakpoint) {
276
// double click
277
this.renderInputBox({ breakpoint: element, type: 'name' });
278
}
279
}));
280
281
// Track collapsed state and update size (items are expanded by default)
282
this._register(this.tree.onDidChangeCollapseState(e => {
283
const element = e.node.element;
284
if (element instanceof BreakpointsFolderItem) {
285
if (e.node.collapsed) {
286
this.collapsedState.add(element.getId());
287
} else {
288
this.collapsedState.delete(element.getId());
289
}
290
this.updateSize();
291
}
292
}));
293
294
// React to configuration changes
295
this._register(this.configurationService.onDidChangeConfiguration(e => {
296
if (e.affectsConfiguration('debug.breakpointsView.presentation')) {
297
const presentation = this.getPresentation();
298
this.tree.updateOptions({ compressionEnabled: presentation === 'tree' });
299
this.onBreakpointsChange();
300
}
301
}));
302
303
this.setTreeInput();
304
305
this._register(this.onDidChangeBodyVisibility(visible => {
306
if (visible) {
307
if (this.needsRefresh) {
308
this.onBreakpointsChange();
309
}
310
311
if (this.needsStateChange) {
312
this.onStateChange();
313
}
314
}
315
}));
316
317
const containerModel = this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!);
318
this._register(containerModel.onDidChangeAllViewDescriptors(() => {
319
this.updateSize();
320
}));
321
}
322
323
protected override renderHeaderTitle(container: HTMLElement, title: string): void {
324
super.renderHeaderTitle(container, title);
325
326
const iconLabelContainer = dom.append(container, $('span.breakpoint-warning'));
327
this.hintContainer = this._register(new IconLabel(iconLabelContainer, {
328
supportIcons: true, hoverDelegate: {
329
showHover: (options, focus?) => this.hoverService.showInstantHover({ content: options.content, target: this.hintContainer!.element }, focus),
330
delay: this.configurationService.getValue<number>('workbench.hover.delay')
331
}
332
}));
333
dom.hide(this.hintContainer.element);
334
}
335
336
override focus(): void {
337
super.focus();
338
this.tree?.domFocus();
339
}
340
341
renderInputBox(data: InputBoxData | undefined): void {
342
this._inputBoxData = data;
343
this.onBreakpointsChange();
344
this._inputBoxData = undefined;
345
}
346
347
get inputBoxData(): InputBoxData | undefined {
348
return this._inputBoxData;
349
}
350
351
protected override layoutBody(height: number, width: number): void {
352
if (this.ignoreLayout) {
353
return;
354
}
355
356
super.layoutBody(height, width);
357
this.tree?.layout(height, width);
358
try {
359
this.ignoreLayout = true;
360
this.updateSize();
361
} finally {
362
this.ignoreLayout = false;
363
}
364
}
365
366
private onTreeContextMenu(e: ITreeContextMenuEvent<BreakpointTreeElement | null>): void {
367
const element = e.element;
368
if (element instanceof BreakpointsFolderItem) {
369
// For folder items, show file-level context menu
370
this.breakpointItemType.set('breakpointFolder');
371
const { secondary } = getContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: false }), 'inline');
372
this.contextMenuService.showContextMenu({
373
getAnchor: () => e.anchor,
374
getActions: () => secondary,
375
getActionsContext: () => element
376
});
377
return;
378
}
379
380
const type = element instanceof Breakpoint ? 'breakpoint' : element instanceof ExceptionBreakpoint ? 'exceptionBreakpoint' :
381
element instanceof FunctionBreakpoint ? 'functionBreakpoint' : element instanceof DataBreakpoint ? 'dataBreakpoint' :
382
element instanceof InstructionBreakpoint ? 'instructionBreakpoint' : undefined;
383
this.breakpointItemType.set(type);
384
const session = this.debugService.getViewModel().focusedSession;
385
const conditionSupported = element instanceof ExceptionBreakpoint ? element.supportsCondition : (!session || !!session.capabilities.supportsConditionalBreakpoints);
386
this.breakpointSupportsCondition.set(conditionSupported);
387
this.breakpointIsDataBytes.set(element instanceof DataBreakpoint && element.src.type === DataBreakpointSetType.Address);
388
this.breakpointHasMultipleModes.set(this.debugService.getModel().getBreakpointModes(getModeKindForBreakpoint(element as IBreakpoint)).length > 1);
389
390
const { secondary } = getContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false }), 'inline');
391
392
this.contextMenuService.showContextMenu({
393
getAnchor: () => e.anchor,
394
getActions: () => secondary,
395
getActionsContext: () => element
396
});
397
}
398
399
private updateSize(): void {
400
const containerModel = this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!);
401
402
// Calculate visible row count from tree's content height
403
// Each row is 22px high
404
const rowHeight = 22;
405
406
this.minimumBodySize = this.orientation === Orientation.VERTICAL ? Math.min(MAX_VISIBLE_BREAKPOINTS * rowHeight, this.tree.contentHeight) : 170;
407
this.maximumBodySize = this.orientation === Orientation.VERTICAL && containerModel.visibleViewDescriptors.length > 1 ? this.tree.contentHeight : Number.POSITIVE_INFINITY;
408
}
409
410
private updateBreakpointsHint(delayed = false): void {
411
if (!this.hintContainer) {
412
return;
413
}
414
415
const currentType = this.debugService.getViewModel().focusedSession?.configuration.type;
416
const dbg = currentType ? this.debugService.getAdapterManager().getDebugger(currentType) : undefined;
417
const message = dbg?.strings?.[DebuggerString.UnverifiedBreakpoints];
418
const debuggerHasUnverifiedBps = message && this.debugService.getModel().getBreakpoints().filter(bp => {
419
if (bp.verified || !bp.enabled) {
420
return false;
421
}
422
423
const langId = this.languageService.guessLanguageIdByFilepathOrFirstLine(bp.uri);
424
return langId && dbg.interestedInLanguage(langId);
425
});
426
427
if (message && debuggerHasUnverifiedBps?.length && this.debugService.getModel().areBreakpointsActivated()) {
428
if (delayed) {
429
const mdown = new MarkdownString(undefined, { isTrusted: true }).appendMarkdown(message);
430
this.hintContainer.setLabel('$(warning)', undefined, { title: { markdown: mdown, markdownNotSupportedFallback: message } });
431
dom.show(this.hintContainer.element);
432
} else {
433
this.hintDelayer.schedule();
434
}
435
} else {
436
dom.hide(this.hintContainer.element);
437
}
438
}
439
440
private onBreakpointsChange(): void {
441
if (this.isBodyVisible()) {
442
if (this.tree) {
443
this.setTreeInput();
444
this.needsRefresh = false;
445
}
446
this.updateBreakpointsHint();
447
this.updateSize();
448
} else {
449
this.needsRefresh = true;
450
}
451
}
452
453
private onStateChange(): void {
454
if (this.isBodyVisible()) {
455
this.needsStateChange = false;
456
const thread = this.debugService.getViewModel().focusedThread;
457
let found = false;
458
if (thread && thread.stoppedDetails && thread.stoppedDetails.hitBreakpointIds && thread.stoppedDetails.hitBreakpointIds.length > 0) {
459
const hitBreakpointIds = thread.stoppedDetails.hitBreakpointIds;
460
const elements = this.flatElements;
461
const hitElement = elements.find(e => {
462
const id = e.getIdFromAdapter(thread.session.getId());
463
return typeof id === 'number' && hitBreakpointIds.indexOf(id) !== -1;
464
});
465
if (hitElement) {
466
this.tree.setFocus([hitElement]);
467
this.tree.setSelection([hitElement]);
468
found = true;
469
this.autoFocusedElement = hitElement;
470
}
471
}
472
if (!found) {
473
// Deselect breakpoint in breakpoint view when no longer stopped on it #125528
474
const focus = this.tree.getFocus();
475
const selection = this.tree.getSelection();
476
if (this.autoFocusedElement && equals(focus, selection) && selection.includes(this.autoFocusedElement)) {
477
this.tree.setFocus([]);
478
this.tree.setSelection([]);
479
}
480
this.autoFocusedElement = undefined;
481
}
482
this.updateBreakpointsHint();
483
} else {
484
this.needsStateChange = true;
485
}
486
}
487
488
private setTreeInput(): void {
489
const treeInput = this.getTreeElements();
490
this.tree.setChildren(null, treeInput);
491
}
492
493
private getTreeElements(): ICompressedTreeElement<BreakpointTreeElement>[] {
494
const model = this.debugService.getModel();
495
const sessionId = this.debugService.getViewModel().focusedSession?.getId();
496
const showAsTree = this.getPresentation() === 'tree';
497
498
const result: ICompressedTreeElement<BreakpointTreeElement>[] = [];
499
500
// Exception breakpoints at the top (root level)
501
for (const exBp of model.getExceptionBreakpointsForSession(sessionId)) {
502
result.push({ element: exBp, incompressible: true });
503
}
504
505
// Function breakpoints (root level)
506
for (const funcBp of model.getFunctionBreakpoints()) {
507
result.push({ element: funcBp, incompressible: true });
508
}
509
510
// Data breakpoints (root level)
511
for (const dataBp of model.getDataBreakpoints()) {
512
result.push({ element: dataBp, incompressible: true });
513
}
514
515
// Source breakpoints - group by file if showAsTree is enabled
516
const sourceBreakpoints = model.getBreakpoints();
517
if (showAsTree && sourceBreakpoints.length > 0) {
518
// Group breakpoints by URI
519
const breakpointsByUri = new Map<string, IBreakpoint[]>();
520
for (const bp of sourceBreakpoints) {
521
const key = bp.uri.toString();
522
if (!breakpointsByUri.has(key)) {
523
breakpointsByUri.set(key, []);
524
}
525
breakpointsByUri.get(key)!.push(bp);
526
}
527
528
// Create folder items for each file
529
for (const [uriStr, breakpoints] of breakpointsByUri) {
530
const uri = URI.parse(uriStr);
531
const folderItem = new BreakpointsFolderItem(uri, breakpoints);
532
533
// Sort breakpoints by line number
534
breakpoints.sort((a, b) => a.lineNumber - b.lineNumber);
535
536
const children: ICompressedTreeElement<BreakpointTreeElement>[] = breakpoints.map(bp => ({
537
element: bp,
538
incompressible: false
539
}));
540
541
result.push({
542
element: folderItem,
543
incompressible: false,
544
collapsed: this.collapsedState.has(folderItem.getId()),
545
children
546
});
547
}
548
} else {
549
// Flat mode - just add all source breakpoints
550
for (const bp of sourceBreakpoints) {
551
result.push({ element: bp, incompressible: true });
552
}
553
}
554
555
// Instruction breakpoints (root level)
556
for (const instrBp of model.getInstructionBreakpoints()) {
557
result.push({ element: instrBp, incompressible: true });
558
}
559
560
return result;
561
}
562
563
private get flatElements(): BreakpointItem[] {
564
const model = this.debugService.getModel();
565
const sessionId = this.debugService.getViewModel().focusedSession?.getId();
566
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpointsForSession(sessionId)).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()).concat(model.getInstructionBreakpoints());
567
568
return elements as BreakpointItem[];
569
}
570
}
571
572
class BreakpointsDelegate implements IListVirtualDelegate<BreakpointTreeElement> {
573
574
constructor(private view: BreakpointsView) {
575
// noop
576
}
577
578
getHeight(_element: BreakpointTreeElement): number {
579
return 22;
580
}
581
582
getTemplateId(element: BreakpointTreeElement): string {
583
if (element instanceof BreakpointsFolderItem) {
584
return BreakpointsFolderRenderer.ID;
585
}
586
if (element instanceof Breakpoint) {
587
return BreakpointsRenderer.ID;
588
}
589
if (element instanceof FunctionBreakpoint) {
590
const inputBoxBreakpoint = this.view.inputBoxData?.breakpoint;
591
if (!element.name || (inputBoxBreakpoint && inputBoxBreakpoint.getId() === element.getId())) {
592
return FunctionBreakpointInputRenderer.ID;
593
}
594
595
return FunctionBreakpointsRenderer.ID;
596
}
597
if (element instanceof ExceptionBreakpoint) {
598
const inputBoxBreakpoint = this.view.inputBoxData?.breakpoint;
599
if (inputBoxBreakpoint && inputBoxBreakpoint.getId() === element.getId()) {
600
return ExceptionBreakpointInputRenderer.ID;
601
}
602
return ExceptionBreakpointsRenderer.ID;
603
}
604
if (element instanceof DataBreakpoint) {
605
const inputBoxBreakpoint = this.view.inputBoxData?.breakpoint;
606
if (inputBoxBreakpoint && inputBoxBreakpoint.getId() === element.getId()) {
607
return DataBreakpointInputRenderer.ID;
608
}
609
610
return DataBreakpointsRenderer.ID;
611
}
612
if (element instanceof InstructionBreakpoint) {
613
return InstructionBreakpointsRenderer.ID;
614
}
615
616
return '';
617
}
618
}
619
620
interface IBaseBreakpointTemplateData {
621
breakpoint: HTMLElement;
622
name: HTMLElement;
623
checkbox: Checkbox;
624
context: BreakpointItem;
625
actionBar: ActionBar;
626
templateDisposables: DisposableStore;
627
elementDisposables: DisposableStore;
628
badge: HTMLElement;
629
}
630
631
interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData {
632
icon: HTMLElement;
633
}
634
635
interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
636
filePath: HTMLElement;
637
}
638
639
interface IExceptionBreakpointTemplateData extends IBaseBreakpointTemplateData {
640
condition: HTMLElement;
641
}
642
643
interface IFunctionBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
644
condition: HTMLElement;
645
}
646
647
interface IDataBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
648
accessType: HTMLElement;
649
condition: HTMLElement;
650
}
651
652
interface IInstructionBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
653
address: HTMLElement;
654
}
655
656
interface IFunctionBreakpointInputTemplateData {
657
inputBox: InputBox;
658
checkbox: Checkbox;
659
icon: HTMLElement;
660
breakpoint: IFunctionBreakpoint;
661
templateDisposables: DisposableStore;
662
elementDisposables: DisposableStore;
663
type: 'hitCount' | 'condition' | 'name';
664
updating?: boolean;
665
}
666
667
interface IDataBreakpointInputTemplateData {
668
inputBox: InputBox;
669
checkbox: Checkbox;
670
icon: HTMLElement;
671
breakpoint: IDataBreakpoint;
672
elementDisposables: DisposableStore;
673
templateDisposables: DisposableStore;
674
type: 'hitCount' | 'condition' | 'name';
675
updating?: boolean;
676
}
677
678
interface IExceptionBreakpointInputTemplateData {
679
inputBox: InputBox;
680
checkbox: Checkbox;
681
currentBreakpoint?: IExceptionBreakpoint;
682
templateDisposables: DisposableStore;
683
elementDisposables: DisposableStore;
684
}
685
686
interface IBreakpointsFolderTemplateData {
687
container: HTMLElement;
688
checkbox: TriStateCheckbox;
689
name: HTMLElement;
690
actionBar: ActionBar;
691
context: BreakpointsFolderItem;
692
templateDisposables: DisposableStore;
693
elementDisposables: DisposableStore;
694
}
695
696
const breakpointIdToActionBarDomeNode = new Map<string, HTMLElement>();
697
698
class BreakpointsFolderRenderer implements ICompressibleTreeRenderer<BreakpointsFolderItem, void, IBreakpointsFolderTemplateData> {
699
700
static readonly ID = 'breakpointFolder';
701
702
constructor(
703
@IDebugService private readonly debugService: IDebugService,
704
@ILabelService private readonly labelService: ILabelService,
705
@IHoverService private readonly hoverService: IHoverService,
706
) { }
707
708
get templateId() {
709
return BreakpointsFolderRenderer.ID;
710
}
711
712
renderTemplate(container: HTMLElement): IBreakpointsFolderTemplateData {
713
const data: IBreakpointsFolderTemplateData = Object.create(null);
714
data.elementDisposables = new DisposableStore();
715
data.templateDisposables = new DisposableStore();
716
data.templateDisposables.add(data.elementDisposables);
717
718
data.container = container;
719
container.classList.add('breakpoint', 'breakpoint-folder');
720
721
data.templateDisposables.add(toDisposable(() => {
722
container.classList.remove('breakpoint', 'breakpoint-folder');
723
}));
724
725
data.checkbox = new TriStateCheckbox('', false, defaultCheckboxStyles);
726
data.checkbox.domNode.tabIndex = -1;
727
data.templateDisposables.add(data.checkbox);
728
data.templateDisposables.add(data.checkbox.onChange(() => {
729
const checked = data.checkbox.checked;
730
const enabled = checked === 'mixed' ? true : checked;
731
for (const bp of data.context.breakpoints) {
732
this.debugService.enableOrDisableBreakpoints(enabled, bp);
733
}
734
}));
735
736
dom.append(data.container, data.checkbox.domNode);
737
data.name = dom.append(data.container, $('span.name'));
738
dom.append(data.container, $('span.file-path'));
739
740
data.actionBar = new ActionBar(data.container);
741
data.templateDisposables.add(data.actionBar);
742
743
return data;
744
}
745
746
renderElement(node: ITreeNode<BreakpointsFolderItem, void>, _index: number, data: IBreakpointsFolderTemplateData): void {
747
const folderItem = node.element;
748
data.context = folderItem;
749
750
data.name.textContent = this.labelService.getUriBasenameLabel(folderItem.uri);
751
data.container.classList.toggle('disabled', !this.debugService.getModel().areBreakpointsActivated());
752
753
const fullPath = this.labelService.getUriLabel(folderItem.uri, { relative: true });
754
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.container, fullPath));
755
756
// Set checkbox state
757
if (folderItem.indeterminate) {
758
data.checkbox.checked = 'mixed';
759
} else {
760
data.checkbox.checked = folderItem.enabled;
761
}
762
763
// Add remove action
764
data.actionBar.clear();
765
const removeAction = data.elementDisposables.add(new Action(
766
'debug.removeBreakpointsInFile',
767
localize('removeBreakpointsInFile', "Remove Breakpoints in File"),
768
ThemeIcon.asClassName(Codicon.close),
769
true,
770
async () => {
771
for (const bp of folderItem.breakpoints) {
772
await this.debugService.removeBreakpoints(bp.getId());
773
}
774
}
775
));
776
data.actionBar.push(removeAction, { icon: true, label: false });
777
}
778
779
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<BreakpointsFolderItem>, void>, _index: number, data: IBreakpointsFolderTemplateData): void {
780
const elements = node.element.elements;
781
const folderItem = elements[elements.length - 1];
782
data.context = folderItem;
783
784
// For compressed nodes, show the combined path
785
const names = elements.map(e => resources.basenameOrAuthority(e.uri));
786
data.name.textContent = names.join('/');
787
788
const fullPath = this.labelService.getUriLabel(folderItem.uri, { relative: true });
789
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.container, fullPath));
790
791
// Set checkbox state
792
if (folderItem.indeterminate) {
793
data.checkbox.checked = 'mixed';
794
} else {
795
data.checkbox.checked = folderItem.enabled;
796
}
797
798
// Add remove action
799
data.actionBar.clear();
800
const removeAction = data.elementDisposables.add(new Action(
801
'debug.removeBreakpointsInFile',
802
localize('removeBreakpointsInFile', "Remove Breakpoints in File"),
803
ThemeIcon.asClassName(Codicon.close),
804
true,
805
async () => {
806
for (const bp of folderItem.breakpoints) {
807
await this.debugService.removeBreakpoints(bp.getId());
808
}
809
}
810
));
811
data.actionBar.push(removeAction, { icon: true, label: false });
812
}
813
814
disposeElement(element: ITreeNode<BreakpointsFolderItem, void>, index: number, templateData: IBreakpointsFolderTemplateData): void {
815
templateData.elementDisposables.clear();
816
}
817
818
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<BreakpointsFolderItem>, void>, index: number, templateData: IBreakpointsFolderTemplateData): void {
819
templateData.elementDisposables.clear();
820
}
821
822
disposeTemplate(templateData: IBreakpointsFolderTemplateData): void {
823
templateData.templateDisposables.dispose();
824
}
825
}
826
827
class BreakpointsRenderer implements ICompressibleTreeRenderer<IBreakpoint, void, IBreakpointTemplateData> {
828
829
constructor(
830
private menu: IMenu,
831
private breakpointHasMultipleModes: IContextKey<boolean>,
832
private breakpointSupportsCondition: IContextKey<boolean>,
833
private breakpointItemType: IContextKey<string | undefined>,
834
@IDebugService private readonly debugService: IDebugService,
835
@IHoverService private readonly hoverService: IHoverService,
836
@ILabelService private readonly labelService: ILabelService,
837
@ITextModelService private readonly textModelService: ITextModelService
838
) {
839
// noop
840
}
841
842
static readonly ID = 'breakpoints';
843
844
get templateId() {
845
return BreakpointsRenderer.ID;
846
}
847
848
renderTemplate(container: HTMLElement): IBreakpointTemplateData {
849
const data: IBreakpointTemplateData = Object.create(null);
850
data.elementDisposables = new DisposableStore();
851
data.templateDisposables = new DisposableStore();
852
data.templateDisposables.add(data.elementDisposables);
853
854
data.breakpoint = container;
855
container.classList.add('breakpoint');
856
857
data.templateDisposables.add(toDisposable(() => {
858
container.classList.remove('breakpoint');
859
}));
860
861
data.icon = $('.icon');
862
data.checkbox = createCheckbox(data.templateDisposables);
863
864
data.templateDisposables.add(data.checkbox.onChange(() => {
865
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
866
}));
867
868
dom.append(data.breakpoint, data.icon);
869
dom.append(data.breakpoint, data.checkbox.domNode);
870
871
data.name = dom.append(data.breakpoint, $('span.name'));
872
873
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
874
data.actionBar = new ActionBar(data.breakpoint);
875
data.templateDisposables.add(data.actionBar);
876
const badgeContainer = dom.append(data.breakpoint, $('.badge-container'));
877
data.badge = dom.append(badgeContainer, $('span.line-number.monaco-count-badge'));
878
879
return data;
880
}
881
882
renderElement(node: ITreeNode<IBreakpoint, void>, index: number, data: IBreakpointTemplateData): void {
883
const breakpoint = node.element;
884
data.context = breakpoint;
885
886
if (node.depth > 1) {
887
this.renderBreakpointLineLabel(breakpoint, data);
888
} else {
889
this.renderBreakpointFileLabel(breakpoint, data);
890
}
891
892
this.renderBreakpointCommon(breakpoint, data);
893
}
894
895
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IBreakpoint>, void>, index: number, data: IBreakpointTemplateData): void {
896
const breakpoint = node.element.elements[node.element.elements.length - 1];
897
data.context = breakpoint;
898
this.renderBreakpointFileLabel(breakpoint, data);
899
this.renderBreakpointCommon(breakpoint, data);
900
}
901
902
private renderBreakpointCommon(breakpoint: IBreakpoint, data: IBreakpointTemplateData): void {
903
data.breakpoint.classList.toggle('disabled', !this.debugService.getModel().areBreakpointsActivated());
904
let badgeContent = breakpoint.lineNumber.toString();
905
if (breakpoint.column) {
906
badgeContent += `:${breakpoint.column}`;
907
}
908
if (breakpoint.modeLabel) {
909
badgeContent = `${breakpoint.modeLabel}: ${badgeContent}`;
910
}
911
data.badge.textContent = badgeContent;
912
data.checkbox.checked = breakpoint.enabled;
913
914
const { message, icon } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService, this.debugService.getModel());
915
data.icon.className = ThemeIcon.asClassName(icon);
916
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, breakpoint.message || message || ''));
917
918
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
919
if (debugActive && !breakpoint.verified) {
920
data.breakpoint.classList.add('disabled');
921
}
922
923
const session = this.debugService.getViewModel().focusedSession;
924
this.breakpointSupportsCondition.set(!session || !!session.capabilities.supportsConditionalBreakpoints);
925
this.breakpointItemType.set('breakpoint');
926
this.breakpointHasMultipleModes.set(this.debugService.getModel().getBreakpointModes('source').length > 1);
927
const { primary } = getActionBarActions(this.menu.getActions({ arg: breakpoint, shouldForwardArgs: true }), 'inline');
928
data.actionBar.clear();
929
data.actionBar.push(primary, { icon: true, label: false });
930
breakpointIdToActionBarDomeNode.set(breakpoint.getId(), data.actionBar.domNode);
931
}
932
933
private renderBreakpointFileLabel(breakpoint: IBreakpoint, data: IBreakpointTemplateData): void {
934
data.name.textContent = resources.basenameOrAuthority(breakpoint.uri);
935
data.filePath.textContent = this.labelService.getUriLabel(resources.dirname(breakpoint.uri), { relative: true });
936
}
937
938
private renderBreakpointLineLabel(breakpoint: IBreakpoint, data: IBreakpointTemplateData): void {
939
data.name.textContent = localize('loading', "Loading...");
940
data.filePath.textContent = '';
941
942
this.textModelService.createModelReference(breakpoint.uri).then(reference => {
943
if (data.context !== breakpoint) {
944
reference.dispose();
945
return;
946
}
947
data.elementDisposables.add(reference);
948
const model = reference.object.textEditorModel;
949
if (model && breakpoint.lineNumber <= model.getLineCount()) {
950
const lineContent = model.getLineContent(breakpoint.lineNumber).trim();
951
data.name.textContent = lineContent || localize('emptyLine', "(empty line)");
952
} else {
953
data.name.textContent = localize('lineNotFound', "(line not found)");
954
}
955
}).catch(() => {
956
if (data.context === breakpoint) {
957
data.name.textContent = localize('cannotLoadLine', "(cannot load line)");
958
}
959
});
960
}
961
962
disposeElement(node: ITreeNode<IBreakpoint, void>, index: number, template: IBreakpointTemplateData): void {
963
template.elementDisposables.clear();
964
}
965
966
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IBreakpoint>, void>, index: number, template: IBreakpointTemplateData): void {
967
template.elementDisposables.clear();
968
}
969
970
disposeTemplate(templateData: IBreakpointTemplateData): void {
971
templateData.templateDisposables.dispose();
972
}
973
}
974
975
class ExceptionBreakpointsRenderer implements ICompressibleTreeRenderer<IExceptionBreakpoint, void, IExceptionBreakpointTemplateData> {
976
977
constructor(
978
private menu: IMenu,
979
private breakpointHasMultipleModes: IContextKey<boolean>,
980
private breakpointSupportsCondition: IContextKey<boolean>,
981
private breakpointItemType: IContextKey<string | undefined>,
982
private debugService: IDebugService,
983
private readonly hoverService: IHoverService,
984
) {
985
// noop
986
}
987
988
static readonly ID = 'exceptionbreakpoints';
989
990
get templateId() {
991
return ExceptionBreakpointsRenderer.ID;
992
}
993
994
renderTemplate(container: HTMLElement): IExceptionBreakpointTemplateData {
995
const data: IExceptionBreakpointTemplateData = Object.create(null);
996
data.elementDisposables = new DisposableStore();
997
data.templateDisposables = new DisposableStore();
998
data.templateDisposables.add(data.elementDisposables);
999
data.breakpoint = dom.append(container, $('.breakpoint'));
1000
1001
data.checkbox = createCheckbox(data.templateDisposables);
1002
data.templateDisposables.add(data.checkbox.onChange(() => {
1003
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
1004
}));
1005
1006
dom.append(data.breakpoint, data.checkbox.domNode);
1007
1008
data.name = dom.append(data.breakpoint, $('span.name'));
1009
data.condition = dom.append(data.breakpoint, $('span.condition'));
1010
data.breakpoint.classList.add('exception');
1011
1012
data.actionBar = new ActionBar(data.breakpoint);
1013
data.templateDisposables.add(data.actionBar);
1014
const badgeContainer = dom.append(data.breakpoint, $('.badge-container'));
1015
data.badge = dom.append(badgeContainer, $('span.line-number.monaco-count-badge'));
1016
1017
return data;
1018
}
1019
1020
renderElement(node: ITreeNode<IExceptionBreakpoint, void>, index: number, data: IExceptionBreakpointTemplateData): void {
1021
const exceptionBreakpoint = node.element;
1022
this.renderExceptionBreakpoint(exceptionBreakpoint, data);
1023
}
1024
1025
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IExceptionBreakpoint>, void>, index: number, data: IExceptionBreakpointTemplateData): void {
1026
const exceptionBreakpoint = node.element.elements[node.element.elements.length - 1];
1027
this.renderExceptionBreakpoint(exceptionBreakpoint, data);
1028
}
1029
1030
private renderExceptionBreakpoint(exceptionBreakpoint: IExceptionBreakpoint, data: IExceptionBreakpointTemplateData): void {
1031
data.context = exceptionBreakpoint;
1032
data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
1033
const exceptionBreakpointtitle = exceptionBreakpoint.verified ? (exceptionBreakpoint.description || data.name.textContent) : exceptionBreakpoint.message || localize('unverifiedExceptionBreakpoint', "Unverified Exception Breakpoint");
1034
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, exceptionBreakpointtitle));
1035
data.breakpoint.classList.toggle('disabled', !exceptionBreakpoint.verified);
1036
data.checkbox.checked = exceptionBreakpoint.enabled;
1037
data.condition.textContent = exceptionBreakpoint.condition || '';
1038
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.condition, localize('expressionCondition', "Expression condition: {0}", exceptionBreakpoint.condition)));
1039
1040
if (exceptionBreakpoint.modeLabel) {
1041
data.badge.textContent = exceptionBreakpoint.modeLabel;
1042
data.badge.style.display = 'block';
1043
} else {
1044
data.badge.style.display = 'none';
1045
}
1046
1047
this.breakpointSupportsCondition.set((exceptionBreakpoint as ExceptionBreakpoint).supportsCondition);
1048
this.breakpointItemType.set('exceptionBreakpoint');
1049
this.breakpointHasMultipleModes.set(this.debugService.getModel().getBreakpointModes('exception').length > 1);
1050
const { primary } = getActionBarActions(this.menu.getActions({ arg: exceptionBreakpoint, shouldForwardArgs: true }), 'inline');
1051
data.actionBar.clear();
1052
data.actionBar.push(primary, { icon: true, label: false });
1053
breakpointIdToActionBarDomeNode.set(exceptionBreakpoint.getId(), data.actionBar.domNode);
1054
}
1055
1056
disposeElement(node: ITreeNode<IExceptionBreakpoint, void>, index: number, templateData: IExceptionBreakpointTemplateData): void {
1057
templateData.elementDisposables.clear();
1058
}
1059
1060
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IExceptionBreakpoint>, void>, index: number, templateData: IExceptionBreakpointTemplateData): void {
1061
templateData.elementDisposables.clear();
1062
}
1063
1064
disposeTemplate(templateData: IExceptionBreakpointTemplateData): void {
1065
templateData.templateDisposables.dispose();
1066
}
1067
}
1068
1069
class FunctionBreakpointsRenderer implements ICompressibleTreeRenderer<FunctionBreakpoint, void, IFunctionBreakpointTemplateData> {
1070
1071
constructor(
1072
private menu: IMenu,
1073
private breakpointSupportsCondition: IContextKey<boolean>,
1074
private breakpointItemType: IContextKey<string | undefined>,
1075
@IDebugService private readonly debugService: IDebugService,
1076
@IHoverService private readonly hoverService: IHoverService,
1077
@ILabelService private readonly labelService: ILabelService
1078
) {
1079
// noop
1080
}
1081
1082
static readonly ID = 'functionbreakpoints';
1083
1084
get templateId() {
1085
return FunctionBreakpointsRenderer.ID;
1086
}
1087
1088
renderTemplate(container: HTMLElement): IFunctionBreakpointTemplateData {
1089
const data: IFunctionBreakpointTemplateData = Object.create(null);
1090
data.elementDisposables = new DisposableStore();
1091
data.templateDisposables = new DisposableStore();
1092
data.templateDisposables.add(data.elementDisposables);
1093
data.breakpoint = dom.append(container, $('.breakpoint'));
1094
1095
data.icon = $('.icon');
1096
data.checkbox = createCheckbox(data.templateDisposables);
1097
data.templateDisposables.add(data.checkbox.onChange(() => {
1098
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
1099
}));
1100
1101
dom.append(data.breakpoint, data.icon);
1102
dom.append(data.breakpoint, data.checkbox.domNode);
1103
1104
data.name = dom.append(data.breakpoint, $('span.name'));
1105
data.condition = dom.append(data.breakpoint, $('span.condition'));
1106
1107
data.actionBar = new ActionBar(data.breakpoint);
1108
data.templateDisposables.add(data.actionBar);
1109
const badgeContainer = dom.append(data.breakpoint, $('.badge-container'));
1110
data.badge = dom.append(badgeContainer, $('span.line-number.monaco-count-badge'));
1111
1112
return data;
1113
}
1114
1115
renderElement(node: ITreeNode<FunctionBreakpoint, void>, _index: number, data: IFunctionBreakpointTemplateData): void {
1116
this.renderFunctionBreakpoint(node.element, data);
1117
}
1118
1119
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<FunctionBreakpoint>, void>, _index: number, data: IFunctionBreakpointTemplateData): void {
1120
this.renderFunctionBreakpoint(node.element.elements[node.element.elements.length - 1], data);
1121
}
1122
1123
private renderFunctionBreakpoint(functionBreakpoint: FunctionBreakpoint, data: IFunctionBreakpointTemplateData): void {
1124
data.context = functionBreakpoint;
1125
data.name.textContent = functionBreakpoint.name;
1126
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService, this.debugService.getModel());
1127
data.icon.className = ThemeIcon.asClassName(icon);
1128
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.icon, message ? message : ''));
1129
data.checkbox.checked = functionBreakpoint.enabled;
1130
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, message ? message : ''));
1131
if (functionBreakpoint.condition && functionBreakpoint.hitCondition) {
1132
data.condition.textContent = localize('expressionAndHitCount', "Condition: {0} | Hit Count: {1}", functionBreakpoint.condition, functionBreakpoint.hitCondition);
1133
} else {
1134
data.condition.textContent = functionBreakpoint.condition || functionBreakpoint.hitCondition || '';
1135
}
1136
1137
if (functionBreakpoint.modeLabel) {
1138
data.badge.textContent = functionBreakpoint.modeLabel;
1139
data.badge.style.display = 'block';
1140
} else {
1141
data.badge.style.display = 'none';
1142
}
1143
1144
// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
1145
const session = this.debugService.getViewModel().focusedSession;
1146
data.breakpoint.classList.toggle('disabled', (session && !session.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated());
1147
if (session && !session.capabilities.supportsFunctionBreakpoints) {
1148
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type")));
1149
}
1150
1151
this.breakpointSupportsCondition.set(!session || !!session.capabilities.supportsConditionalBreakpoints);
1152
this.breakpointItemType.set('functionBreakpoint');
1153
const { primary } = getActionBarActions(this.menu.getActions({ arg: functionBreakpoint, shouldForwardArgs: true }), 'inline');
1154
data.actionBar.clear();
1155
data.actionBar.push(primary, { icon: true, label: false });
1156
breakpointIdToActionBarDomeNode.set(functionBreakpoint.getId(), data.actionBar.domNode);
1157
}
1158
1159
disposeElement(node: ITreeNode<FunctionBreakpoint, void>, index: number, templateData: IFunctionBreakpointTemplateData): void {
1160
templateData.elementDisposables.clear();
1161
}
1162
1163
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<FunctionBreakpoint>, void>, index: number, templateData: IFunctionBreakpointTemplateData): void {
1164
templateData.elementDisposables.clear();
1165
}
1166
1167
disposeTemplate(templateData: IFunctionBreakpointTemplateData): void {
1168
templateData.templateDisposables.dispose();
1169
}
1170
}
1171
1172
class DataBreakpointsRenderer implements ICompressibleTreeRenderer<DataBreakpoint, void, IDataBreakpointTemplateData> {
1173
1174
constructor(
1175
private menu: IMenu,
1176
private breakpointHasMultipleModes: IContextKey<boolean>,
1177
private breakpointSupportsCondition: IContextKey<boolean>,
1178
private breakpointItemType: IContextKey<string | undefined>,
1179
private breakpointIsDataBytes: IContextKey<boolean | undefined>,
1180
@IDebugService private readonly debugService: IDebugService,
1181
@IHoverService private readonly hoverService: IHoverService,
1182
@ILabelService private readonly labelService: ILabelService
1183
) {
1184
// noop
1185
}
1186
1187
static readonly ID = 'databreakpoints';
1188
1189
get templateId() {
1190
return DataBreakpointsRenderer.ID;
1191
}
1192
1193
renderTemplate(container: HTMLElement): IDataBreakpointTemplateData {
1194
const data: IDataBreakpointTemplateData = Object.create(null);
1195
data.breakpoint = dom.append(container, $('.breakpoint'));
1196
data.elementDisposables = new DisposableStore();
1197
data.templateDisposables = new DisposableStore();
1198
data.templateDisposables.add(data.elementDisposables);
1199
1200
data.icon = $('.icon');
1201
data.checkbox = createCheckbox(data.templateDisposables);
1202
data.templateDisposables.add(data.checkbox.onChange(() => {
1203
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
1204
}));
1205
1206
dom.append(data.breakpoint, data.icon);
1207
dom.append(data.breakpoint, data.checkbox.domNode);
1208
1209
data.name = dom.append(data.breakpoint, $('span.name'));
1210
data.accessType = dom.append(data.breakpoint, $('span.access-type'));
1211
data.condition = dom.append(data.breakpoint, $('span.condition'));
1212
1213
data.actionBar = new ActionBar(data.breakpoint);
1214
data.templateDisposables.add(data.actionBar);
1215
const badgeContainer = dom.append(data.breakpoint, $('.badge-container'));
1216
data.badge = dom.append(badgeContainer, $('span.line-number.monaco-count-badge'));
1217
1218
return data;
1219
}
1220
1221
renderElement(node: ITreeNode<DataBreakpoint, void>, _index: number, data: IDataBreakpointTemplateData): void {
1222
this.renderDataBreakpoint(node.element, data);
1223
}
1224
1225
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<DataBreakpoint>, void>, _index: number, data: IDataBreakpointTemplateData): void {
1226
this.renderDataBreakpoint(node.element.elements[node.element.elements.length - 1], data);
1227
}
1228
1229
private renderDataBreakpoint(dataBreakpoint: DataBreakpoint, data: IDataBreakpointTemplateData): void {
1230
data.context = dataBreakpoint;
1231
data.name.textContent = dataBreakpoint.description;
1232
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService, this.debugService.getModel());
1233
data.icon.className = ThemeIcon.asClassName(icon);
1234
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.icon, message ? message : ''));
1235
data.checkbox.checked = dataBreakpoint.enabled;
1236
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, message ? message : ''));
1237
1238
if (dataBreakpoint.modeLabel) {
1239
data.badge.textContent = dataBreakpoint.modeLabel;
1240
data.badge.style.display = 'block';
1241
} else {
1242
data.badge.style.display = 'none';
1243
}
1244
1245
// Mark data breakpoints as disabled if deactivated or if debug type does not support them
1246
const session = this.debugService.getViewModel().focusedSession;
1247
data.breakpoint.classList.toggle('disabled', (session && !session.capabilities.supportsDataBreakpoints) || !this.debugService.getModel().areBreakpointsActivated());
1248
if (session && !session.capabilities.supportsDataBreakpoints) {
1249
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, localize('dataBreakpointsNotSupported', "Data breakpoints are not supported by this debug type")));
1250
}
1251
if (dataBreakpoint.accessType) {
1252
const accessType = dataBreakpoint.accessType === 'read' ? localize('read', "Read") : dataBreakpoint.accessType === 'write' ? localize('write', "Write") : localize('access', "Access");
1253
data.accessType.textContent = accessType;
1254
} else {
1255
data.accessType.textContent = '';
1256
}
1257
if (dataBreakpoint.condition && dataBreakpoint.hitCondition) {
1258
data.condition.textContent = localize('expressionAndHitCount', "Condition: {0} | Hit Count: {1}", dataBreakpoint.condition, dataBreakpoint.hitCondition);
1259
} else {
1260
data.condition.textContent = dataBreakpoint.condition || dataBreakpoint.hitCondition || '';
1261
}
1262
1263
this.breakpointSupportsCondition.set(!session || !!session.capabilities.supportsConditionalBreakpoints);
1264
this.breakpointHasMultipleModes.set(this.debugService.getModel().getBreakpointModes('data').length > 1);
1265
this.breakpointItemType.set('dataBreakpoint');
1266
this.breakpointIsDataBytes.set(dataBreakpoint.src.type === DataBreakpointSetType.Address);
1267
const { primary } = getActionBarActions(this.menu.getActions({ arg: dataBreakpoint, shouldForwardArgs: true }), 'inline');
1268
data.actionBar.clear();
1269
data.actionBar.push(primary, { icon: true, label: false });
1270
breakpointIdToActionBarDomeNode.set(dataBreakpoint.getId(), data.actionBar.domNode);
1271
this.breakpointIsDataBytes.reset();
1272
}
1273
1274
disposeElement(node: ITreeNode<DataBreakpoint, void>, index: number, templateData: IDataBreakpointTemplateData): void {
1275
templateData.elementDisposables.clear();
1276
}
1277
1278
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<DataBreakpoint>, void>, index: number, templateData: IDataBreakpointTemplateData): void {
1279
templateData.elementDisposables.clear();
1280
}
1281
1282
disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void {
1283
templateData.templateDisposables.dispose();
1284
}
1285
}
1286
1287
class InstructionBreakpointsRenderer implements ICompressibleTreeRenderer<IInstructionBreakpoint, void, IInstructionBreakpointTemplateData> {
1288
1289
constructor(
1290
@IDebugService private readonly debugService: IDebugService,
1291
@IHoverService private readonly hoverService: IHoverService,
1292
@ILabelService private readonly labelService: ILabelService
1293
) {
1294
// noop
1295
}
1296
1297
static readonly ID = 'instructionBreakpoints';
1298
1299
get templateId() {
1300
return InstructionBreakpointsRenderer.ID;
1301
}
1302
1303
renderTemplate(container: HTMLElement): IInstructionBreakpointTemplateData {
1304
const data: IInstructionBreakpointTemplateData = Object.create(null);
1305
data.elementDisposables = new DisposableStore();
1306
data.templateDisposables = new DisposableStore();
1307
data.templateDisposables.add(data.elementDisposables);
1308
data.breakpoint = dom.append(container, $('.breakpoint'));
1309
1310
data.icon = $('.icon');
1311
data.checkbox = createCheckbox(data.templateDisposables);
1312
data.templateDisposables.add(data.checkbox.onChange(() => {
1313
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
1314
}));
1315
1316
dom.append(data.breakpoint, data.icon);
1317
dom.append(data.breakpoint, data.checkbox.domNode);
1318
1319
data.name = dom.append(data.breakpoint, $('span.name'));
1320
1321
data.address = dom.append(data.breakpoint, $('span.file-path'));
1322
data.actionBar = new ActionBar(data.breakpoint);
1323
data.templateDisposables.add(data.actionBar);
1324
const badgeContainer = dom.append(data.breakpoint, $('.badge-container'));
1325
data.badge = dom.append(badgeContainer, $('span.line-number.monaco-count-badge'));
1326
1327
return data;
1328
}
1329
1330
renderElement(node: ITreeNode<IInstructionBreakpoint, void>, index: number, data: IInstructionBreakpointTemplateData): void {
1331
this.renderInstructionBreakpoint(node.element, data);
1332
}
1333
1334
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IInstructionBreakpoint>, void>, index: number, data: IInstructionBreakpointTemplateData): void {
1335
this.renderInstructionBreakpoint(node.element.elements[node.element.elements.length - 1], data);
1336
}
1337
1338
private renderInstructionBreakpoint(breakpoint: IInstructionBreakpoint, data: IInstructionBreakpointTemplateData): void {
1339
data.context = breakpoint;
1340
data.breakpoint.classList.toggle('disabled', !this.debugService.getModel().areBreakpointsActivated());
1341
1342
data.name.textContent = '0x' + breakpoint.address.toString(16);
1343
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.name, localize('debug.decimal.address', "Decimal Address: {0}", breakpoint.address.toString())));
1344
data.checkbox.checked = breakpoint.enabled;
1345
1346
const { message, icon } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService, this.debugService.getModel());
1347
data.icon.className = ThemeIcon.asClassName(icon);
1348
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.breakpoint, breakpoint.message || message || ''));
1349
1350
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
1351
if (debugActive && !breakpoint.verified) {
1352
data.breakpoint.classList.add('disabled');
1353
}
1354
1355
if (breakpoint.modeLabel) {
1356
data.badge.textContent = breakpoint.modeLabel;
1357
data.badge.style.display = 'block';
1358
} else {
1359
data.badge.style.display = 'none';
1360
}
1361
}
1362
1363
disposeElement(node: ITreeNode<IInstructionBreakpoint, void>, index: number, templateData: IInstructionBreakpointTemplateData): void {
1364
templateData.elementDisposables.clear();
1365
}
1366
1367
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IInstructionBreakpoint>, void>, index: number, templateData: IInstructionBreakpointTemplateData): void {
1368
templateData.elementDisposables.clear();
1369
}
1370
1371
disposeTemplate(templateData: IInstructionBreakpointTemplateData): void {
1372
templateData.templateDisposables.dispose();
1373
}
1374
}
1375
1376
class FunctionBreakpointInputRenderer implements ICompressibleTreeRenderer<IFunctionBreakpoint, void, IFunctionBreakpointInputTemplateData> {
1377
1378
constructor(
1379
private view: BreakpointsView,
1380
private debugService: IDebugService,
1381
private contextViewService: IContextViewService,
1382
private readonly hoverService: IHoverService,
1383
private labelService: ILabelService
1384
) { }
1385
1386
static readonly ID = 'functionbreakpointinput';
1387
1388
get templateId() {
1389
return FunctionBreakpointInputRenderer.ID;
1390
}
1391
1392
renderTemplate(container: HTMLElement): IFunctionBreakpointInputTemplateData {
1393
const template: IFunctionBreakpointInputTemplateData = Object.create(null);
1394
const toDispose = new DisposableStore();
1395
1396
const breakpoint = dom.append(container, $('.breakpoint'));
1397
template.icon = $('.icon');
1398
template.checkbox = createCheckbox(toDispose);
1399
1400
dom.append(breakpoint, template.icon);
1401
dom.append(breakpoint, template.checkbox.domNode);
1402
this.view.breakpointInputFocused.set(true);
1403
const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer'));
1404
1405
1406
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { inputBoxStyles: defaultInputBoxStyles });
1407
1408
toDispose.add(inputBox);
1409
1410
const wrapUp = (success: boolean) => {
1411
template.updating = true;
1412
try {
1413
this.view.breakpointInputFocused.set(false);
1414
const id = template.breakpoint.getId();
1415
1416
if (success) {
1417
if (template.type === 'name') {
1418
this.debugService.updateFunctionBreakpoint(id, { name: inputBox.value });
1419
}
1420
if (template.type === 'condition') {
1421
this.debugService.updateFunctionBreakpoint(id, { condition: inputBox.value });
1422
}
1423
if (template.type === 'hitCount') {
1424
this.debugService.updateFunctionBreakpoint(id, { hitCondition: inputBox.value });
1425
}
1426
} else {
1427
if (template.type === 'name' && !template.breakpoint.name) {
1428
this.debugService.removeFunctionBreakpoints(id);
1429
} else {
1430
this.view.renderInputBox(undefined);
1431
}
1432
}
1433
} finally {
1434
template.updating = false;
1435
}
1436
};
1437
1438
toDispose.add(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
1439
const isEscape = e.equals(KeyCode.Escape);
1440
const isEnter = e.equals(KeyCode.Enter);
1441
if (isEscape || isEnter) {
1442
e.preventDefault();
1443
e.stopPropagation();
1444
wrapUp(isEnter);
1445
}
1446
}));
1447
toDispose.add(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
1448
if (!template.updating) {
1449
wrapUp(!!inputBox.value);
1450
}
1451
}));
1452
1453
template.inputBox = inputBox;
1454
template.elementDisposables = new DisposableStore();
1455
template.templateDisposables = toDispose;
1456
template.templateDisposables.add(template.elementDisposables);
1457
return template;
1458
}
1459
1460
renderElement(node: ITreeNode<FunctionBreakpoint, void>, _index: number, data: IFunctionBreakpointInputTemplateData): void {
1461
const functionBreakpoint = node.element;
1462
data.breakpoint = functionBreakpoint;
1463
data.type = this.view.inputBoxData?.type || 'name'; // If there is no type set take the 'name' as the default
1464
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService, this.debugService.getModel());
1465
1466
data.icon.className = ThemeIcon.asClassName(icon);
1467
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.icon, message ? message : ''));
1468
data.checkbox.checked = functionBreakpoint.enabled;
1469
data.checkbox.disable();
1470
data.inputBox.value = functionBreakpoint.name || '';
1471
1472
let placeholder = localize('functionBreakpointPlaceholder', "Function to break on");
1473
let ariaLabel = localize('functionBreakPointInputAriaLabel', "Type function breakpoint.");
1474
if (data.type === 'condition') {
1475
data.inputBox.value = functionBreakpoint.condition || '';
1476
placeholder = localize('functionBreakpointExpressionPlaceholder', "Break when expression evaluates to true");
1477
ariaLabel = localize('functionBreakPointExpresionAriaLabel', "Type expression. Function breakpoint will break when expression evaluates to true");
1478
} else if (data.type === 'hitCount') {
1479
data.inputBox.value = functionBreakpoint.hitCondition || '';
1480
placeholder = localize('functionBreakpointHitCountPlaceholder', "Break when hit count is met");
1481
ariaLabel = localize('functionBreakPointHitCountAriaLabel', "Type hit count. Function breakpoint will break when hit count is met.");
1482
}
1483
data.inputBox.setAriaLabel(ariaLabel);
1484
data.inputBox.setPlaceHolder(placeholder);
1485
1486
setTimeout(() => {
1487
data.inputBox.focus();
1488
data.inputBox.select();
1489
}, 0);
1490
}
1491
1492
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IFunctionBreakpoint>, void>, _index: number, data: IFunctionBreakpointInputTemplateData): void {
1493
// Function breakpoints are not compressible
1494
}
1495
1496
disposeElement(node: ITreeNode<IFunctionBreakpoint, void>, index: number, templateData: IFunctionBreakpointInputTemplateData): void {
1497
templateData.elementDisposables.clear();
1498
}
1499
1500
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IFunctionBreakpoint>, void>, index: number, templateData: IFunctionBreakpointInputTemplateData): void {
1501
templateData.elementDisposables.clear();
1502
}
1503
1504
disposeTemplate(templateData: IFunctionBreakpointInputTemplateData): void {
1505
templateData.templateDisposables.dispose();
1506
}
1507
}
1508
1509
class DataBreakpointInputRenderer implements ICompressibleTreeRenderer<IDataBreakpoint, void, IDataBreakpointInputTemplateData> {
1510
1511
constructor(
1512
private view: BreakpointsView,
1513
private debugService: IDebugService,
1514
private contextViewService: IContextViewService,
1515
private readonly hoverService: IHoverService,
1516
private labelService: ILabelService
1517
) { }
1518
1519
static readonly ID = 'databreakpointinput';
1520
1521
get templateId() {
1522
return DataBreakpointInputRenderer.ID;
1523
}
1524
1525
renderTemplate(container: HTMLElement): IDataBreakpointInputTemplateData {
1526
const template: IDataBreakpointInputTemplateData = Object.create(null);
1527
const toDispose = new DisposableStore();
1528
1529
const breakpoint = dom.append(container, $('.breakpoint'));
1530
template.icon = $('.icon');
1531
template.checkbox = createCheckbox(toDispose);
1532
1533
dom.append(breakpoint, template.icon);
1534
dom.append(breakpoint, template.checkbox.domNode);
1535
this.view.breakpointInputFocused.set(true);
1536
const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer'));
1537
1538
1539
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { inputBoxStyles: defaultInputBoxStyles });
1540
toDispose.add(inputBox);
1541
1542
const wrapUp = (success: boolean) => {
1543
template.updating = true;
1544
try {
1545
this.view.breakpointInputFocused.set(false);
1546
const id = template.breakpoint.getId();
1547
1548
if (success) {
1549
if (template.type === 'condition') {
1550
this.debugService.updateDataBreakpoint(id, { condition: inputBox.value });
1551
}
1552
if (template.type === 'hitCount') {
1553
this.debugService.updateDataBreakpoint(id, { hitCondition: inputBox.value });
1554
}
1555
} else {
1556
this.view.renderInputBox(undefined);
1557
}
1558
} finally {
1559
template.updating = false;
1560
}
1561
};
1562
1563
toDispose.add(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
1564
const isEscape = e.equals(KeyCode.Escape);
1565
const isEnter = e.equals(KeyCode.Enter);
1566
if (isEscape || isEnter) {
1567
e.preventDefault();
1568
e.stopPropagation();
1569
wrapUp(isEnter);
1570
}
1571
}));
1572
toDispose.add(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
1573
if (!template.updating) {
1574
wrapUp(!!inputBox.value);
1575
}
1576
}));
1577
1578
template.inputBox = inputBox;
1579
template.elementDisposables = new DisposableStore();
1580
template.templateDisposables = toDispose;
1581
template.templateDisposables.add(template.elementDisposables);
1582
return template;
1583
}
1584
1585
renderElement(node: ITreeNode<DataBreakpoint, void>, _index: number, data: IDataBreakpointInputTemplateData): void {
1586
const dataBreakpoint = node.element;
1587
data.breakpoint = dataBreakpoint;
1588
data.type = this.view.inputBoxData?.type || 'condition'; // If there is no type set take the 'condition' as the default
1589
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService, this.debugService.getModel());
1590
1591
data.icon.className = ThemeIcon.asClassName(icon);
1592
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.icon, message ?? ''));
1593
data.checkbox.checked = dataBreakpoint.enabled;
1594
data.checkbox.disable();
1595
data.inputBox.value = '';
1596
let placeholder = '';
1597
let ariaLabel = '';
1598
if (data.type === 'condition') {
1599
data.inputBox.value = dataBreakpoint.condition || '';
1600
placeholder = localize('dataBreakpointExpressionPlaceholder', "Break when expression evaluates to true");
1601
ariaLabel = localize('dataBreakPointExpresionAriaLabel', "Type expression. Data breakpoint will break when expression evaluates to true");
1602
} else if (data.type === 'hitCount') {
1603
data.inputBox.value = dataBreakpoint.hitCondition || '';
1604
placeholder = localize('dataBreakpointHitCountPlaceholder', "Break when hit count is met");
1605
ariaLabel = localize('dataBreakPointHitCountAriaLabel', "Type hit count. Data breakpoint will break when hit count is met.");
1606
}
1607
data.inputBox.setAriaLabel(ariaLabel);
1608
data.inputBox.setPlaceHolder(placeholder);
1609
1610
setTimeout(() => {
1611
data.inputBox.focus();
1612
data.inputBox.select();
1613
}, 0);
1614
}
1615
1616
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IDataBreakpoint>, void>, _index: number, data: IDataBreakpointInputTemplateData): void {
1617
// Data breakpoints are not compressible
1618
}
1619
1620
disposeElement(node: ITreeNode<IDataBreakpoint, void>, index: number, templateData: IDataBreakpointInputTemplateData): void {
1621
templateData.elementDisposables.clear();
1622
}
1623
1624
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IDataBreakpoint>, void>, index: number, templateData: IDataBreakpointInputTemplateData): void {
1625
templateData.elementDisposables.clear();
1626
}
1627
1628
disposeTemplate(templateData: IDataBreakpointInputTemplateData): void {
1629
templateData.templateDisposables.dispose();
1630
}
1631
}
1632
1633
class ExceptionBreakpointInputRenderer implements ICompressibleTreeRenderer<IExceptionBreakpoint, void, IExceptionBreakpointInputTemplateData> {
1634
1635
constructor(
1636
private view: BreakpointsView,
1637
private debugService: IDebugService,
1638
private contextViewService: IContextViewService,
1639
) {
1640
// noop
1641
}
1642
1643
static readonly ID = 'exceptionbreakpointinput';
1644
1645
get templateId() {
1646
return ExceptionBreakpointInputRenderer.ID;
1647
}
1648
1649
renderTemplate(container: HTMLElement): IExceptionBreakpointInputTemplateData {
1650
const toDispose = new DisposableStore();
1651
1652
const breakpoint = dom.append(container, $('.breakpoint'));
1653
breakpoint.classList.add('exception');
1654
const checkbox = createCheckbox(toDispose);
1655
1656
dom.append(breakpoint, checkbox.domNode);
1657
this.view.breakpointInputFocused.set(true);
1658
const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer'));
1659
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
1660
ariaLabel: localize('exceptionBreakpointAriaLabel', "Type exception breakpoint condition"),
1661
inputBoxStyles: defaultInputBoxStyles
1662
});
1663
1664
1665
toDispose.add(inputBox);
1666
const wrapUp = (success: boolean) => {
1667
if (!templateData.currentBreakpoint) {
1668
return;
1669
}
1670
1671
this.view.breakpointInputFocused.set(false);
1672
let newCondition = templateData.currentBreakpoint.condition;
1673
if (success) {
1674
newCondition = inputBox.value !== '' ? inputBox.value : undefined;
1675
}
1676
this.debugService.setExceptionBreakpointCondition(templateData.currentBreakpoint, newCondition);
1677
};
1678
1679
toDispose.add(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
1680
const isEscape = e.equals(KeyCode.Escape);
1681
const isEnter = e.equals(KeyCode.Enter);
1682
if (isEscape || isEnter) {
1683
e.preventDefault();
1684
e.stopPropagation();
1685
wrapUp(isEnter);
1686
}
1687
}));
1688
toDispose.add(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
1689
// Need to react with a timeout on the blur event due to possible concurent splices #56443
1690
setTimeout(() => {
1691
wrapUp(true);
1692
});
1693
}));
1694
1695
const elementDisposables = new DisposableStore();
1696
toDispose.add(elementDisposables);
1697
1698
const templateData: IExceptionBreakpointInputTemplateData = {
1699
inputBox,
1700
checkbox,
1701
templateDisposables: toDispose,
1702
elementDisposables: new DisposableStore(),
1703
};
1704
1705
return templateData;
1706
}
1707
1708
renderElement(node: ITreeNode<ExceptionBreakpoint, void>, _index: number, data: IExceptionBreakpointInputTemplateData): void {
1709
const exceptionBreakpoint = node.element;
1710
const placeHolder = exceptionBreakpoint.conditionDescription || localize('exceptionBreakpointPlaceholder', "Break when expression evaluates to true");
1711
data.inputBox.setPlaceHolder(placeHolder);
1712
data.currentBreakpoint = exceptionBreakpoint;
1713
data.checkbox.checked = exceptionBreakpoint.enabled;
1714
data.checkbox.disable();
1715
data.inputBox.value = exceptionBreakpoint.condition || '';
1716
setTimeout(() => {
1717
data.inputBox.focus();
1718
data.inputBox.select();
1719
}, 0);
1720
}
1721
1722
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IExceptionBreakpoint>, void>, _index: number, data: IExceptionBreakpointInputTemplateData): void {
1723
// Exception breakpoints are not compressible
1724
}
1725
1726
disposeElement(node: ITreeNode<IExceptionBreakpoint, void>, index: number, templateData: IExceptionBreakpointInputTemplateData): void {
1727
templateData.elementDisposables.clear();
1728
}
1729
1730
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IExceptionBreakpoint>, void>, index: number, templateData: IExceptionBreakpointInputTemplateData): void {
1731
templateData.elementDisposables.clear();
1732
}
1733
1734
disposeTemplate(templateData: IExceptionBreakpointInputTemplateData): void {
1735
templateData.templateDisposables.dispose();
1736
}
1737
}
1738
1739
class BreakpointsAccessibilityProvider implements IListAccessibilityProvider<BreakpointTreeElement> {
1740
1741
constructor(
1742
private readonly debugService: IDebugService,
1743
private readonly labelService: ILabelService
1744
) { }
1745
1746
getWidgetAriaLabel(): string {
1747
return localize('breakpoints', "Breakpoints");
1748
}
1749
1750
getRole(): AriaRole {
1751
return 'checkbox';
1752
}
1753
1754
isChecked(element: BreakpointTreeElement) {
1755
if (element instanceof BreakpointsFolderItem) {
1756
return element.enabled;
1757
}
1758
return element.enabled;
1759
}
1760
1761
getAriaLabel(element: BreakpointTreeElement): string | null {
1762
if (element instanceof BreakpointsFolderItem) {
1763
return localize('breakpointFolder', "Breakpoints in {0}, {1} breakpoints", resources.basenameOrAuthority(element.uri), element.breakpoints.length);
1764
}
1765
1766
if (element instanceof ExceptionBreakpoint) {
1767
return element.toString();
1768
}
1769
1770
const { message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint, this.labelService, this.debugService.getModel());
1771
const toString = element.toString();
1772
1773
return message ? `${toString}, ${message}` : toString;
1774
}
1775
}
1776
1777
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, pinned: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditorPane | undefined> {
1778
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
1779
return Promise.resolve(undefined);
1780
}
1781
1782
const selection = breakpoint.endLineNumber ? {
1783
startLineNumber: breakpoint.lineNumber,
1784
endLineNumber: breakpoint.endLineNumber,
1785
startColumn: breakpoint.column || 1,
1786
endColumn: breakpoint.endColumn || Constants.MAX_SAFE_SMALL_INTEGER
1787
} : {
1788
startLineNumber: breakpoint.lineNumber,
1789
startColumn: breakpoint.column || 1,
1790
endLineNumber: breakpoint.lineNumber,
1791
endColumn: breakpoint.column || Constants.MAX_SAFE_SMALL_INTEGER
1792
};
1793
1794
return editorService.openEditor({
1795
resource: breakpoint.uri,
1796
options: {
1797
preserveFocus,
1798
selection,
1799
revealIfOpened: true,
1800
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,
1801
pinned
1802
}
1803
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
1804
}
1805
1806
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: BreakpointItem, labelService: ILabelService, debugModel: IDebugModel): { message?: string; icon: ThemeIcon; showAdapterUnverifiedMessage?: boolean } {
1807
const debugActive = state === State.Running || state === State.Stopped;
1808
1809
const breakpointIcon = breakpoint instanceof DataBreakpoint ? icons.dataBreakpoint : breakpoint instanceof FunctionBreakpoint ? icons.functionBreakpoint : breakpoint.logMessage ? icons.logBreakpoint : icons.breakpoint;
1810
1811
if (!breakpoint.enabled || !breakpointsActivated) {
1812
return {
1813
icon: breakpointIcon.disabled,
1814
message: breakpoint.logMessage ? localize('disabledLogpoint', "Disabled Logpoint") : localize('disabledBreakpoint', "Disabled Breakpoint"),
1815
};
1816
}
1817
1818
const appendMessage = (text: string): string => {
1819
return breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
1820
};
1821
1822
if (debugActive && breakpoint instanceof Breakpoint && breakpoint.pending) {
1823
return {
1824
icon: icons.breakpoint.pending
1825
};
1826
}
1827
1828
if (debugActive && !breakpoint.verified) {
1829
return {
1830
icon: breakpointIcon.unverified,
1831
message: breakpoint.message ? breakpoint.message : (breakpoint.logMessage ? localize('unverifiedLogpoint', "Unverified Logpoint") : localize('unverifiedBreakpoint', "Unverified Breakpoint")),
1832
showAdapterUnverifiedMessage: true
1833
};
1834
}
1835
1836
if (breakpoint instanceof DataBreakpoint) {
1837
if (!breakpoint.supported) {
1838
return {
1839
icon: breakpointIcon.unverified,
1840
message: localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"),
1841
};
1842
}
1843
1844
return {
1845
icon: breakpointIcon.regular,
1846
message: breakpoint.message || localize('dataBreakpoint', "Data Breakpoint")
1847
};
1848
}
1849
1850
if (breakpoint instanceof FunctionBreakpoint) {
1851
if (!breakpoint.supported) {
1852
return {
1853
icon: breakpointIcon.unverified,
1854
message: localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"),
1855
};
1856
}
1857
const messages: string[] = [];
1858
messages.push(breakpoint.message || localize('functionBreakpoint', "Function Breakpoint"));
1859
if (breakpoint.condition) {
1860
messages.push(localize('expression', "Condition: {0}", breakpoint.condition));
1861
}
1862
if (breakpoint.hitCondition) {
1863
messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
1864
}
1865
1866
return {
1867
icon: breakpointIcon.regular,
1868
message: appendMessage(messages.join('\n'))
1869
};
1870
}
1871
1872
if (breakpoint instanceof InstructionBreakpoint) {
1873
if (!breakpoint.supported) {
1874
return {
1875
icon: breakpointIcon.unverified,
1876
message: localize('instructionBreakpointUnsupported', "Instruction breakpoints not supported by this debug type"),
1877
};
1878
}
1879
const messages: string[] = [];
1880
if (breakpoint.message) {
1881
messages.push(breakpoint.message);
1882
} else if (breakpoint.instructionReference) {
1883
messages.push(localize('instructionBreakpointAtAddress', "Instruction breakpoint at address {0}", breakpoint.instructionReference));
1884
} else {
1885
messages.push(localize('instructionBreakpoint', "Instruction breakpoint"));
1886
}
1887
1888
if (breakpoint.hitCondition) {
1889
messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
1890
}
1891
1892
return {
1893
icon: breakpointIcon.regular,
1894
message: appendMessage(messages.join('\n'))
1895
};
1896
}
1897
1898
// can change this when all breakpoint supports dependent breakpoint condition
1899
let triggeringBreakpoint: IBreakpoint | undefined;
1900
if (breakpoint instanceof Breakpoint && breakpoint.triggeredBy) {
1901
triggeringBreakpoint = debugModel.getBreakpoints().find(bp => bp.getId() === breakpoint.triggeredBy);
1902
}
1903
1904
if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition || triggeringBreakpoint) {
1905
const messages: string[] = [];
1906
let icon = breakpoint.logMessage ? icons.logBreakpoint.regular : icons.conditionalBreakpoint.regular;
1907
if (!breakpoint.supported) {
1908
icon = icons.debugBreakpointUnsupported;
1909
messages.push(localize('breakpointUnsupported', "Breakpoints of this type are not supported by the debugger"));
1910
}
1911
1912
if (breakpoint.logMessage) {
1913
messages.push(localize('logMessage', "Log Message: {0}", breakpoint.logMessage));
1914
}
1915
if (breakpoint.condition) {
1916
messages.push(localize('expression', "Condition: {0}", breakpoint.condition));
1917
}
1918
if (breakpoint.hitCondition) {
1919
messages.push(localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
1920
}
1921
if (triggeringBreakpoint) {
1922
messages.push(localize('triggeredBy', "Hit after breakpoint: {0}", `${labelService.getUriLabel(triggeringBreakpoint.uri, { relative: true })}: ${triggeringBreakpoint.lineNumber}`));
1923
}
1924
1925
return {
1926
icon,
1927
message: appendMessage(messages.join('\n'))
1928
};
1929
}
1930
1931
const message = breakpoint.message ? breakpoint.message : breakpoint instanceof Breakpoint && labelService ? labelService.getUriLabel(breakpoint.uri) : localize('breakpoint', "Breakpoint");
1932
return {
1933
icon: breakpointIcon.regular,
1934
message
1935
};
1936
}
1937
1938
registerAction2(class extends Action2 {
1939
constructor() {
1940
super({
1941
id: 'workbench.debug.viewlet.action.addFunctionBreakpointAction',
1942
title: {
1943
...localize2('addFunctionBreakpoint', "Add Function Breakpoint"),
1944
mnemonicTitle: localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint..."),
1945
},
1946
f1: true,
1947
icon: icons.watchExpressionsAddFuncBreakpoint,
1948
menu: [{
1949
id: MenuId.ViewTitle,
1950
group: 'navigation',
1951
order: 10,
1952
when: ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID)
1953
}, {
1954
id: MenuId.MenubarNewBreakpointMenu,
1955
group: '1_breakpoints',
1956
order: 3,
1957
when: CONTEXT_DEBUGGERS_AVAILABLE
1958
}]
1959
});
1960
}
1961
1962
async run(accessor: ServicesAccessor): Promise<void> {
1963
const debugService = accessor.get(IDebugService);
1964
const viewService = accessor.get(IViewsService);
1965
await viewService.openView(BREAKPOINTS_VIEW_ID);
1966
debugService.addFunctionBreakpoint();
1967
}
1968
});
1969
1970
abstract class MemoryBreakpointAction extends Action2 {
1971
async run(accessor: ServicesAccessor, existingBreakpoint?: IDataBreakpoint): Promise<void> {
1972
const debugService = accessor.get(IDebugService);
1973
const session = debugService.getViewModel().focusedSession;
1974
if (!session) {
1975
return;
1976
}
1977
1978
let defaultValue = undefined;
1979
if (existingBreakpoint && existingBreakpoint.src.type === DataBreakpointSetType.Address) {
1980
defaultValue = `${existingBreakpoint.src.address} + ${existingBreakpoint.src.bytes}`;
1981
}
1982
1983
const quickInput = accessor.get(IQuickInputService);
1984
const notifications = accessor.get(INotificationService);
1985
const range = await this.getRange(quickInput, defaultValue);
1986
if (!range) {
1987
return;
1988
}
1989
1990
let info: IDataBreakpointInfoResponse | undefined;
1991
try {
1992
info = await session.dataBytesBreakpointInfo(range.address, range.bytes);
1993
} catch (e) {
1994
notifications.error(localize('dataBreakpointError', "Failed to set data breakpoint at {0}: {1}", range.address, e.message));
1995
}
1996
1997
if (!info?.dataId) {
1998
return;
1999
}
2000
2001
let accessType: DebugProtocol.DataBreakpointAccessType = 'write';
2002
if (info.accessTypes && info.accessTypes?.length > 1) {
2003
const accessTypes = info.accessTypes.map(type => ({ label: type }));
2004
const selectedAccessType = await quickInput.pick(accessTypes, { placeHolder: localize('dataBreakpointAccessType', "Select the access type to monitor") });
2005
if (!selectedAccessType) {
2006
return;
2007
}
2008
2009
accessType = selectedAccessType.label;
2010
}
2011
2012
const src: DataBreakpointSource = { type: DataBreakpointSetType.Address, ...range };
2013
if (existingBreakpoint) {
2014
await debugService.removeDataBreakpoints(existingBreakpoint.getId());
2015
}
2016
2017
await debugService.addDataBreakpoint({
2018
description: info.description,
2019
src,
2020
canPersist: true,
2021
accessTypes: info.accessTypes,
2022
accessType: accessType,
2023
initialSessionData: { session, dataId: info.dataId }
2024
});
2025
}
2026
2027
private getRange(quickInput: IQuickInputService, defaultValue?: string) {
2028
return new Promise<{ address: string; bytes: number } | undefined>(resolve => {
2029
const disposables = new DisposableStore();
2030
const input = disposables.add(quickInput.createInputBox());
2031
input.prompt = localize('dataBreakpointMemoryRangePrompt', "Enter a memory range in which to break");
2032
input.placeholder = localize('dataBreakpointMemoryRangePlaceholder', 'Absolute range (0x1234 - 0x1300) or range of bytes after an address (0x1234 + 0xff)');
2033
if (defaultValue) {
2034
input.value = defaultValue;
2035
input.valueSelection = [0, defaultValue.length];
2036
}
2037
disposables.add(input.onDidChangeValue(e => {
2038
const err = this.parseAddress(e, false);
2039
input.validationMessage = err?.error;
2040
}));
2041
disposables.add(input.onDidAccept(() => {
2042
const r = this.parseAddress(input.value, true);
2043
if (hasKey(r, { error: true })) {
2044
input.validationMessage = r.error;
2045
} else {
2046
resolve(r);
2047
}
2048
input.dispose();
2049
}));
2050
disposables.add(input.onDidHide(() => {
2051
resolve(undefined);
2052
disposables.dispose();
2053
}));
2054
input.ignoreFocusOut = true;
2055
input.show();
2056
});
2057
}
2058
2059
private parseAddress(range: string, isFinal: false): { error: string } | undefined;
2060
private parseAddress(range: string, isFinal: true): { error: string } | { address: string; bytes: number };
2061
private parseAddress(range: string, isFinal: boolean): { error: string } | { address: string; bytes: number } | undefined {
2062
const parts = /^(\S+)\s*(?:([+-])\s*(\S+))?/.exec(range);
2063
if (!parts) {
2064
return { error: localize('dataBreakpointAddrFormat', 'Address should be a range of numbers the form "[Start] - [End]" or "[Start] + [Bytes]"') };
2065
}
2066
2067
const isNum = (e: string) => isFinal ? /^0x[0-9a-f]*|[0-9]*$/i.test(e) : /^0x[0-9a-f]+|[0-9]+$/i.test(e);
2068
const [, startStr, sign = '+', endStr = '1'] = parts;
2069
2070
for (const n of [startStr, endStr]) {
2071
if (!isNum(n)) {
2072
return { error: localize('dataBreakpointAddrStartEnd', 'Number must be a decimal integer or hex value starting with \"0x\", got {0}', n) };
2073
}
2074
}
2075
2076
if (!isFinal) {
2077
return;
2078
}
2079
2080
const start = BigInt(startStr);
2081
const end = BigInt(endStr);
2082
const address = `0x${start.toString(16)}`;
2083
if (sign === '-') {
2084
if (start > end) {
2085
return { error: localize('dataBreakpointAddrOrder', 'End ({1}) should be greater than Start ({0})', startStr, endStr) };
2086
}
2087
return { address, bytes: Number(end - start) };
2088
}
2089
2090
return { address, bytes: Number(end) };
2091
}
2092
}
2093
2094
registerAction2(class extends MemoryBreakpointAction {
2095
constructor() {
2096
super({
2097
id: 'workbench.debug.viewlet.action.addDataBreakpointOnAddress',
2098
title: {
2099
...localize2('addDataBreakpointOnAddress', "Add Data Breakpoint at Address"),
2100
mnemonicTitle: localize({ key: 'miDataBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Data Breakpoint..."),
2101
},
2102
f1: true,
2103
icon: icons.watchExpressionsAddDataBreakpoint,
2104
menu: [{
2105
id: MenuId.ViewTitle,
2106
group: 'navigation',
2107
order: 11,
2108
when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID))
2109
}, {
2110
id: MenuId.MenubarNewBreakpointMenu,
2111
group: '1_breakpoints',
2112
order: 4,
2113
when: CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED
2114
}]
2115
});
2116
}
2117
});
2118
2119
registerAction2(class extends MemoryBreakpointAction {
2120
constructor() {
2121
super({
2122
id: 'workbench.debug.viewlet.action.editDataBreakpointOnAddress',
2123
title: localize2('editDataBreakpointOnAddress', "Edit Address..."),
2124
menu: [{
2125
id: MenuId.DebugBreakpointsContext,
2126
when: ContextKeyExpr.and(CONTEXT_SET_DATA_BREAKPOINT_BYTES_SUPPORTED, CONTEXT_BREAKPOINT_ITEM_IS_DATA_BYTES),
2127
group: 'navigation',
2128
order: 15,
2129
}]
2130
});
2131
}
2132
});
2133
2134
registerAction2(class extends Action2 {
2135
constructor() {
2136
super({
2137
id: 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction',
2138
title: localize2('activateBreakpoints', 'Toggle Activate Breakpoints'),
2139
f1: true,
2140
icon: icons.breakpointsActivate,
2141
menu: {
2142
id: MenuId.ViewTitle,
2143
group: 'navigation',
2144
order: 20,
2145
when: ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID)
2146
}
2147
});
2148
}
2149
2150
run(accessor: ServicesAccessor): void {
2151
const debugService = accessor.get(IDebugService);
2152
debugService.setBreakpointsActivated(!debugService.getModel().areBreakpointsActivated());
2153
}
2154
});
2155
2156
registerAction2(class extends Action2 {
2157
constructor() {
2158
super({
2159
id: 'workbench.debug.viewlet.action.removeBreakpoint',
2160
title: localize('removeBreakpoint', "Remove Breakpoint"),
2161
icon: Codicon.removeClose,
2162
menu: [{
2163
id: MenuId.DebugBreakpointsContext,
2164
group: '3_modification',
2165
order: 10,
2166
when: CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')
2167
}, {
2168
id: MenuId.DebugBreakpointsContext,
2169
group: 'inline',
2170
order: 20,
2171
when: CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint')
2172
}]
2173
});
2174
}
2175
2176
async run(accessor: ServicesAccessor, breakpoint: IBaseBreakpoint): Promise<void> {
2177
const debugService = accessor.get(IDebugService);
2178
if (breakpoint instanceof Breakpoint) {
2179
await debugService.removeBreakpoints(breakpoint.getId());
2180
} else if (breakpoint instanceof FunctionBreakpoint) {
2181
await debugService.removeFunctionBreakpoints(breakpoint.getId());
2182
} else if (breakpoint instanceof DataBreakpoint) {
2183
await debugService.removeDataBreakpoints(breakpoint.getId());
2184
} else if (breakpoint instanceof InstructionBreakpoint) {
2185
await debugService.removeInstructionBreakpoints(breakpoint.instructionReference, breakpoint.offset);
2186
}
2187
}
2188
});
2189
2190
registerAction2(class extends Action2 {
2191
constructor() {
2192
super({
2193
id: 'workbench.debug.viewlet.action.removeAllBreakpoints',
2194
title: {
2195
...localize2('removeAllBreakpoints', "Remove All Breakpoints"),
2196
mnemonicTitle: localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints"),
2197
},
2198
f1: true,
2199
icon: icons.breakpointsRemoveAll,
2200
menu: [{
2201
id: MenuId.ViewTitle,
2202
group: 'navigation',
2203
order: 30,
2204
when: ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID)
2205
}, {
2206
id: MenuId.DebugBreakpointsContext,
2207
group: '3_modification',
2208
order: 20,
2209
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint'))
2210
}, {
2211
id: MenuId.MenubarDebugMenu,
2212
group: '5_breakpoints',
2213
order: 3,
2214
when: CONTEXT_DEBUGGERS_AVAILABLE
2215
}]
2216
});
2217
}
2218
2219
run(accessor: ServicesAccessor): void {
2220
const debugService = accessor.get(IDebugService);
2221
debugService.removeBreakpoints();
2222
debugService.removeFunctionBreakpoints();
2223
debugService.removeDataBreakpoints();
2224
debugService.removeInstructionBreakpoints();
2225
}
2226
});
2227
2228
registerAction2(class extends Action2 {
2229
constructor() {
2230
super({
2231
id: 'workbench.debug.viewlet.action.enableAllBreakpoints',
2232
title: {
2233
...localize2('enableAllBreakpoints', "Enable All Breakpoints"),
2234
mnemonicTitle: localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "&&Enable All Breakpoints"),
2235
},
2236
f1: true,
2237
precondition: CONTEXT_DEBUGGERS_AVAILABLE,
2238
menu: [{
2239
id: MenuId.DebugBreakpointsContext,
2240
group: 'z_commands',
2241
order: 10,
2242
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint'))
2243
}, {
2244
id: MenuId.MenubarDebugMenu,
2245
group: '5_breakpoints',
2246
order: 1,
2247
when: CONTEXT_DEBUGGERS_AVAILABLE
2248
}]
2249
});
2250
}
2251
2252
async run(accessor: ServicesAccessor): Promise<void> {
2253
const debugService = accessor.get(IDebugService);
2254
await debugService.enableOrDisableBreakpoints(true);
2255
}
2256
});
2257
2258
registerAction2(class extends Action2 {
2259
constructor() {
2260
super({
2261
id: 'workbench.debug.viewlet.action.disableAllBreakpoints',
2262
title: {
2263
...localize2('disableAllBreakpoints', "Disable All Breakpoints"),
2264
mnemonicTitle: localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints"),
2265
},
2266
f1: true,
2267
precondition: CONTEXT_DEBUGGERS_AVAILABLE,
2268
menu: [{
2269
id: MenuId.DebugBreakpointsContext,
2270
group: 'z_commands',
2271
order: 20,
2272
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint'))
2273
}, {
2274
id: MenuId.MenubarDebugMenu,
2275
group: '5_breakpoints',
2276
order: 2,
2277
when: CONTEXT_DEBUGGERS_AVAILABLE
2278
}]
2279
});
2280
}
2281
2282
async run(accessor: ServicesAccessor): Promise<void> {
2283
const debugService = accessor.get(IDebugService);
2284
await debugService.enableOrDisableBreakpoints(false);
2285
}
2286
});
2287
2288
registerAction2(class extends Action2 {
2289
constructor() {
2290
super({
2291
id: 'workbench.debug.viewlet.action.reapplyBreakpointsAction',
2292
title: localize2('reapplyAllBreakpoints', 'Reapply All Breakpoints'),
2293
f1: true,
2294
precondition: CONTEXT_IN_DEBUG_MODE,
2295
menu: [{
2296
id: MenuId.DebugBreakpointsContext,
2297
group: 'z_commands',
2298
order: 30,
2299
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('exceptionBreakpoint'))
2300
}]
2301
});
2302
}
2303
2304
async run(accessor: ServicesAccessor): Promise<void> {
2305
const debugService = accessor.get(IDebugService);
2306
await debugService.setBreakpointsActivated(true);
2307
}
2308
});
2309
2310
registerAction2(class extends Action2 {
2311
constructor() {
2312
super({
2313
id: 'workbench.debug.viewlet.action.toggleBreakpointsPresentation',
2314
title: localize2('toggleBreakpointsPresentation', "Toggle Breakpoints View Presentation"),
2315
f1: true,
2316
icon: icons.breakpointsViewIcon,
2317
menu: {
2318
id: MenuId.ViewTitle,
2319
group: 'navigation',
2320
order: 10,
2321
when: ContextKeyExpr.equals('view', BREAKPOINTS_VIEW_ID)
2322
}
2323
});
2324
}
2325
2326
async run(accessor: ServicesAccessor): Promise<void> {
2327
const configurationService = accessor.get(IConfigurationService);
2328
const currentPresentation = configurationService.getValue<'list' | 'tree'>('debug.breakpointsView.presentation');
2329
const newPresentation = currentPresentation === 'tree' ? 'list' : 'tree';
2330
await configurationService.updateValue('debug.breakpointsView.presentation', newPresentation);
2331
}
2332
});
2333
2334
registerAction2(class extends ViewAction<BreakpointsView> {
2335
constructor() {
2336
super({
2337
id: 'debug.editBreakpoint',
2338
viewId: BREAKPOINTS_VIEW_ID,
2339
title: localize('editCondition', "Edit Condition..."),
2340
icon: Codicon.edit,
2341
precondition: CONTEXT_BREAKPOINT_SUPPORTS_CONDITION,
2342
menu: [{
2343
id: MenuId.DebugBreakpointsContext,
2344
when: CONTEXT_BREAKPOINT_ITEM_TYPE.notEqualsTo('functionBreakpoint'),
2345
group: 'navigation',
2346
order: 10
2347
}, {
2348
id: MenuId.DebugBreakpointsContext,
2349
group: 'inline',
2350
order: 10
2351
}]
2352
});
2353
}
2354
2355
async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: ExceptionBreakpoint | Breakpoint | FunctionBreakpoint | DataBreakpoint): Promise<void> {
2356
const debugService = accessor.get(IDebugService);
2357
const editorService = accessor.get(IEditorService);
2358
if (breakpoint instanceof Breakpoint) {
2359
const editor = await openBreakpointSource(breakpoint, false, false, true, debugService, editorService);
2360
if (editor) {
2361
const codeEditor = editor.getControl();
2362
if (isCodeEditor(codeEditor)) {
2363
codeEditor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID)?.showBreakpointWidget(breakpoint.lineNumber, breakpoint.column);
2364
}
2365
}
2366
} else if (breakpoint instanceof FunctionBreakpoint) {
2367
const contextMenuService = accessor.get(IContextMenuService);
2368
const actions: Action[] = [new Action('breakpoint.editCondition', localize('editCondition', "Edit Condition..."), undefined, true, async () => view.renderInputBox({ breakpoint, type: 'condition' })),
2369
new Action('breakpoint.editCondition', localize('editHitCount', "Edit Hit Count..."), undefined, true, async () => view.renderInputBox({ breakpoint, type: 'hitCount' }))];
2370
const domNode = breakpointIdToActionBarDomeNode.get(breakpoint.getId());
2371
2372
if (domNode) {
2373
contextMenuService.showContextMenu({
2374
getActions: () => actions,
2375
getAnchor: () => domNode,
2376
onHide: () => dispose(actions)
2377
});
2378
}
2379
} else {
2380
view.renderInputBox({ breakpoint, type: 'condition' });
2381
}
2382
}
2383
});
2384
2385
2386
registerAction2(class extends ViewAction<BreakpointsView> {
2387
constructor() {
2388
super({
2389
id: 'debug.editFunctionBreakpoint',
2390
viewId: BREAKPOINTS_VIEW_ID,
2391
title: localize('editBreakpoint', "Edit Function Condition..."),
2392
menu: [{
2393
id: MenuId.DebugBreakpointsContext,
2394
group: 'navigation',
2395
order: 10,
2396
when: CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('functionBreakpoint')
2397
}]
2398
});
2399
}
2400
2401
runInView(_accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IFunctionBreakpoint) {
2402
view.renderInputBox({ breakpoint, type: 'name' });
2403
}
2404
});
2405
2406
registerAction2(class extends ViewAction<BreakpointsView> {
2407
constructor() {
2408
super({
2409
id: 'debug.editFunctionBreakpointHitCount',
2410
viewId: BREAKPOINTS_VIEW_ID,
2411
title: localize('editHitCount', "Edit Hit Count..."),
2412
precondition: CONTEXT_BREAKPOINT_SUPPORTS_CONDITION,
2413
menu: [{
2414
id: MenuId.DebugBreakpointsContext,
2415
group: 'navigation',
2416
order: 20,
2417
when: ContextKeyExpr.or(CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('functionBreakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('dataBreakpoint'))
2418
}]
2419
});
2420
}
2421
2422
runInView(_accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IFunctionBreakpoint) {
2423
view.renderInputBox({ breakpoint, type: 'hitCount' });
2424
}
2425
});
2426
2427
registerAction2(class extends ViewAction<BreakpointsView> {
2428
constructor() {
2429
super({
2430
id: 'debug.editBreakpointMode',
2431
viewId: BREAKPOINTS_VIEW_ID,
2432
title: localize('editMode', "Edit Mode..."),
2433
menu: [{
2434
id: MenuId.DebugBreakpointsContext,
2435
group: 'navigation',
2436
order: 20,
2437
when: ContextKeyExpr.and(
2438
CONTEXT_BREAKPOINT_HAS_MODES,
2439
ContextKeyExpr.or(CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('breakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('exceptionBreakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('instructionBreakpoint'))
2440
)
2441
}]
2442
});
2443
}
2444
2445
async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IBreakpoint) {
2446
const debugService = accessor.get(IDebugService);
2447
const kind = getModeKindForBreakpoint(breakpoint);
2448
const modes = debugService.getModel().getBreakpointModes(kind);
2449
const picked = await accessor.get(IQuickInputService).pick(
2450
modes.map(mode => ({ label: mode.label, description: mode.description, mode: mode.mode })),
2451
{ placeHolder: localize('selectBreakpointMode', "Select Breakpoint Mode") }
2452
);
2453
2454
if (!picked) {
2455
return;
2456
}
2457
2458
if (kind === 'source') {
2459
const data = new Map<string, IBreakpointUpdateData>();
2460
data.set(breakpoint.getId(), { mode: picked.mode, modeLabel: picked.label });
2461
debugService.updateBreakpoints(breakpoint.originalUri, data, false);
2462
} else if (breakpoint instanceof InstructionBreakpoint) {
2463
debugService.removeInstructionBreakpoints(breakpoint.instructionReference, breakpoint.offset);
2464
debugService.addInstructionBreakpoint({ ...breakpoint.toJSON(), mode: picked.mode, modeLabel: picked.label });
2465
} else if (breakpoint instanceof ExceptionBreakpoint) {
2466
breakpoint.mode = picked.mode;
2467
breakpoint.modeLabel = picked.label;
2468
debugService.setExceptionBreakpointCondition(breakpoint, breakpoint.condition); // no-op to trigger a re-send
2469
}
2470
}
2471
});
2472
2473