Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/callStackView.ts
5221 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 { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
8
import { AriaRole } from '../../../../base/browser/ui/aria/aria.js';
9
import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
10
import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js';
11
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
12
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
13
import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js';
14
import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/asyncDataTree.js';
15
import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
16
import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
17
import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
18
import { Action } from '../../../../base/common/actions.js';
19
import { RunOnceScheduler } from '../../../../base/common/async.js';
20
import { Codicon } from '../../../../base/common/codicons.js';
21
import { Event } from '../../../../base/common/event.js';
22
import { createMatches, FuzzyScore, IMatch } from '../../../../base/common/filters.js';
23
import { DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js';
24
import { posix } from '../../../../base/common/path.js';
25
import { commonSuffixLength } from '../../../../base/common/strings.js';
26
import { ThemeIcon } from '../../../../base/common/themables.js';
27
import { IRange } from '../../../../editor/common/core/range.js';
28
import { localize } from '../../../../nls.js';
29
import { ICommandActionTitle, Icon } from '../../../../platform/action/common/action.js';
30
import { getActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
31
import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
32
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
33
import { ContextKeyExpr, ContextKeyExpression, ContextKeyValue, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
34
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
35
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
36
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
37
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
38
import { ILabelService } from '../../../../platform/label/common/label.js';
39
import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js';
40
import { INotificationService } from '../../../../platform/notification/common/notification.js';
41
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
42
import { asCssVariable, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js';
43
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
44
import { ViewAction, ViewPane } from '../../../browser/parts/views/viewPane.js';
45
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
46
import { IViewDescriptorService } from '../../../common/views.js';
47
import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_FOCUSED, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, getStateLabel, IDebugModel, IDebugService, IDebugSession, IRawStoppedDetails, isFrameDeemphasized, IStackFrame, IThread, State } from '../common/debug.js';
48
import { StackFrame, Thread, ThreadAndSessionIds } from '../common/debugModel.js';
49
import { isSessionAttach } from '../common/debugUtils.js';
50
import { renderViewTree } from './baseDebugView.js';
51
import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from './debugCommands.js';
52
import * as icons from './debugIcons.js';
53
import { createDisconnectMenuItemAction } from './debugToolBar.js';
54
55
const $ = dom.$;
56
57
type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[];
58
59
interface ICallStackItemContext {
60
sessionId: string;
61
threadId?: string;
62
frameId?: string;
63
frameName?: string;
64
frameLocation?: { range: IRange; source: DebugProtocol.Source };
65
}
66
67
function getSessionContext(element: IDebugSession): ICallStackItemContext {
68
return {
69
sessionId: element.getId()
70
};
71
}
72
73
function getThreadContext(element: IThread): ICallStackItemContext {
74
return {
75
...getSessionContext(element.session),
76
threadId: element.getId()
77
};
78
}
79
80
function getStackFrameContext(element: StackFrame): ICallStackItemContext {
81
return {
82
...getThreadContext(element.thread),
83
frameId: element.getId(),
84
frameName: element.name,
85
frameLocation: { range: element.range, source: element.source.raw }
86
};
87
}
88
89
export function getContext(element: CallStackItem | null): ICallStackItemContext | undefined {
90
if (element instanceof StackFrame) {
91
return getStackFrameContext(element);
92
} else if (element instanceof Thread) {
93
return getThreadContext(element);
94
} else if (isDebugSession(element)) {
95
return getSessionContext(element);
96
} else {
97
return undefined;
98
}
99
}
100
101
// Extensions depend on this context, should not be changed even though it is not fully deterministic
102
export function getContextForContributedActions(element: CallStackItem | null): string | number {
103
if (element instanceof StackFrame) {
104
if (element.source.inMemory) {
105
return element.source.raw.path || element.source.reference || element.source.name;
106
}
107
108
return element.source.uri.toString();
109
}
110
if (element instanceof Thread) {
111
return element.threadId;
112
}
113
if (isDebugSession(element)) {
114
return element.getId();
115
}
116
117
return '';
118
}
119
120
export function getSpecificSourceName(stackFrame: IStackFrame): string {
121
// To reduce flashing of the path name and the way we fetch stack frames
122
// We need to compute the source name based on the other frames in the stale call stack
123
let callStack = (<Thread>stackFrame.thread).getStaleCallStack();
124
callStack = callStack.length > 0 ? callStack : stackFrame.thread.getCallStack();
125
const otherSources = callStack.map(sf => sf.source).filter(s => s !== stackFrame.source);
126
let suffixLength = 0;
127
otherSources.forEach(s => {
128
if (s.name === stackFrame.source.name) {
129
suffixLength = Math.max(suffixLength, commonSuffixLength(stackFrame.source.uri.path, s.uri.path));
130
}
131
});
132
if (suffixLength === 0) {
133
return stackFrame.source.name;
134
}
135
136
const from = Math.max(0, stackFrame.source.uri.path.lastIndexOf(posix.sep, stackFrame.source.uri.path.length - suffixLength - 1));
137
return (from > 0 ? '...' : '') + stackFrame.source.uri.path.substring(from);
138
}
139
140
async function expandTo(session: IDebugSession, tree: WorkbenchCompressibleAsyncDataTree<IDebugModel, CallStackItem, FuzzyScore>): Promise<void> {
141
if (session.parentSession) {
142
await expandTo(session.parentSession, tree);
143
}
144
await tree.expand(session);
145
}
146
147
export class CallStackView extends ViewPane {
148
private stateMessage!: HTMLSpanElement;
149
private stateMessageLabel!: HTMLSpanElement;
150
private stateMessageLabelHover!: IManagedHover;
151
private onCallStackChangeScheduler: RunOnceScheduler;
152
private needsRefresh = false;
153
private ignoreSelectionChangedEvent = false;
154
private ignoreFocusStackFrameEvent = false;
155
156
private dataSource!: CallStackDataSource;
157
private tree!: WorkbenchCompressibleAsyncDataTree<IDebugModel, CallStackItem, FuzzyScore>;
158
private autoExpandedSessions = new Set<IDebugSession>();
159
private selectionNeedsUpdate = false;
160
161
constructor(
162
private options: IViewletViewOptions,
163
@IContextMenuService contextMenuService: IContextMenuService,
164
@IDebugService private readonly debugService: IDebugService,
165
@IKeybindingService keybindingService: IKeybindingService,
166
@IInstantiationService instantiationService: IInstantiationService,
167
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
168
@IConfigurationService configurationService: IConfigurationService,
169
@IContextKeyService contextKeyService: IContextKeyService,
170
@IOpenerService openerService: IOpenerService,
171
@IThemeService themeService: IThemeService,
172
@IHoverService hoverService: IHoverService,
173
@IMenuService private readonly menuService: IMenuService,
174
) {
175
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
176
177
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
178
this.onCallStackChangeScheduler = this._register(new RunOnceScheduler(async () => {
179
// Only show the global pause message if we do not display threads.
180
// Otherwise there will be a pause message per thread and there is no need for a global one.
181
const sessions = this.debugService.getModel().getSessions();
182
if (sessions.length === 0) {
183
this.autoExpandedSessions.clear();
184
}
185
186
const thread = sessions.length === 1 && sessions[0].getAllThreads().length === 1 ? sessions[0].getAllThreads()[0] : undefined;
187
const stoppedDetails = sessions.length === 1 ? sessions[0].getStoppedDetails() : undefined;
188
if (stoppedDetails && (thread || typeof stoppedDetails.threadId !== 'number')) {
189
this.stateMessageLabel.textContent = stoppedDescription(stoppedDetails);
190
this.stateMessageLabelHover.update(stoppedText(stoppedDetails));
191
this.stateMessageLabel.classList.toggle('exception', stoppedDetails.reason === 'exception');
192
this.stateMessage.hidden = false;
193
} else if (sessions.length === 1 && sessions[0].state === State.Running) {
194
this.stateMessageLabel.textContent = localize({ key: 'running', comment: ['indicates state'] }, "Running");
195
this.stateMessageLabelHover.update(sessions[0].getLabel());
196
this.stateMessageLabel.classList.remove('exception');
197
this.stateMessage.hidden = false;
198
} else {
199
this.stateMessage.hidden = true;
200
}
201
this.updateActions();
202
203
this.needsRefresh = false;
204
await this.tree.updateChildren();
205
try {
206
const toExpand = new Set<IDebugSession>();
207
sessions.forEach(s => {
208
// Automatically expand sessions that have children, but only do this once.
209
if (s.parentSession && !this.autoExpandedSessions.has(s.parentSession)) {
210
toExpand.add(s.parentSession);
211
}
212
});
213
for (const session of toExpand) {
214
await expandTo(session, this.tree);
215
this.autoExpandedSessions.add(session);
216
}
217
} catch (e) {
218
// Ignore tree expand errors if element no longer present
219
}
220
if (this.selectionNeedsUpdate) {
221
this.selectionNeedsUpdate = false;
222
await this.updateTreeSelection();
223
}
224
}, 50));
225
}
226
227
protected override renderHeaderTitle(container: HTMLElement): void {
228
super.renderHeaderTitle(container, this.options.title);
229
230
this.stateMessage = dom.append(container, $('span.call-stack-state-message'));
231
this.stateMessage.hidden = true;
232
this.stateMessageLabel = dom.append(this.stateMessage, $('span.label'));
233
this.stateMessageLabelHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.stateMessage, ''));
234
}
235
236
protected override renderBody(container: HTMLElement): void {
237
super.renderBody(container);
238
this.element.classList.add('debug-pane');
239
container.classList.add('debug-call-stack');
240
const treeContainer = renderViewTree(container);
241
242
this.dataSource = new CallStackDataSource(this.debugService);
243
this.tree = this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree<IDebugModel, CallStackItem, FuzzyScore>, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(this.debugService), [
244
this.instantiationService.createInstance(SessionsRenderer),
245
this.instantiationService.createInstance(ThreadsRenderer),
246
this.instantiationService.createInstance(StackFramesRenderer),
247
this.instantiationService.createInstance(ErrorsRenderer),
248
new LoadMoreRenderer(),
249
new ShowMoreRenderer()
250
], this.dataSource, {
251
accessibilityProvider: new CallStackAccessibilityProvider(),
252
compressionEnabled: true,
253
autoExpandSingleChildren: true,
254
identityProvider: {
255
getId: (element: CallStackItem) => {
256
if (typeof element === 'string') {
257
return element;
258
}
259
if (element instanceof Array) {
260
return `showMore ${element[0].getId()}`;
261
}
262
263
return element.getId();
264
}
265
},
266
keyboardNavigationLabelProvider: {
267
getKeyboardNavigationLabel: (e: CallStackItem) => {
268
if (isDebugSession(e)) {
269
return e.getLabel();
270
}
271
if (e instanceof Thread) {
272
return `${e.name} ${e.stateLabel}`;
273
}
274
if (e instanceof StackFrame || typeof e === 'string') {
275
return e;
276
}
277
if (e instanceof ThreadAndSessionIds) {
278
return LoadMoreRenderer.LABEL;
279
}
280
281
return localize('showMoreStackFrames2', "Show More Stack Frames");
282
},
283
getCompressedNodeKeyboardNavigationLabel: (e: CallStackItem[]) => {
284
const firstItem = e[0];
285
if (isDebugSession(firstItem)) {
286
return firstItem.getLabel();
287
}
288
return '';
289
}
290
},
291
expandOnlyOnTwistieClick: true,
292
overrideStyles: this.getLocationBasedColors().listOverrideStyles
293
});
294
295
CONTEXT_CALLSTACK_FOCUSED.bindTo(this.tree.contextKeyService);
296
297
this.tree.setInput(this.debugService.getModel());
298
this._register(this.tree);
299
this._register(this.tree.onDidOpen(async e => {
300
if (this.ignoreSelectionChangedEvent) {
301
return;
302
}
303
304
const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession, options: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean } = {}) => {
305
this.ignoreFocusStackFrameEvent = true;
306
try {
307
this.debugService.focusStackFrame(stackFrame, thread, session, { ...options, ...{ explicit: true } });
308
} finally {
309
this.ignoreFocusStackFrameEvent = false;
310
}
311
};
312
313
const element = e.element;
314
if (element instanceof StackFrame) {
315
const opts = {
316
preserveFocus: e.editorOptions.preserveFocus,
317
sideBySide: e.sideBySide,
318
pinned: e.editorOptions.pinned
319
};
320
focusStackFrame(element, element.thread, element.thread.session, opts);
321
}
322
if (element instanceof Thread) {
323
focusStackFrame(undefined, element, element.session);
324
}
325
if (isDebugSession(element)) {
326
focusStackFrame(undefined, undefined, element);
327
}
328
if (element instanceof ThreadAndSessionIds) {
329
const session = this.debugService.getModel().getSession(element.sessionId);
330
const thread = session && session.getThread(element.threadId);
331
if (thread) {
332
const totalFrames = thread.stoppedDetails?.totalFrames;
333
const remainingFramesCount = typeof totalFrames === 'number' ? (totalFrames - thread.getCallStack().length) : undefined;
334
// Get all the remaining frames
335
await (<Thread>thread).fetchCallStack(remainingFramesCount);
336
await this.tree.updateChildren();
337
}
338
}
339
if (element instanceof Array) {
340
element.forEach(sf => this.dataSource.deemphasizedStackFramesToShow.add(sf));
341
this.tree.updateChildren();
342
}
343
}));
344
345
this._register(this.debugService.getModel().onDidChangeCallStack(() => {
346
if (!this.isBodyVisible()) {
347
this.needsRefresh = true;
348
return;
349
}
350
351
if (!this.onCallStackChangeScheduler.isScheduled()) {
352
this.onCallStackChangeScheduler.schedule();
353
}
354
}));
355
const onFocusChange = Event.any<unknown>(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession);
356
this._register(onFocusChange(async () => {
357
if (this.ignoreFocusStackFrameEvent) {
358
return;
359
}
360
if (!this.isBodyVisible()) {
361
this.needsRefresh = true;
362
this.selectionNeedsUpdate = true;
363
return;
364
}
365
if (this.onCallStackChangeScheduler.isScheduled()) {
366
this.selectionNeedsUpdate = true;
367
return;
368
}
369
370
await this.updateTreeSelection();
371
}));
372
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
373
374
// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
375
if (this.debugService.state === State.Stopped) {
376
this.onCallStackChangeScheduler.schedule(0);
377
}
378
379
this._register(this.onDidChangeBodyVisibility(visible => {
380
if (visible && this.needsRefresh) {
381
this.onCallStackChangeScheduler.schedule();
382
}
383
}));
384
385
this._register(this.debugService.onDidNewSession(s => {
386
const sessionListeners: IDisposable[] = [];
387
sessionListeners.push(s.onDidChangeName(() => {
388
// this.tree.updateChildren is called on a delay after a session is added,
389
// so don't rerender if the tree doesn't have the node yet
390
if (this.tree.hasNode(s)) {
391
this.tree.rerender(s);
392
}
393
}));
394
sessionListeners.push(s.onDidEndAdapter(() => dispose(sessionListeners)));
395
if (s.parentSession) {
396
// A session we already expanded has a new child session, allow to expand it again.
397
this.autoExpandedSessions.delete(s.parentSession);
398
}
399
}));
400
}
401
402
protected override layoutBody(height: number, width: number): void {
403
super.layoutBody(height, width);
404
this.tree.layout(height, width);
405
}
406
407
override focus(): void {
408
super.focus();
409
this.tree.domFocus();
410
}
411
412
collapseAll(): void {
413
this.tree.collapseAll();
414
}
415
416
private async updateTreeSelection(): Promise<void> {
417
if (!this.tree || !this.tree.getInput()) {
418
// Tree not initialized yet
419
return;
420
}
421
422
const updateSelectionAndReveal = (element: IStackFrame | IDebugSession) => {
423
this.ignoreSelectionChangedEvent = true;
424
try {
425
this.tree.setSelection([element]);
426
// If the element is outside of the screen bounds,
427
// position it in the middle
428
if (this.tree.getRelativeTop(element) === null) {
429
this.tree.reveal(element, 0.5);
430
} else {
431
this.tree.reveal(element);
432
}
433
} catch (e) { }
434
finally {
435
this.ignoreSelectionChangedEvent = false;
436
}
437
};
438
439
const thread = this.debugService.getViewModel().focusedThread;
440
const session = this.debugService.getViewModel().focusedSession;
441
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
442
if (!thread) {
443
if (!session) {
444
this.tree.setSelection([]);
445
} else {
446
updateSelectionAndReveal(session);
447
}
448
} else {
449
// Ignore errors from this expansions because we are not aware if we rendered the threads and sessions or we hide them to declutter the view
450
try {
451
await expandTo(thread.session, this.tree);
452
} catch (e) { }
453
try {
454
await this.tree.expand(thread);
455
} catch (e) { }
456
457
const toReveal = stackFrame || session;
458
if (toReveal) {
459
updateSelectionAndReveal(toReveal);
460
}
461
}
462
}
463
464
private onContextMenu(e: ITreeContextMenuEvent<CallStackItem>): void {
465
const element = e.element;
466
let overlay: [string, ContextKeyValue][] = [];
467
if (isDebugSession(element)) {
468
overlay = getSessionContextOverlay(element);
469
} else if (element instanceof Thread) {
470
overlay = getThreadContextOverlay(element);
471
} else if (element instanceof StackFrame) {
472
overlay = getStackFrameContextOverlay(element);
473
}
474
475
const contextKeyService = this.contextKeyService.createOverlay(overlay);
476
const menu = this.menuService.getMenuActions(MenuId.DebugCallStackContext, contextKeyService, { arg: getContextForContributedActions(element), shouldForwardArgs: true });
477
const result = getContextMenuActions(menu, 'inline');
478
this.contextMenuService.showContextMenu({
479
getAnchor: () => e.anchor,
480
getActions: () => result.secondary,
481
getActionsContext: () => getContext(element)
482
});
483
}
484
}
485
486
interface IThreadTemplateData {
487
thread: HTMLElement;
488
name: HTMLElement;
489
stateLabel: HTMLSpanElement;
490
label: HighlightedLabel;
491
actionBar: ActionBar;
492
elementDisposable: DisposableStore;
493
templateDisposable: IDisposable;
494
}
495
496
interface ISessionTemplateData {
497
session: HTMLElement;
498
name: HTMLElement;
499
stateLabel: HTMLSpanElement;
500
label: HighlightedLabel;
501
actionBar: ActionBar;
502
elementDisposable: DisposableStore;
503
templateDisposable: IDisposable;
504
}
505
506
interface IErrorTemplateData {
507
label: HTMLElement;
508
templateDisposable: DisposableStore;
509
}
510
511
interface ILabelTemplateData {
512
label: HTMLElement;
513
}
514
515
interface IStackFrameTemplateData {
516
stackFrame: HTMLElement;
517
file: HTMLElement;
518
fileName: HTMLElement;
519
lineNumber: HTMLElement;
520
label: HighlightedLabel;
521
actionBar: ActionBar;
522
templateDisposable: DisposableStore;
523
elementDisposables: DisposableStore;
524
}
525
526
function getSessionContextOverlay(session: IDebugSession): [string, ContextKeyValue][] {
527
return [
528
[CONTEXT_CALLSTACK_ITEM_TYPE.key, 'session'],
529
[CONTEXT_CALLSTACK_SESSION_IS_ATTACH.key, isSessionAttach(session)],
530
[CONTEXT_CALLSTACK_ITEM_STOPPED.key, session.state === State.Stopped],
531
[CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD.key, session.getAllThreads().length === 1],
532
];
533
}
534
535
class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, FuzzyScore, ISessionTemplateData> {
536
static readonly ID = 'session';
537
538
constructor(
539
@IInstantiationService private readonly instantiationService: IInstantiationService,
540
@IContextKeyService private readonly contextKeyService: IContextKeyService,
541
@IHoverService private readonly hoverService: IHoverService,
542
@IMenuService private readonly menuService: IMenuService,
543
) { }
544
545
get templateId(): string {
546
return SessionsRenderer.ID;
547
}
548
549
renderTemplate(container: HTMLElement): ISessionTemplateData {
550
const session = dom.append(container, $('.session'));
551
dom.append(session, $(ThemeIcon.asCSSSelector(icons.callstackViewSession)));
552
const name = dom.append(session, $('.name'));
553
const stateLabel = dom.append(session, $('span.state.label.monaco-count-badge.long'));
554
const templateDisposable = new DisposableStore();
555
const label = templateDisposable.add(new HighlightedLabel(name));
556
557
const stopActionViewItemDisposables = templateDisposable.add(new DisposableStore());
558
const actionBar = templateDisposable.add(new ActionBar(session, {
559
actionViewItemProvider: (action, options) => {
560
if ((action.id === STOP_ID || action.id === DISCONNECT_ID) && action instanceof MenuItemAction) {
561
stopActionViewItemDisposables.clear();
562
const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor, { ...options, menuAsChild: false }));
563
if (item) {
564
return item;
565
}
566
}
567
568
if (action instanceof MenuItemAction) {
569
return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate });
570
} else if (action instanceof SubmenuItemAction) {
571
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate });
572
}
573
574
return undefined;
575
}
576
}));
577
578
const elementDisposable = templateDisposable.add(new DisposableStore());
579
return { session, name, stateLabel, label, actionBar, elementDisposable, templateDisposable };
580
}
581
582
renderElement(element: ITreeNode<IDebugSession, FuzzyScore>, _: number, data: ISessionTemplateData): void {
583
this.doRenderElement(element.element, createMatches(element.filterData), data);
584
}
585
586
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IDebugSession>, FuzzyScore>, _index: number, templateData: ISessionTemplateData): void {
587
const lastElement = node.element.elements[node.element.elements.length - 1];
588
const matches = createMatches(node.filterData);
589
this.doRenderElement(lastElement, matches, templateData);
590
}
591
592
private doRenderElement(session: IDebugSession, matches: IMatch[], data: ISessionTemplateData): void {
593
const sessionHover = data.elementDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.session, localize({ key: 'session', comment: ['Session is a noun'] }, "Session")));
594
data.label.set(session.getLabel(), matches);
595
const stoppedDetails = session.getStoppedDetails();
596
const thread = session.getAllThreads().find(t => t.stopped);
597
598
const contextKeyService = this.contextKeyService.createOverlay(getSessionContextOverlay(session));
599
const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService));
600
601
const setupActionBar = () => {
602
data.actionBar.clear();
603
604
const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(session), shouldForwardArgs: true }), 'inline');
605
data.actionBar.push(primary, { icon: true, label: false });
606
// We need to set our internal context on the action bar, since our commands depend on that one
607
// While the external context our extensions rely on
608
data.actionBar.context = getContext(session);
609
};
610
data.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
611
setupActionBar();
612
613
data.stateLabel.style.display = '';
614
615
if (stoppedDetails) {
616
data.stateLabel.textContent = stoppedDescription(stoppedDetails);
617
sessionHover.update(`${session.getLabel()}: ${stoppedText(stoppedDetails)}`);
618
data.stateLabel.classList.toggle('exception', stoppedDetails.reason === 'exception');
619
} else if (thread && thread.stoppedDetails) {
620
data.stateLabel.textContent = stoppedDescription(thread.stoppedDetails);
621
sessionHover.update(`${session.getLabel()}: ${stoppedText(thread.stoppedDetails)}`);
622
data.stateLabel.classList.toggle('exception', thread.stoppedDetails.reason === 'exception');
623
} else {
624
data.stateLabel.textContent = localize({ key: 'running', comment: ['indicates state'] }, "Running");
625
data.stateLabel.classList.remove('exception');
626
}
627
}
628
629
disposeTemplate(templateData: ISessionTemplateData): void {
630
templateData.templateDisposable.dispose();
631
}
632
633
disposeElement(_element: ITreeNode<IDebugSession, FuzzyScore>, _: number, templateData: ISessionTemplateData): void {
634
templateData.elementDisposable.clear();
635
}
636
637
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IDebugSession>, FuzzyScore>, index: number, templateData: ISessionTemplateData): void {
638
templateData.elementDisposable.clear();
639
}
640
}
641
642
function getThreadContextOverlay(thread: IThread): [string, ContextKeyValue][] {
643
return [
644
[CONTEXT_CALLSTACK_ITEM_TYPE.key, 'thread'],
645
[CONTEXT_CALLSTACK_ITEM_STOPPED.key, thread.stopped]
646
];
647
}
648
649
class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore, IThreadTemplateData> {
650
static readonly ID = 'thread';
651
652
constructor(
653
@IContextKeyService private readonly contextKeyService: IContextKeyService,
654
@IHoverService private readonly hoverService: IHoverService,
655
@IMenuService private readonly menuService: IMenuService,
656
) { }
657
658
get templateId(): string {
659
return ThreadsRenderer.ID;
660
}
661
662
renderTemplate(container: HTMLElement): IThreadTemplateData {
663
const thread = dom.append(container, $('.thread'));
664
const name = dom.append(thread, $('.name'));
665
const stateLabel = dom.append(thread, $('span.state.label.monaco-count-badge.long'));
666
667
const templateDisposable = new DisposableStore();
668
const label = templateDisposable.add(new HighlightedLabel(name));
669
670
const actionBar = templateDisposable.add(new ActionBar(thread));
671
const elementDisposable = templateDisposable.add(new DisposableStore());
672
673
return { thread, name, stateLabel, label, actionBar, elementDisposable, templateDisposable };
674
}
675
676
renderElement(element: ITreeNode<IThread, FuzzyScore>, _index: number, data: IThreadTemplateData): void {
677
const thread = element.element;
678
data.elementDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name));
679
data.label.set(thread.name, createMatches(element.filterData));
680
data.stateLabel.textContent = thread.stateLabel;
681
data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception');
682
683
const contextKeyService = this.contextKeyService.createOverlay(getThreadContextOverlay(thread));
684
const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService));
685
686
const setupActionBar = () => {
687
data.actionBar.clear();
688
689
const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(thread), shouldForwardArgs: true }), 'inline');
690
data.actionBar.push(primary, { icon: true, label: false });
691
// We need to set our internal context on the action bar, since our commands depend on that one
692
// While the external context our extensions rely on
693
data.actionBar.context = getContext(thread);
694
};
695
data.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
696
setupActionBar();
697
}
698
699
renderCompressedElements(_node: ITreeNode<ICompressedTreeNode<IThread>, FuzzyScore>, _index: number, _templateData: IThreadTemplateData): void {
700
throw new Error('Method not implemented.');
701
}
702
703
disposeElement(_element: ITreeNode<IThread, FuzzyScore>, _index: number, templateData: IThreadTemplateData): void {
704
templateData.elementDisposable.clear();
705
}
706
707
disposeTemplate(templateData: IThreadTemplateData): void {
708
templateData.templateDisposable.dispose();
709
}
710
}
711
712
function getStackFrameContextOverlay(stackFrame: IStackFrame): [string, ContextKeyValue][] {
713
return [
714
[CONTEXT_CALLSTACK_ITEM_TYPE.key, 'stackFrame'],
715
[CONTEXT_STACK_FRAME_SUPPORTS_RESTART.key, stackFrame.canRestart]
716
];
717
}
718
719
class StackFramesRenderer implements ICompressibleTreeRenderer<IStackFrame, FuzzyScore, IStackFrameTemplateData> {
720
static readonly ID = 'stackFrame';
721
722
constructor(
723
@IHoverService private readonly hoverService: IHoverService,
724
@ILabelService private readonly labelService: ILabelService,
725
@INotificationService private readonly notificationService: INotificationService,
726
) { }
727
728
get templateId(): string {
729
return StackFramesRenderer.ID;
730
}
731
732
renderTemplate(container: HTMLElement): IStackFrameTemplateData {
733
const stackFrame = dom.append(container, $('.stack-frame'));
734
const labelDiv = dom.append(stackFrame, $('span.label.expression'));
735
const file = dom.append(stackFrame, $('.file'));
736
const fileName = dom.append(file, $('span.file-name'));
737
const wrapper = dom.append(file, $('span.line-number-wrapper'));
738
const lineNumber = dom.append(wrapper, $('span.line-number.monaco-count-badge'));
739
740
const templateDisposable = new DisposableStore();
741
const elementDisposables = new DisposableStore();
742
templateDisposable.add(elementDisposables);
743
const label = templateDisposable.add(new HighlightedLabel(labelDiv));
744
const actionBar = templateDisposable.add(new ActionBar(stackFrame));
745
746
return { file, fileName, label, lineNumber, stackFrame, actionBar, templateDisposable, elementDisposables };
747
}
748
749
renderElement(element: ITreeNode<IStackFrame, FuzzyScore>, index: number, data: IStackFrameTemplateData): void {
750
const stackFrame = element.element;
751
data.stackFrame.classList.toggle('disabled', !stackFrame.source || !stackFrame.source.available || isFrameDeemphasized(stackFrame));
752
data.stackFrame.classList.toggle('label', stackFrame.presentationHint === 'label');
753
const hasActions = !!stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle' && stackFrame.canRestart;
754
data.stackFrame.classList.toggle('has-actions', hasActions);
755
756
let title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri);
757
if (stackFrame.source.raw.origin) {
758
title += `\n${stackFrame.source.raw.origin}`;
759
}
760
data.elementDisposables.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.file, title));
761
762
data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name);
763
data.fileName.textContent = getSpecificSourceName(stackFrame);
764
if (stackFrame.range.startLineNumber !== undefined) {
765
data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
766
if (stackFrame.range.startColumn) {
767
data.lineNumber.textContent += `:${stackFrame.range.startColumn}`;
768
}
769
data.lineNumber.classList.remove('unavailable');
770
} else {
771
data.lineNumber.classList.add('unavailable');
772
}
773
774
data.actionBar.clear();
775
if (hasActions) {
776
const action = data.elementDisposables.add(new Action('debug.callStack.restartFrame', localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => {
777
try {
778
await stackFrame.restart();
779
} catch (e) {
780
this.notificationService.error(e);
781
}
782
}));
783
data.actionBar.push(action, { icon: true, label: false });
784
}
785
}
786
787
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IStackFrame>, FuzzyScore>, index: number, templateData: IStackFrameTemplateData): void {
788
throw new Error('Method not implemented.');
789
}
790
disposeElement(element: ITreeNode<IStackFrame, FuzzyScore>, index: number, templateData: IStackFrameTemplateData): void {
791
templateData.elementDisposables.clear();
792
}
793
794
disposeTemplate(templateData: IStackFrameTemplateData): void {
795
templateData.templateDisposable.dispose();
796
}
797
}
798
799
class ErrorsRenderer implements ICompressibleTreeRenderer<string, FuzzyScore, IErrorTemplateData> {
800
static readonly ID = 'error';
801
802
get templateId(): string {
803
return ErrorsRenderer.ID;
804
}
805
806
constructor(
807
@IHoverService private readonly hoverService: IHoverService
808
) {
809
}
810
811
renderTemplate(container: HTMLElement): IErrorTemplateData {
812
const label = dom.append(container, $('.error'));
813
814
return { label, templateDisposable: new DisposableStore() };
815
}
816
817
renderElement(element: ITreeNode<string, FuzzyScore>, index: number, data: IErrorTemplateData): void {
818
const error = element.element;
819
data.label.textContent = error;
820
data.templateDisposable.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), data.label, error));
821
}
822
823
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<string>, FuzzyScore>, index: number, templateData: IErrorTemplateData): void {
824
throw new Error('Method not implemented.');
825
}
826
827
disposeTemplate(templateData: IErrorTemplateData): void {
828
// noop
829
}
830
}
831
832
class LoadMoreRenderer implements ICompressibleTreeRenderer<ThreadAndSessionIds, FuzzyScore, ILabelTemplateData> {
833
static readonly ID = 'loadMore';
834
static readonly LABEL = localize('loadAllStackFrames', "Load More Stack Frames");
835
836
constructor() { }
837
838
get templateId(): string {
839
return LoadMoreRenderer.ID;
840
}
841
842
renderTemplate(container: HTMLElement): ILabelTemplateData {
843
const label = dom.append(container, $('.load-all'));
844
label.style.color = asCssVariable(textLinkForeground);
845
return { label };
846
}
847
848
renderElement(element: ITreeNode<ThreadAndSessionIds, FuzzyScore>, index: number, data: ILabelTemplateData): void {
849
data.label.textContent = LoadMoreRenderer.LABEL;
850
}
851
852
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ThreadAndSessionIds>, FuzzyScore>, index: number, templateData: ILabelTemplateData): void {
853
throw new Error('Method not implemented.');
854
}
855
856
disposeTemplate(templateData: ILabelTemplateData): void {
857
// noop
858
}
859
}
860
861
class ShowMoreRenderer implements ICompressibleTreeRenderer<IStackFrame[], FuzzyScore, ILabelTemplateData> {
862
static readonly ID = 'showMore';
863
864
constructor() { }
865
866
867
get templateId(): string {
868
return ShowMoreRenderer.ID;
869
}
870
871
renderTemplate(container: HTMLElement): ILabelTemplateData {
872
const label = dom.append(container, $('.show-more'));
873
label.style.color = asCssVariable(textLinkForeground);
874
return { label };
875
}
876
877
renderElement(element: ITreeNode<IStackFrame[], FuzzyScore>, index: number, data: ILabelTemplateData): void {
878
const stackFrames = element.element;
879
if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) {
880
data.label.textContent = localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin);
881
} else {
882
data.label.textContent = localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length);
883
}
884
}
885
886
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IStackFrame[]>, FuzzyScore>, index: number, templateData: ILabelTemplateData): void {
887
throw new Error('Method not implemented.');
888
}
889
890
disposeTemplate(templateData: ILabelTemplateData): void {
891
// noop
892
}
893
}
894
895
class CallStackDelegate implements IListVirtualDelegate<CallStackItem> {
896
897
getHeight(element: CallStackItem): number {
898
if (element instanceof StackFrame && element.presentationHint === 'label') {
899
return 16;
900
}
901
if (element instanceof ThreadAndSessionIds || element instanceof Array) {
902
return 16;
903
}
904
905
return 22;
906
}
907
908
getTemplateId(element: CallStackItem): string {
909
if (isDebugSession(element)) {
910
return SessionsRenderer.ID;
911
}
912
if (element instanceof Thread) {
913
return ThreadsRenderer.ID;
914
}
915
if (element instanceof StackFrame) {
916
return StackFramesRenderer.ID;
917
}
918
if (typeof element === 'string') {
919
return ErrorsRenderer.ID;
920
}
921
if (element instanceof ThreadAndSessionIds) {
922
return LoadMoreRenderer.ID;
923
}
924
925
// element instanceof Array
926
return ShowMoreRenderer.ID;
927
}
928
}
929
930
function stoppedText(stoppedDetails: IRawStoppedDetails): string {
931
return stoppedDetails.text ?? stoppedDescription(stoppedDetails);
932
}
933
934
function stoppedDescription(stoppedDetails: IRawStoppedDetails): string {
935
return stoppedDetails.description ||
936
(stoppedDetails.reason ? localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", stoppedDetails.reason) : localize('paused', "Paused"));
937
}
938
939
function isDebugModel(obj: unknown): obj is IDebugModel {
940
return !!obj && typeof (obj as IDebugModel).getSessions === 'function';
941
}
942
943
function isDebugSession(obj: unknown): obj is IDebugSession {
944
return !!obj && typeof (obj as IDebugSession).getAllThreads === 'function';
945
}
946
947
class CallStackDataSource implements IAsyncDataSource<IDebugModel, CallStackItem> {
948
deemphasizedStackFramesToShow = new WeakSet<IStackFrame>();
949
950
constructor(private debugService: IDebugService) { }
951
952
hasChildren(element: IDebugModel | CallStackItem): boolean {
953
if (isDebugSession(element)) {
954
const threads = element.getAllThreads();
955
return (threads.length > 1) || (threads.length === 1 && threads[0].stopped) || !!(this.debugService.getModel().getSessions().find(s => s.parentSession === element));
956
}
957
958
return isDebugModel(element) || (element instanceof Thread && element.stopped);
959
}
960
961
async getChildren(element: IDebugModel | CallStackItem): Promise<CallStackItem[]> {
962
if (isDebugModel(element)) {
963
const sessions = element.getSessions();
964
if (sessions.length === 0) {
965
return Promise.resolve([]);
966
}
967
if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) {
968
return Promise.resolve(sessions.filter(s => !s.parentSession));
969
}
970
971
const threads = sessions[0].getAllThreads();
972
// Only show the threads in the call stack if there is more than 1 thread.
973
return threads.length === 1 ? this.getThreadChildren(<Thread>threads[0]) : Promise.resolve(threads);
974
} else if (isDebugSession(element)) {
975
const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element);
976
const threads: CallStackItem[] = element.getAllThreads();
977
if (threads.length === 1) {
978
// Do not show thread when there is only one to be compact.
979
const children = await this.getThreadChildren(<Thread>threads[0]);
980
return children.concat(childSessions);
981
}
982
983
return Promise.resolve(threads.concat(childSessions));
984
} else {
985
return this.getThreadChildren(<Thread>element);
986
}
987
}
988
989
private getThreadChildren(thread: Thread): Promise<CallStackItem[]> {
990
return this.getThreadCallstack(thread).then(children => {
991
// Check if some stack frames should be hidden under a parent element since they are deemphasized
992
const result: CallStackItem[] = [];
993
children.forEach((child, index) => {
994
if (child instanceof StackFrame && child.source && isFrameDeemphasized(child)) {
995
// Check if the user clicked to show the deemphasized source
996
if (!this.deemphasizedStackFramesToShow.has(child)) {
997
if (result.length) {
998
const last = result[result.length - 1];
999
if (last instanceof Array) {
1000
// Collect all the stackframes that will be "collapsed"
1001
last.push(child);
1002
return;
1003
}
1004
}
1005
1006
const nextChild = index < children.length - 1 ? children[index + 1] : undefined;
1007
if (nextChild instanceof StackFrame && nextChild.source && isFrameDeemphasized(nextChild)) {
1008
// Start collecting stackframes that will be "collapsed"
1009
result.push([child]);
1010
return;
1011
}
1012
}
1013
}
1014
1015
result.push(child);
1016
});
1017
1018
return result;
1019
});
1020
}
1021
1022
private async getThreadCallstack(thread: Thread): Promise<Array<IStackFrame | string | ThreadAndSessionIds>> {
1023
let callStack: Array<IStackFrame | string | ThreadAndSessionIds> = thread.getCallStack();
1024
if (!callStack || !callStack.length) {
1025
await thread.fetchCallStack();
1026
callStack = thread.getCallStack();
1027
}
1028
1029
if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > 1) {
1030
// To reduce flashing of the call stack view simply append the stale call stack
1031
// once we have the correct data the tree will refresh and we will no longer display it.
1032
callStack = callStack.concat(thread.getStaleCallStack().slice(1));
1033
}
1034
1035
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
1036
callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]);
1037
}
1038
if (!thread.reachedEndOfCallStack && thread.stoppedDetails) {
1039
callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]);
1040
}
1041
1042
return callStack;
1043
}
1044
}
1045
1046
class CallStackAccessibilityProvider implements IListAccessibilityProvider<CallStackItem> {
1047
1048
getWidgetAriaLabel(): string {
1049
return localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack");
1050
}
1051
1052
getWidgetRole(): AriaRole {
1053
// Use treegrid as a role since each element can have additional actions inside #146210
1054
return 'treegrid';
1055
}
1056
1057
getRole(_element: CallStackItem): AriaRole | undefined {
1058
return 'row';
1059
}
1060
1061
getAriaLabel(element: CallStackItem): string {
1062
if (element instanceof Thread) {
1063
return localize({ key: 'threadAriaLabel', comment: ['Placeholders stand for the thread name and the thread state.For example "Thread 1" and "Stopped'] }, "Thread {0} {1}", element.name, element.stateLabel);
1064
}
1065
if (element instanceof StackFrame) {
1066
return localize('stackFrameAriaLabel', "Stack Frame {0}, line {1}, {2}", element.name, element.range.startLineNumber, getSpecificSourceName(element));
1067
}
1068
if (isDebugSession(element)) {
1069
const thread = element.getAllThreads().find(t => t.stopped);
1070
const state = thread ? thread.stateLabel : localize({ key: 'running', comment: ['indicates state'] }, "Running");
1071
return localize({ key: 'sessionLabel', comment: ['Placeholders stand for the session name and the session state. For example "Launch Program" and "Running"'] }, "Session {0} {1}", element.getLabel(), state);
1072
}
1073
if (typeof element === 'string') {
1074
return element;
1075
}
1076
if (element instanceof Array) {
1077
return localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length);
1078
}
1079
1080
// element instanceof ThreadAndSessionIds
1081
return LoadMoreRenderer.LABEL;
1082
}
1083
}
1084
1085
class CallStackCompressionDelegate implements ITreeCompressionDelegate<CallStackItem> {
1086
1087
constructor(private readonly debugService: IDebugService) { }
1088
1089
isIncompressible(stat: CallStackItem): boolean {
1090
if (isDebugSession(stat)) {
1091
if (stat.compact) {
1092
return false;
1093
}
1094
const sessions = this.debugService.getModel().getSessions();
1095
if (sessions.some(s => s.parentSession === stat && s.compact)) {
1096
return false;
1097
}
1098
1099
return true;
1100
}
1101
1102
return true;
1103
}
1104
}
1105
1106
registerAction2(class Collapse extends ViewAction<CallStackView> {
1107
constructor() {
1108
super({
1109
id: 'callStack.collapse',
1110
viewId: CALLSTACK_VIEW_ID,
1111
title: localize('collapse', "Collapse All"),
1112
f1: false,
1113
icon: Codicon.collapseAll,
1114
precondition: CONTEXT_DEBUG_STATE.isEqualTo(getStateLabel(State.Stopped)),
1115
menu: {
1116
id: MenuId.ViewTitle,
1117
order: 10,
1118
group: 'navigation',
1119
when: ContextKeyExpr.equals('view', CALLSTACK_VIEW_ID)
1120
}
1121
});
1122
}
1123
1124
runInView(_accessor: ServicesAccessor, view: CallStackView) {
1125
view.collapseAll();
1126
}
1127
});
1128
1129
function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void {
1130
MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, {
1131
group: 'inline',
1132
order,
1133
when,
1134
command: { id, title, icon, precondition }
1135
});
1136
}
1137
1138
const threadOrSessionWithOneThread = ContextKeyExpr.or(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD))!;
1139
registerCallStackInlineMenuItem(PAUSE_ID, PAUSE_LABEL, icons.debugPause, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED.toNegated())!, 10, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG.toNegated());
1140
registerCallStackInlineMenuItem(CONTINUE_ID, CONTINUE_LABEL, icons.debugContinue, ContextKeyExpr.and(threadOrSessionWithOneThread, CONTEXT_CALLSTACK_ITEM_STOPPED)!, 10);
1141
registerCallStackInlineMenuItem(STEP_OVER_ID, STEP_OVER_LABEL, icons.debugStepOver, threadOrSessionWithOneThread, 20, CONTEXT_CALLSTACK_ITEM_STOPPED);
1142
registerCallStackInlineMenuItem(STEP_INTO_ID, STEP_INTO_LABEL, icons.debugStepInto, threadOrSessionWithOneThread, 30, CONTEXT_CALLSTACK_ITEM_STOPPED);
1143
registerCallStackInlineMenuItem(STEP_OUT_ID, STEP_OUT_LABEL, icons.debugStepOut, threadOrSessionWithOneThread, 40, CONTEXT_CALLSTACK_ITEM_STOPPED);
1144
registerCallStackInlineMenuItem(RESTART_SESSION_ID, RESTART_LABEL, icons.debugRestart, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), 50);
1145
registerCallStackInlineMenuItem(STOP_ID, STOP_LABEL, icons.debugStop, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH.toNegated(), CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60);
1146
registerCallStackInlineMenuItem(DISCONNECT_ID, DISCONNECT_LABEL, icons.debugDisconnect, ContextKeyExpr.and(CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'))!, 60);
1147
1148