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