callStackView.ts 21.6 KB
Newer Older
I
isidor 已提交
1 2 3 4 5 6 7 8 9 10
/*---------------------------------------------------------------------------------------------
 *  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';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
11
import { TreeViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
12
import { IDebugService, State, IStackFrame, ISession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug';
I
isidor 已提交
13
import { Thread, StackFrame, ThreadAndSessionIds, Session, Model } from 'vs/workbench/parts/debug/common/debugModel';
I
isidor 已提交
14 15 16 17
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
18
import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView';
19 20
import { ITree, IActionProvider, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { IAction, IActionItem } from 'vs/base/common/actions';
21
import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction, TerminateThreadAction } from 'vs/workbench/parts/debug/browser/debugActions';
22 23
import { CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
B
Benjamin Pasero 已提交
24
import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService';
25
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
26
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
27
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
28
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
29
import { IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
I
isidor 已提交
30 31
import { getPathLabel } from 'vs/base/common/labels';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
I
isidor 已提交
32

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

35
export class CallStackView extends TreeViewsViewletPanel {
I
isidor 已提交
36 37

	private static readonly MEMENTO = 'callstackview.memento';
38 39
	private pauseMessage: HTMLSpanElement;
	private pauseMessageLabel: HTMLSpanElement;
I
isidor 已提交
40 41
	private onCallStackChangeScheduler: RunOnceScheduler;
	private settings: any;
42
	private needsRefresh: boolean;
43
	private ignoreSelectionChangedEvent: boolean;
S
Sandeep Somavarapu 已提交
44
	private treeContainer: HTMLElement;
45
	private callStackItemType: IContextKey<string>;
I
isidor 已提交
46 47 48 49 50 51 52

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

		// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
		this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
			let newTreeInput: any = this.debugService.getModel();
I
isidor 已提交
64 65 66
			const sessions = this.debugService.getModel().getSessions();
			if (!this.debugService.getViewModel().isMultiSessionView() && sessions.length) {
				const threads = sessions[0].getAllThreads();
I
isidor 已提交
67
				// Only show the threads in the call stack if there is more than 1 thread.
I
isidor 已提交
68
				newTreeInput = threads.length === 1 ? threads[0] : sessions[0];
I
isidor 已提交
69 70 71 72 73
			}

			// 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.
			if (newTreeInput instanceof Thread && newTreeInput.stoppedDetails) {
74
				this.pauseMessageLabel.textContent = newTreeInput.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", newTreeInput.stoppedDetails.reason);
I
isidor 已提交
75
				if (newTreeInput.stoppedDetails.text) {
76
					this.pauseMessageLabel.title = newTreeInput.stoppedDetails.text;
I
isidor 已提交
77
				}
78 79
				dom.toggleClass(this.pauseMessageLabel, 'exception', newTreeInput.stoppedDetails.reason === 'exception');
				this.pauseMessage.hidden = false;
I
isidor 已提交
80
			} else {
81
				this.pauseMessage.hidden = true;
I
isidor 已提交
82 83
			}

84
			this.needsRefresh = false;
I
isidor 已提交
85 86 87 88 89
			(this.tree.getInput() === newTreeInput ? this.tree.refresh() : this.tree.setInput(newTreeInput))
				.done(() => this.updateTreeSelection(), errors.onUnexpectedError);
		}, 50);
	}

90 91
	protected renderHeaderTitle(container: HTMLElement): HTMLElement {
		const name = $('span');
92
		name.textContent = this.options.title;
93
		this.pauseMessage = $('span.pause-message');
94 95
		this.pauseMessage.hidden = true;
		this.pauseMessageLabel = dom.append(this.pauseMessage, $('span.label'));
96 97

		return super.renderHeaderTitle(container, ['debug-call-stack-title'], [name, this.pauseMessage]);
I
isidor 已提交
98 99 100 101 102
	}

	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'debug-call-stack');
		this.treeContainer = renderViewTree(container);
I
isidor 已提交
103
		const actionProvider = new CallStackActionProvider(this.debugService, this.keybindingService, this.instantiationService);
104
		const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext, {});
I
isidor 已提交
105

106
		this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
107
			dataSource: new CallStackDataSource(),
108 109
			renderer: this.instantiationService.createInstance(CallStackRenderer),
			accessibilityProvider: this.instantiationService.createInstance(CallstackAccessibilityProvider),
I
isidor 已提交
110 111 112
			controller
		}, {
				ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
113
				twistiePixels
114
			});
I
isidor 已提交
115

B
Benjamin Pasero 已提交
116 117 118
		const callstackNavigator = new TreeResourceNavigator(this.tree);
		this.disposables.push(callstackNavigator);
		this.disposables.push(callstackNavigator.openResource(e => {
119 120 121
			if (this.ignoreSelectionChangedEvent) {
				return;
			}
122

123
			const element = e.element;
124
			if (element instanceof StackFrame) {
I
isidor 已提交
125
				this.debugService.focusStackFrame(element, element.thread, element.thread.session, true);
126
				element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError);
127 128
			}
			if (element instanceof Thread) {
I
isidor 已提交
129
				this.debugService.focusStackFrame(undefined, element, element.session, true);
130
			}
I
isidor 已提交
131
			if (element instanceof Session) {
132 133
				this.debugService.focusStackFrame(undefined, undefined, element, true);
			}
I
isidor 已提交
134 135 136
			if (element instanceof ThreadAndSessionIds) {
				const session = this.debugService.getModel().getSessions().filter(p => p.getId() === element.sessionId).pop();
				const thread = session && session.getThread(element.threadId);
137 138 139 140
				if (thread) {
					(<Thread>thread).fetchCallStack()
						.done(() => this.tree.refresh(), errors.onUnexpectedError);
				}
I
isidor 已提交
141 142
			}
		}));
143 144 145 146 147 148 149 150 151 152 153 154
		this.disposables.push(this.tree.onDidChangeFocus(() => {
			const focus = this.tree.getFocus();
			if (focus instanceof StackFrame) {
				this.callStackItemType.set('stackFrame');
			} else if (focus instanceof Thread) {
				this.callStackItemType.set('thread');
			} else if (focus instanceof Session) {
				this.callStackItemType.set('session');
			} else {
				this.callStackItemType.reset();
			}
		}));
I
isidor 已提交
155 156

		this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => {
157 158 159 160 161
			if (!this.isVisible()) {
				this.needsRefresh = true;
				return;
			}

I
isidor 已提交
162 163 164 165
			if (!this.onCallStackChangeScheduler.isScheduled()) {
				this.onCallStackChangeScheduler.schedule();
			}
		}));
166 167 168 169 170 171 172 173
		this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
			if (!this.isVisible) {
				this.needsRefresh = true;
				return;
			}

			this.updateTreeSelection().done(undefined, errors.onUnexpectedError);
		}));
I
isidor 已提交
174 175 176 177 178 179 180

		// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
		if (this.debugService.state === State.Stopped) {
			this.onCallStackChangeScheduler.schedule();
		}
	}

S
Sandeep Somavarapu 已提交
181 182 183 184 185 186 187
	layoutBody(size: number): void {
		if (this.treeContainer) {
			this.treeContainer.style.height = size + 'px';
		}
		super.layoutBody(size);
	}

I
isidor 已提交
188 189 190 191 192 193 194 195
	private updateTreeSelection(): TPromise<void> {
		if (!this.tree.getInput()) {
			// Tree not initialized yet
			return TPromise.as(null);
		}

		const stackFrame = this.debugService.getViewModel().focusedStackFrame;
		const thread = this.debugService.getViewModel().focusedThread;
I
isidor 已提交
196 197
		const session = this.debugService.getViewModel().focusedSession;
		const updateSelection = (element: IStackFrame | ISession) => {
198 199 200 201 202 203 204 205
			this.ignoreSelectionChangedEvent = true;
			try {
				this.tree.setSelection([element]);
			} finally {
				this.ignoreSelectionChangedEvent = false;
			}
		};

I
isidor 已提交
206
		if (!thread) {
I
isidor 已提交
207
			if (!session) {
I
isidor 已提交
208 209 210 211
				this.tree.clearSelection();
				return TPromise.as(null);
			}

I
isidor 已提交
212 213
			updateSelection(session);
			return this.tree.reveal(session);
I
isidor 已提交
214 215
		}

I
isidor 已提交
216
		return this.tree.expandAll([thread.session, thread]).then(() => {
I
isidor 已提交
217 218 219 220
			if (!stackFrame) {
				return TPromise.as(null);
			}

221
			updateSelection(stackFrame);
I
isidor 已提交
222 223 224 225
			return this.tree.reveal(stackFrame);
		});
	}

226 227 228 229 230 231 232 233
	public setVisible(visible: boolean): TPromise<void> {
		return super.setVisible(visible).then(() => {
			if (visible && this.needsRefresh) {
				this.onCallStackChangeScheduler.schedule();
			}
		});
	}

I
isidor 已提交
234 235 236 237 238 239
	public shutdown(): void {
		this.settings[CallStackView.MEMENTO] = !this.isExpanded();
		super.shutdown();
	}
}

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
class CallStackController extends BaseDebugController {
	protected getContext(element: any): any {
		if (element instanceof StackFrame) {
			if (element.source.inMemory) {
				return element.source.raw.path || element.source.reference;
			}

			return element.source.uri.toString();
		}
		if (element instanceof Thread) {
			return element.threadId;
		}
	}
}


class CallStackActionProvider implements IActionProvider {

I
isidor 已提交
258
	constructor(private debugService: IDebugService, private keybindingService: IKeybindingService, private instantiationService: IInstantiationService) {
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
		// noop
	}

	public hasActions(tree: ITree, element: any): boolean {
		return false;
	}

	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
		return TPromise.as([]);
	}

	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element !== tree.getInput();
	}

	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
I
isidor 已提交
276
		if (element instanceof Session) {
I
isidor 已提交
277
			actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL));
278 279 280 281 282 283 284 285 286 287 288
			actions.push(new StopAction(StopAction.ID, StopAction.LABEL, this.debugService, this.keybindingService));
		} else if (element instanceof Thread) {
			const thread = <Thread>element;
			if (thread.stopped) {
				actions.push(new ContinueAction(ContinueAction.ID, ContinueAction.LABEL, this.debugService, this.keybindingService));
				actions.push(new StepOverAction(StepOverAction.ID, StepOverAction.LABEL, this.debugService, this.keybindingService));
				actions.push(new StepIntoAction(StepIntoAction.ID, StepIntoAction.LABEL, this.debugService, this.keybindingService));
				actions.push(new StepOutAction(StepOutAction.ID, StepOutAction.LABEL, this.debugService, this.keybindingService));
			} else {
				actions.push(new PauseAction(PauseAction.ID, PauseAction.LABEL, this.debugService, this.keybindingService));
			}
289 290 291

			actions.push(new Separator());
			actions.push(new TerminateThreadAction(TerminateThreadAction.ID, TerminateThreadAction.LABEL, this.debugService, this.keybindingService));
292
		} else if (element instanceof StackFrame) {
I
isidor 已提交
293
			if (element.thread.session.raw.capabilities.supportsRestartFrame) {
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
				actions.push(new RestartFrameAction(RestartFrameAction.ID, RestartFrameAction.LABEL, this.debugService, this.keybindingService));
			}
			actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
		}

		return TPromise.as(actions);
	}

	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
		return null;
	}
}

class CallStackDataSource implements IDataSource {

	public getId(tree: ITree, element: any): string {
		if (typeof element === 'string') {
			return element;
		}

		return element.getId();
	}

	public hasChildren(tree: ITree, element: any): boolean {
I
isidor 已提交
318
		return element instanceof Model || element instanceof Session || (element instanceof Thread && (<Thread>element).stopped);
319 320 321 322 323 324 325
	}

	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Thread) {
			return this.getThreadChildren(element);
		}
		if (element instanceof Model) {
I
isidor 已提交
326
			return TPromise.as(element.getSessions());
327 328
		}

I
isidor 已提交
329 330
		const session = <ISession>element;
		return TPromise.as(session.getAllThreads());
331 332 333 334 335 336 337 338 339 340
	}

	private getThreadChildren(thread: Thread): TPromise<any> {
		let callStack: any[] = thread.getCallStack();
		let callStackPromise: TPromise<any> = TPromise.as(null);
		if (!callStack || !callStack.length) {
			callStackPromise = thread.fetchCallStack().then(() => callStack = thread.getCallStack());
		}

		return callStackPromise.then(() => {
I
isidor 已提交
341
			if (callStack.length === 1 && thread.session.raw.capabilities.supportsDelayedStackTraceLoading) {
342 343 344 345 346 347 348 349 350
				// 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]);
			}
			if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
I
isidor 已提交
351
				callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]);
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
			}

			return callStack;
		});
	}

	public getParent(tree: ITree, element: any): TPromise<any> {
		return TPromise.as(null);
	}
}

interface IThreadTemplateData {
	thread: HTMLElement;
	name: HTMLElement;
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
}

I
isidor 已提交
370 371
interface ISessionTemplateData {
	session: HTMLElement;
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
	name: HTMLElement;
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
}

interface IErrorTemplateData {
	label: HTMLElement;
}

interface ILoadMoreTemplateData {
	label: HTMLElement;
}

interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
}

class CallStackRenderer implements IRenderer {

	private static readonly THREAD_TEMPLATE_ID = 'thread';
	private static readonly STACK_FRAME_TEMPLATE_ID = 'stackFrame';
	private static readonly ERROR_TEMPLATE_ID = 'error';
	private static readonly LOAD_MORE_TEMPLATE_ID = 'loadMore';
I
isidor 已提交
399
	private static readonly SESSION_TEMPLATE_ID = 'session';
400 401 402

	constructor(
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
I
isidor 已提交
403
		@IEnvironmentService private environmentService: IEnvironmentService
404 405 406 407 408 409 410 411 412
	) {
		// noop
	}

	public getHeight(tree: ITree, element: any): number {
		return 22;
	}

	public getTemplateId(tree: ITree, element: any): string {
I
isidor 已提交
413 414
		if (element instanceof Session) {
			return CallStackRenderer.SESSION_TEMPLATE_ID;
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
		}
		if (element instanceof Thread) {
			return CallStackRenderer.THREAD_TEMPLATE_ID;
		}
		if (element instanceof StackFrame) {
			return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
		}
		if (typeof element === 'string') {
			return CallStackRenderer.ERROR_TEMPLATE_ID;
		}

		return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
	}

	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
430 431 432 433 434
		if (templateId === CallStackRenderer.SESSION_TEMPLATE_ID) {
			let data: ISessionTemplateData = Object.create(null);
			data.session = dom.append(container, $('.session'));
			data.name = dom.append(data.session, $('.name'));
			data.state = dom.append(data.session, $('.state'));
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
			data.stateLabel = dom.append(data.state, $('span.label'));

			return data;
		}

		if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
			let data: ILoadMoreTemplateData = Object.create(null);
			data.label = dom.append(container, $('.load-more'));

			return data;
		}
		if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			let data: ILoadMoreTemplateData = Object.create(null);
			data.label = dom.append(container, $('.error'));

			return data;
		}
		if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			let data: IThreadTemplateData = Object.create(null);
			data.thread = dom.append(container, $('.thread'));
			data.name = dom.append(data.thread, $('.name'));
			data.state = dom.append(data.thread, $('.state'));
			data.stateLabel = dom.append(data.state, $('span.label'));

			return data;
		}

		let data: IStackFrameTemplateData = Object.create(null);
		data.stackFrame = dom.append(container, $('.stack-frame'));
		data.label = dom.append(data.stackFrame, $('span.label.expression'));
		data.file = dom.append(data.stackFrame, $('.file'));
		data.fileName = dom.append(data.file, $('span.file-name'));
		const wrapper = dom.append(data.file, $('span.line-number-wrapper'));
		data.lineNumber = dom.append(wrapper, $('span.line-number'));

		return data;
	}

	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
I
isidor 已提交
474 475
		if (templateId === CallStackRenderer.SESSION_TEMPLATE_ID) {
			this.renderSession(element, templateData);
476 477 478 479 480 481 482
		} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			this.renderThread(element, templateData);
		} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
			this.renderStackFrame(element, templateData);
		} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			this.renderError(element, templateData);
		} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
I
isidor 已提交
483
			this.renderLoadMore(templateData);
484 485 486
		}
	}

I
isidor 已提交
487 488 489 490
	private renderSession(session: ISession, data: ISessionTemplateData): void {
		data.session.title = nls.localize({ key: 'session', comment: ['Session is a noun'] }, "Session");
		data.name.textContent = session.getName(this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE);
		const stoppedThread = session.getAllThreads().filter(t => t.stopped).pop();
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512

		data.stateLabel.textContent = stoppedThread ? nls.localize('paused', "Paused")
			: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
	}

	private renderThread(thread: IThread, data: IThreadTemplateData): void {
		data.thread.title = nls.localize('thread', "Thread");
		data.name.textContent = thread.name;

		if (thread.stopped) {
			data.stateLabel.textContent = thread.stoppedDetails.description ||
				thread.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", thread.stoppedDetails.reason) : nls.localize('paused', "Paused");
		} else {
			data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
		}
	}

	private renderError(element: string, data: IErrorTemplateData) {
		data.label.textContent = element;
		data.label.title = element;
	}

I
isidor 已提交
513
	private renderLoadMore(data: ILoadMoreTemplateData): void {
514 515 516 517 518 519 520 521
		data.label.textContent = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
	}

	private renderStackFrame(stackFrame: IStackFrame, data: IStackFrameTemplateData): void {
		dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source.available || stackFrame.source.presentationHint === 'deemphasize');
		dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
		dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');

I
isidor 已提交
522 523
		const path = stackFrame.source.raw.path || stackFrame.source.name;
		data.file.title = getPathLabel(path, this.environmentService);
524 525 526 527 528
		if (stackFrame.source.raw.origin) {
			data.file.title += `\n${stackFrame.source.raw.origin}`;
		}
		data.label.textContent = stackFrame.name;
		data.label.title = stackFrame.name;
I
isidor 已提交
529
		data.fileName.textContent = stackFrame.getSpecificSourceName();
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
		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');
		}
	}

	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
		// noop
	}
}

class CallstackAccessibilityProvider implements IAccessibilityProvider {

I
isidor 已提交
548
	constructor() {
549 550 551 552 553 554 555 556
		// noop
	}

	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
		}
		if (element instanceof StackFrame) {
I
isidor 已提交
557
			return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
558 559 560 561 562
		}

		return null;
	}
}