callStackView.ts 25.6 KB
Newer Older
I
isidor 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
I
isidor 已提交
7
import { RunOnceScheduler, ignoreErrors, sequence } from 'vs/base/common/async';
I
isidor 已提交
8
import * as dom from 'vs/base/browser/dom';
I
isidor 已提交
9
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
10 11
import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/contrib/debug/common/debug';
import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel';
I
isidor 已提交
12 13
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
I
isidor 已提交
14
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
I
isidor 已提交
15
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
16
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
I
isidor 已提交
17
import { IAction } from 'vs/base/common/actions';
18
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
19
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
20
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
I
isidor 已提交
21
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
I
isidor 已提交
22
import { ILabelService } from 'vs/platform/label/common/label';
I
isidor 已提交
23
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
24
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
I
isidor 已提交
25
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
J
Joao Moreno 已提交
26
import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
27
import { TreeResourceNavigator2, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
I
isidor 已提交
28
import { onUnexpectedError } from 'vs/base/common/errors';
I
isidor 已提交
29 30
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
I
isidor 已提交
31
import { Event } from 'vs/base/common/event';
32
import { dispose } from 'vs/base/common/lifecycle';
I
isidor 已提交
33

34
const $ = dom.$;
I
isidor 已提交
35

I
isidor 已提交
36 37 38
type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[];

export class CallStackView extends ViewletPanel {
I
isidor 已提交
39

I
isidor 已提交
40 41
	private pauseMessage!: HTMLSpanElement;
	private pauseMessageLabel!: HTMLSpanElement;
I
isidor 已提交
42
	private onCallStackChangeScheduler: RunOnceScheduler;
I
isidor 已提交
43 44 45
	private needsRefresh = false;
	private ignoreSelectionChangedEvent = false;
	private ignoreFocusStackFrameEvent = false;
46
	private callStackItemType: IContextKey<string>;
I
isidor 已提交
47 48
	private dataSource!: CallStackDataSource;
	private tree!: WorkbenchAsyncDataTree<CallStackItem | IDebugModel, CallStackItem, FuzzyScore>;
I
isidor 已提交
49
	private contributedContextMenu: IMenu;
I
isidor 已提交
50
	private parentSessionToExpand = new Set<IDebugSession>();
I
isidor 已提交
51 52 53 54

	constructor(
		private options: IViewletViewOptions,
		@IContextMenuService contextMenuService: IContextMenuService,
55
		@IDebugService private readonly debugService: IDebugService,
I
isidor 已提交
56
		@IKeybindingService keybindingService: IKeybindingService,
57 58
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IEditorService private readonly editorService: IEditorService,
59
		@IConfigurationService configurationService: IConfigurationService,
I
isidor 已提交
60
		@IMenuService menuService: IMenuService,
61
		@IContextKeyService readonly contextKeyService: IContextKeyService,
I
isidor 已提交
62
	) {
S
Sandeep Somavarapu 已提交
63
		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
64
		this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
I
isidor 已提交
65

I
isidor 已提交
66
		this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService);
M
Matt Bierner 已提交
67
		this._register(this.contributedContextMenu);
I
isidor 已提交
68

I
isidor 已提交
69 70 71 72
		// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
		this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
			// Only show the global pause message if we do not display threads.
			// Otherwise there will be a pause message per thread and there is no need for a global one.
I
isidor 已提交
73
			const sessions = this.debugService.getModel().getSessions();
I
isidor 已提交
74 75
			const thread = sessions.length === 1 && sessions[0].getAllThreads().length === 1 ? sessions[0].getAllThreads()[0] : undefined;
			if (thread && thread.stoppedDetails) {
I
isidor 已提交
76 77
				this.pauseMessageLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || '');
				this.pauseMessageLabel.title = thread.stoppedDetails.text || '';
I
isidor 已提交
78
				dom.toggleClass(this.pauseMessageLabel, 'exception', thread.stoppedDetails.reason === 'exception');
79
				this.pauseMessage.hidden = false;
I
isidor 已提交
80
			} else {
81
				this.pauseMessage.hidden = true;
I
isidor 已提交
82 83
			}

84
			this.needsRefresh = false;
I
isidor 已提交
85
			this.dataSource.deemphasizedStackFramesToShow = [];
I
isidor 已提交
86 87 88 89 90
			this.tree.updateChildren().then(() => {
				this.parentSessionToExpand.forEach(s => this.tree.expand(s));
				this.parentSessionToExpand.clear();
				this.updateTreeSelection();
			});
I
isidor 已提交
91 92 93
		}, 50);
	}

J
Jackson Kearl 已提交
94
	protected renderHeaderTitle(container: HTMLElement): void {
I
isidor 已提交
95
		const titleContainer = dom.append(container, $('.debug-call-stack-title'));
J
Jackson Kearl 已提交
96 97 98
		super.renderHeaderTitle(titleContainer, this.options.title);

		this.pauseMessage = dom.append(titleContainer, $('span.pause-message'));
99 100
		this.pauseMessage.hidden = true;
		this.pauseMessageLabel = dom.append(this.pauseMessage, $('span.label'));
I
isidor 已提交
101 102
	}

I
isidor 已提交
103
	renderBody(container: HTMLElement): void {
I
isidor 已提交
104
		dom.addClass(container, 'debug-call-stack');
I
isidor 已提交
105 106
		const treeContainer = renderViewTree(container);

I
isidor 已提交
107
		this.dataSource = new CallStackDataSource(this.debugService);
108
		this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new CallStackDelegate(), [
I
isidor 已提交
109 110 111 112 113 114 115 116
			new SessionsRenderer(),
			new ThreadsRenderer(),
			this.instantiationService.createInstance(StackFramesRenderer),
			new ErrorsRenderer(),
			new LoadMoreRenderer(),
			new ShowMoreRenderer()
		], this.dataSource, {
				accessibilityProvider: new CallStackAccessibilityProvider(),
I
isidor 已提交
117
				ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
118
				identityProvider: {
I
isidor 已提交
119
					getId: (element: CallStackItem) => {
120 121 122 123 124 125 126
						if (typeof element === 'string') {
							return element;
						}
						if (element instanceof Array) {
							return `showMore ${element[0].getId()}`;
						}

I
isidor 已提交
127
						return element.getId();
128
					}
I
isidor 已提交
129
				},
J
Joao Moreno 已提交
130
				keyboardNavigationLabelProvider: {
I
isidor 已提交
131
					getKeyboardNavigationLabel: (e: CallStackItem) => {
I
isidor 已提交
132
						if (isDebugSession(e)) {
I
isidor 已提交
133 134 135
							return e.getLabel();
						}
						if (e instanceof Thread) {
136
							return `${e.name} ${e.stateLabel}`;
I
isidor 已提交
137 138 139 140 141 142 143 144 145 146
						}
						if (e instanceof StackFrame || typeof e === 'string') {
							return e;
						}
						if (e instanceof ThreadAndSessionIds) {
							return LoadMoreRenderer.LABEL;
						}

						return nls.localize('showMoreStackFrames2', "Show More Stack Frames");
					}
147 148
				},
				expandOnlyOnTwistieClick: true
149
			});
I
isidor 已提交
150

I
isidor 已提交
151
		this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError);
J
Joao Moreno 已提交
152

I
isidor 已提交
153
		const callstackNavigator = new TreeResourceNavigator2(this.tree);
M
Matt Bierner 已提交
154 155
		this._register(callstackNavigator);
		this._register(callstackNavigator.onDidOpenResource(e => {
I
isidor 已提交
156 157 158 159
			if (this.ignoreSelectionChangedEvent) {
				return;
			}

I
isidor 已提交
160
			const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession) => {
I
isidor 已提交
161 162 163 164 165 166 167 168
				this.ignoreFocusStackFrameEvent = true;
				try {
					this.debugService.focusStackFrame(stackFrame, thread, session, true);
				} finally {
					this.ignoreFocusStackFrameEvent = false;
				}
			};

I
isidor 已提交
169 170
			const element = e.element;
			if (element instanceof StackFrame) {
I
isidor 已提交
171
				focusStackFrame(element, element.thread, element.thread.session);
I
isidor 已提交
172 173 174
				element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
			}
			if (element instanceof Thread) {
I
isidor 已提交
175
				focusStackFrame(undefined, element, element.session);
I
isidor 已提交
176
			}
I
isidor 已提交
177
			if (isDebugSession(element)) {
I
isidor 已提交
178
				focusStackFrame(undefined, undefined, element);
I
isidor 已提交
179 180
			}
			if (element instanceof ThreadAndSessionIds) {
181
				const session = this.debugService.getModel().getSession(element.sessionId);
I
isidor 已提交
182 183 184
				const thread = session && session.getThread(element.threadId);
				if (thread) {
					(<Thread>thread).fetchCallStack()
185
						.then(() => this.tree.updateChildren());
I
isidor 已提交
186 187 188 189
				}
			}
			if (element instanceof Array) {
				this.dataSource.deemphasizedStackFramesToShow.push(...element);
190
				this.tree.updateChildren();
I
isidor 已提交
191 192
			}
		}));
I
isidor 已提交
193

M
Matt Bierner 已提交
194
		this._register(this.debugService.getModel().onDidChangeCallStack(() => {
195
			if (!this.isBodyVisible()) {
196 197 198 199
				this.needsRefresh = true;
				return;
			}

I
isidor 已提交
200 201 202 203
			if (!this.onCallStackChangeScheduler.isScheduled()) {
				this.onCallStackChangeScheduler.schedule();
			}
		}));
I
isidor 已提交
204
		const onCallStackChange = Event.any<any>(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession);
M
Matt Bierner 已提交
205
		this._register(onCallStackChange(() => {
I
isidor 已提交
206 207 208
			if (this.ignoreFocusStackFrameEvent) {
				return;
			}
209
			if (!this.isBodyVisible()) {
210 211 212 213
				this.needsRefresh = true;
				return;
			}

214
			this.updateTreeSelection();
215
		}));
M
Matt Bierner 已提交
216
		this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
I
isidor 已提交
217 218 219

		// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
		if (this.debugService.state === State.Stopped) {
I
isidor 已提交
220
			this.onCallStackChangeScheduler.schedule(0);
I
isidor 已提交
221
		}
222

M
Matt Bierner 已提交
223
		this._register(this.onDidChangeBodyVisibility(visible => {
224 225 226 227
			if (visible && this.needsRefresh) {
				this.onCallStackChangeScheduler.schedule();
			}
		}));
I
isidor 已提交
228

M
Matt Bierner 已提交
229
		this._register(this.debugService.onDidNewSession(s => {
I
isidor 已提交
230 231 232 233 234
			if (s.parentSession) {
				// Auto expand sessions that have sub sessions
				this.parentSessionToExpand.add(s.parentSession);
			}
		}));
I
isidor 已提交
235 236
	}

237 238
	layoutBody(height: number, width: number): void {
		this.tree.layout(height, width);
S
Sandeep Somavarapu 已提交
239 240
	}

I
isidor 已提交
241 242 243 244
	focus(): void {
		this.tree.domFocus();
	}

I
isidor 已提交
245
	private updateTreeSelection(): void {
I
isidor 已提交
246
		if (!this.tree || !this.tree.getInput()) {
I
isidor 已提交
247
			// Tree not initialized yet
I
isidor 已提交
248
			return;
I
isidor 已提交
249 250
		}

I
isidor 已提交
251
		const updateSelectionAndReveal = (element: IStackFrame | IDebugSession) => {
252 253 254
			this.ignoreSelectionChangedEvent = true;
			try {
				this.tree.setSelection([element]);
I
isidor 已提交
255 256 257
				this.tree.reveal(element);
			} catch (e) { }
			finally {
258 259 260 261
				this.ignoreSelectionChangedEvent = false;
			}
		};

I
isidor 已提交
262 263 264
		const thread = this.debugService.getViewModel().focusedThread;
		const session = this.debugService.getViewModel().focusedSession;
		const stackFrame = this.debugService.getViewModel().focusedStackFrame;
I
isidor 已提交
265
		if (!thread) {
I
isidor 已提交
266
			if (!session) {
I
isidor 已提交
267 268
				this.tree.setSelection([]);
			} else {
I
isidor 已提交
269
				updateSelectionAndReveal(session);
I
isidor 已提交
270
			}
I
isidor 已提交
271
		} else {
I
isidor 已提交
272 273 274 275 276 277
			const expandPromises = [() => ignoreErrors(this.tree.expand(thread))];
			let s: IDebugSession | undefined = thread.session;
			while (s) {
				const sessionToExpand = s;
				expandPromises.push(() => ignoreErrors(this.tree.expand(sessionToExpand)));
				s = s.parentSession;
I
isidor 已提交
278
			}
I
isidor 已提交
279 280 281 282 283 284 285

			sequence(expandPromises.reverse()).then(() => {
				const toReveal = stackFrame || session;
				if (toReveal) {
					updateSelectionAndReveal(toReveal);
				}
			});
I
isidor 已提交
286
		}
I
isidor 已提交
287 288
	}

I
isidor 已提交
289 290
	private onContextMenu(e: ITreeContextMenuEvent<CallStackItem>): void {
		const element = e.element;
I
isidor 已提交
291
		if (isDebugSession(element)) {
I
isidor 已提交
292
			this.callStackItemType.set('session');
293
		} else if (element instanceof Thread) {
I
isidor 已提交
294
			this.callStackItemType.set('thread');
295
		} else if (element instanceof StackFrame) {
I
isidor 已提交
296 297 298
			this.callStackItemType.set('stackFrame');
		} else {
			this.callStackItemType.reset();
299 300
		}

301 302 303
		const actions: IAction[] = [];
		const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);

I
isidor 已提交
304 305
		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
306 307 308
			getActions: () => actions,
			getActionsContext: () => element,
			onHide: () => dispose(actionsDisposable)
I
isidor 已提交
309
		});
310 311
	}

I
isidor 已提交
312
	private getContextForContributedActions(element: CallStackItem | null): string | number | undefined {
I
isidor 已提交
313 314 315 316
		if (element instanceof StackFrame) {
			if (element.source.inMemory) {
				return element.source.raw.path || element.source.reference;
			}
I
isidor 已提交
317

I
isidor 已提交
318
			return element.source.uri.toString();
I
isidor 已提交
319
		}
320
		if (element instanceof Thread) {
I
isidor 已提交
321
			return element.threadId;
322
		}
323 324 325
		if (isDebugSession(element)) {
			return element.getId();
		}
326

I
isidor 已提交
327
		return undefined;
328 329 330 331 332 333 334 335
	}
}

interface IThreadTemplateData {
	thread: HTMLElement;
	name: HTMLElement;
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
336
	label: HighlightedLabel;
337 338
}

I
isidor 已提交
339 340
interface ISessionTemplateData {
	session: HTMLElement;
341 342 343
	name: HTMLElement;
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
344
	label: HighlightedLabel;
345 346 347 348 349 350
}

interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
351
interface ILabelTemplateData {
352 353 354 355 356 357 358 359
	label: HTMLElement;
}

interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
I
isidor 已提交
360
	label: HighlightedLabel;
361 362
}

I
isidor 已提交
363
class SessionsRenderer implements ITreeRenderer<IDebugSession, FuzzyScore, ISessionTemplateData> {
I
isidor 已提交
364
	static readonly ID = 'session';
365

I
isidor 已提交
366 367
	get templateId(): string {
		return SessionsRenderer.ID;
368 369
	}

I
isidor 已提交
370
	renderTemplate(container: HTMLElement): ISessionTemplateData {
371 372 373 374 375
		const session = dom.append(container, $('.session'));
		const name = dom.append(session, $('.name'));
		const state = dom.append(session, $('.state'));
		const stateLabel = dom.append(state, $('span.label'));
		const label = new HighlightedLabel(name, false);
376

377
		return { session, name, state, stateLabel, label };
378 379
	}

I
isidor 已提交
380
	renderElement(element: ITreeNode<IDebugSession, FuzzyScore>, index: number, data: ISessionTemplateData): void {
I
isidor 已提交
381 382
		const session = element.element;
		data.session.title = nls.localize({ key: 'session', comment: ['Session is a noun'] }, "Session");
I
isidor 已提交
383
		data.label.set(session.getLabel(), createMatches(element.filterData));
I
isidor 已提交
384
		const stoppedThread = session.getAllThreads().filter(t => t.stopped).pop();
385

I
isidor 已提交
386 387 388
		data.stateLabel.textContent = stoppedThread ? nls.localize('paused', "Paused")
			: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
	}
389

I
isidor 已提交
390 391
	disposeTemplate(templateData: ISessionTemplateData): void {
		// noop
392
	}
I
isidor 已提交
393
}
394

I
isidor 已提交
395
class ThreadsRenderer implements ITreeRenderer<IThread, FuzzyScore, IThreadTemplateData> {
I
isidor 已提交
396 397 398 399
	static readonly ID = 'thread';

	get templateId(): string {
		return ThreadsRenderer.ID;
400 401
	}

I
isidor 已提交
402
	renderTemplate(container: HTMLElement): IThreadTemplateData {
403 404 405 406 407
		const thread = dom.append(container, $('.thread'));
		const name = dom.append(thread, $('.name'));
		const state = dom.append(thread, $('.state'));
		const stateLabel = dom.append(state, $('span.label'));
		const label = new HighlightedLabel(name, false);
408

409
		return { thread, name, state, stateLabel, label };
410 411
	}

I
isidor 已提交
412
	renderElement(element: ITreeNode<IThread, FuzzyScore>, index: number, data: IThreadTemplateData): void {
I
isidor 已提交
413
		const thread = element.element;
414
		data.thread.title = nls.localize('thread', "Thread");
I
isidor 已提交
415
		data.label.set(thread.name, createMatches(element.filterData));
416
		data.stateLabel.textContent = thread.stateLabel;
417 418
	}

I
isidor 已提交
419 420
	disposeTemplate(templateData: IThreadTemplateData): void {
		// noop
421
	}
I
isidor 已提交
422
}
423

I
isidor 已提交
424
class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, IStackFrameTemplateData> {
I
isidor 已提交
425 426
	static readonly ID = 'stackFrame';

427
	constructor(@ILabelService private readonly labelService: ILabelService) { }
I
isidor 已提交
428 429 430 431 432 433

	get templateId(): string {
		return StackFramesRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IStackFrameTemplateData {
434 435 436 437 438 439 440
		const stackFrame = dom.append(container, $('.stack-frame'));
		const labelDiv = dom.append(stackFrame, $('span.label.expression'));
		const file = dom.append(stackFrame, $('.file'));
		const fileName = dom.append(file, $('span.file-name'));
		const wrapper = dom.append(file, $('span.line-number-wrapper'));
		const lineNumber = dom.append(wrapper, $('span.line-number'));
		const label = new HighlightedLabel(labelDiv, false);
I
isidor 已提交
441

442
		return { file, fileName, label, lineNumber, stackFrame };
I
isidor 已提交
443 444
	}

I
isidor 已提交
445
	renderElement(element: ITreeNode<IStackFrame, FuzzyScore>, index: number, data: IStackFrameTemplateData): void {
I
isidor 已提交
446
		const stackFrame = element.element;
447
		dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame));
448 449 450
		dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
		dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');

I
isidor 已提交
451
		data.file.title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri);
452 453 454
		if (stackFrame.source.raw.origin) {
			data.file.title += `\n${stackFrame.source.raw.origin}`;
		}
I
isidor 已提交
455
		data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name);
I
isidor 已提交
456
		data.fileName.textContent = stackFrame.getSpecificSourceName();
457 458 459 460 461 462 463 464 465 466 467
		if (stackFrame.range.startLineNumber !== undefined) {
			data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
			if (stackFrame.range.startColumn) {
				data.lineNumber.textContent += `:${stackFrame.range.startColumn}`;
			}
			dom.removeClass(data.lineNumber, 'unavailable');
		} else {
			dom.addClass(data.lineNumber, 'unavailable');
		}
	}

I
isidor 已提交
468 469 470 471 472
	disposeTemplate(templateData: IStackFrameTemplateData): void {
		// noop
	}
}

I
isidor 已提交
473
class ErrorsRenderer implements ITreeRenderer<string, FuzzyScore, IErrorTemplateData> {
I
isidor 已提交
474 475 476 477 478 479 480
	static readonly ID = 'error';

	get templateId(): string {
		return ErrorsRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IErrorTemplateData {
481
		const label = dom.append(container, $('.error'));
I
isidor 已提交
482

483
		return { label };
I
isidor 已提交
484 485
	}

I
isidor 已提交
486
	renderElement(element: ITreeNode<string, FuzzyScore>, index: number, data: IErrorTemplateData): void {
I
isidor 已提交
487 488 489 490 491 492 493 494 495 496
		const error = element.element;
		data.label.textContent = error;
		data.label.title = error;
	}

	disposeTemplate(templateData: IErrorTemplateData): void {
		// noop
	}
}

I
isidor 已提交
497
class LoadMoreRenderer implements ITreeRenderer<ThreadAndSessionIds, FuzzyScore, ILabelTemplateData> {
I
isidor 已提交
498
	static readonly ID = 'loadMore';
I
isidor 已提交
499
	static readonly LABEL = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
I
isidor 已提交
500 501 502 503 504 505

	get templateId(): string {
		return LoadMoreRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IErrorTemplateData {
506
		const label = dom.append(container, $('.load-more'));
I
isidor 已提交
507

508
		return { label };
I
isidor 已提交
509 510
	}

I
isidor 已提交
511
	renderElement(element: ITreeNode<ThreadAndSessionIds, FuzzyScore>, index: number, data: ILabelTemplateData): void {
I
isidor 已提交
512
		data.label.textContent = LoadMoreRenderer.LABEL;
I
isidor 已提交
513 514 515 516 517 518 519
	}

	disposeTemplate(templateData: ILabelTemplateData): void {
		// noop
	}
}

I
isidor 已提交
520
class ShowMoreRenderer implements ITreeRenderer<IStackFrame[], FuzzyScore, ILabelTemplateData> {
I
isidor 已提交
521 522 523 524 525 526 527
	static readonly ID = 'showMore';

	get templateId(): string {
		return ShowMoreRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IErrorTemplateData {
528
		const label = dom.append(container, $('.show-more'));
I
isidor 已提交
529

530
		return { label };
I
isidor 已提交
531 532
	}

I
isidor 已提交
533
	renderElement(element: ITreeNode<IStackFrame[], FuzzyScore>, index: number, data: ILabelTemplateData): void {
I
isidor 已提交
534
		const stackFrames = element.element;
535
		if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) {
I
isidor 已提交
536 537 538 539 540 541 542
			data.label.textContent = nls.localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin);
		} else {
			data.label.textContent = nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length);
		}
	}

	disposeTemplate(templateData: ILabelTemplateData): void {
543 544 545 546
		// noop
	}
}

I
isidor 已提交
547 548 549 550 551 552 553
class CallStackDelegate implements IListVirtualDelegate<CallStackItem> {

	getHeight(element: CallStackItem): number {
		return 22;
	}

	getTemplateId(element: CallStackItem): string {
I
isidor 已提交
554
		if (isDebugSession(element)) {
I
isidor 已提交
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
			return SessionsRenderer.ID;
		}
		if (element instanceof Thread) {
			return ThreadsRenderer.ID;
		}
		if (element instanceof StackFrame) {
			return StackFramesRenderer.ID;
		}
		if (typeof element === 'string') {
			return ErrorsRenderer.ID;
		}
		if (element instanceof ThreadAndSessionIds) {
			return LoadMoreRenderer.ID;
		}

I
isidor 已提交
570 571
		// element instanceof Array
		return ShowMoreRenderer.ID;
I
isidor 已提交
572 573 574
	}
}

J
Joao Moreno 已提交
575 576 577
function isDebugModel(obj: any): obj is IDebugModel {
	return typeof obj.getSessions === 'function';
}
I
isidor 已提交
578

I
isidor 已提交
579
function isDebugSession(obj: any): obj is IDebugSession {
I
isidor 已提交
580
	return obj && typeof obj.getAllThreads === 'function';
I
isidor 已提交
581 582
}

583 584 585 586
function isDeemphasized(frame: IStackFrame): boolean {
	return frame.source.presentationHint === 'deemphasize' || frame.presentationHint === 'deemphasize';
}

J
Joao Moreno 已提交
587
class CallStackDataSource implements IAsyncDataSource<IDebugModel, CallStackItem> {
I
isidor 已提交
588
	deemphasizedStackFramesToShow: IStackFrame[] = [];
I
isidor 已提交
589

I
isidor 已提交
590 591
	constructor(private debugService: IDebugService) { }

J
Joao Moreno 已提交
592
	hasChildren(element: IDebugModel | CallStackItem): boolean {
593 594 595 596 597 598
		if (isDebugSession(element)) {
			const threads = element.getAllThreads();
			return (threads.length > 1) || (threads.length === 1 && threads[0].stopped) || (this.debugService.getModel().getSessions().filter(s => s.parentSession === element).length > 0);
		}

		return isDebugModel(element) || (element instanceof Thread && element.stopped);
I
isidor 已提交
599 600
	}

601
	async getChildren(element: IDebugModel | CallStackItem): Promise<CallStackItem[]> {
J
Joao Moreno 已提交
602 603
		if (isDebugModel(element)) {
			const sessions = element.getSessions();
604 605 606
			if (sessions.length === 0) {
				return Promise.resolve([]);
			}
I
isidor 已提交
607
			if (sessions.length > 1) {
I
isidor 已提交
608
				return Promise.resolve(sessions.filter(s => !s.parentSession));
I
isidor 已提交
609 610 611 612 613
			}

			const threads = sessions[0].getAllThreads();
			// Only show the threads in the call stack if there is more than 1 thread.
			return threads.length === 1 ? this.getThreadChildren(<Thread>threads[0]) : Promise.resolve(threads);
I
isidor 已提交
614
		} else if (isDebugSession(element)) {
I
isidor 已提交
615
			const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element);
I
isidor 已提交
616
			const threads: CallStackItem[] = element.getAllThreads();
617
			if (threads.length === 1) {
618
				// Do not show thread when there is only one to be compact.
619 620
				const children = await this.getThreadChildren(<Thread>threads[0]);
				return children.concat(childSessions);
621
			}
I
isidor 已提交
622

I
isidor 已提交
623
			return Promise.resolve(threads.concat(childSessions));
J
Joao Moreno 已提交
624 625
		} else {
			return this.getThreadChildren(<Thread>element);
I
isidor 已提交
626 627 628
		}
	}

I
isidor 已提交
629
	private getThreadChildren(thread: Thread): Promise<CallStackItem[]> {
I
isidor 已提交
630 631
		return this.getThreadCallstack(thread).then(children => {
			// Check if some stack frames should be hidden under a parent element since they are deemphasized
I
isidor 已提交
632
			const result: CallStackItem[] = [];
I
isidor 已提交
633
			children.forEach((child, index) => {
634
				if (child instanceof StackFrame && child.source && isDeemphasized(child)) {
I
isidor 已提交
635 636
					// Check if the user clicked to show the deemphasized source
					if (this.deemphasizedStackFramesToShow.indexOf(child) === -1) {
I
isidor 已提交
637 638 639 640 641 642 643
						if (result.length) {
							const last = result[result.length - 1];
							if (last instanceof Array) {
								// Collect all the stackframes that will be "collapsed"
								last.push(child);
								return;
							}
I
isidor 已提交
644 645 646
						}

						const nextChild = index < children.length - 1 ? children[index + 1] : undefined;
647
						if (nextChild instanceof StackFrame && nextChild.source && isDeemphasized(nextChild)) {
I
isidor 已提交
648 649 650 651 652 653 654 655 656 657 658 659 660 661
							// Start collecting stackframes that will be "collapsed"
							result.push([child]);
							return;
						}
					}
				}

				result.push(child);
			});

			return result;
		});
	}

662
	private getThreadCallstack(thread: Thread): Promise<Array<IStackFrame | string | ThreadAndSessionIds>> {
I
isidor 已提交
663 664 665 666 667 668 669
		let callStack: any[] = thread.getCallStack();
		let callStackPromise: Promise<any> = Promise.resolve(null);
		if (!callStack || !callStack.length) {
			callStackPromise = thread.fetchCallStack().then(() => callStack = thread.getCallStack());
		}

		return callStackPromise.then(() => {
I
isidor 已提交
670
			if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > 1) {
I
isidor 已提交
671 672 673 674 675 676 677 678
				// To reduce flashing of the call stack view simply append the stale call stack
				// once we have the correct data the tree will refresh and we will no longer display it.
				callStack = callStack.concat(thread.getStaleCallStack().slice(1));
			}

			if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
				callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]);
			}
I
isidor 已提交
679
			if (thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
I
isidor 已提交
680 681 682 683 684 685 686 687 688 689
				callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]);
			}

			return callStack;
		});
	}
}

class CallStackAccessibilityProvider implements IAccessibilityProvider<CallStackItem> {
	getAriaLabel(element: CallStackItem): string {
690 691 692 693
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
		}
		if (element instanceof StackFrame) {
I
isidor 已提交
694
			return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
695
		}
I
isidor 已提交
696
		if (isDebugSession(element)) {
I
isidor 已提交
697 698 699 700 701 702 703 704
			return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
		}
		if (typeof element === 'string') {
			return element;
		}
		if (element instanceof Array) {
			return nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length);
		}
705

I
isidor 已提交
706 707
		// element instanceof ThreadAndSessionIds
		return nls.localize('loadMoreStackFrames', "Load More Stack Frames");
708 709
	}
}