debugViewer.ts 47.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
8
import * as lifecycle from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
9
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
I
isidor 已提交
10 11 12 13
import * as paths from 'vs/base/common/paths';
import * as async from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors';
import { equalsIgnoreCase } from 'vs/base/common/strings';
J
Johannes Rieken 已提交
14
import { isMacintosh } from 'vs/base/common/platform';
I
isidor 已提交
15
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
16
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
I
isidor 已提交
17 18 19 20
import { getPathLabel } from 'vs/base/common/labels';
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { IActionItem, Separator, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer } from 'vs/base/parts/tree/browser/tree';
J
Johannes Rieken 已提交
21
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
I
isidor 已提交
22 23 24 25 26 27
import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IActionProvider } from 'vs/base/parts/tree/browser/actionsRenderer';
import * as debug from 'vs/workbench/parts/debug/common/debug';
import { Expression, Variable, FunctionBreakpoint, StackFrame, Thread, Process, Breakpoint, ExceptionBreakpoint, Model, Scope } from 'vs/workbench/parts/debug/common/debugModel';
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { ContinueAction, StepOverAction, PauseAction, AddFunctionBreakpointAction, ReapplyBreakpointsAction, DisableAllBreakpointsAction, RemoveBreakpointAction, ToggleEnablementAction, RenameFunctionBreakpointAction, RemoveWatchExpressionAction, AddWatchExpressionAction, EditWatchExpressionAction, RemoveAllBreakpointsAction, EnableAllBreakpointsAction, StepOutAction, StepIntoAction, SetValueAction, RemoveAllWatchExpressionsAction, ToggleBreakpointsActivatedAction, RestartFrameAction, AddToWatchExpressionsAction } from 'vs/workbench/parts/debug/browser/debugActions';
J
Johannes Rieken 已提交
28 29 30 31 32 33
import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
E
Erich Gamma 已提交
34

J
Joao Moreno 已提交
35
const $ = dom.$;
I
isidor 已提交
36 37
const booleanRegex = /^true|false$/i;
const stringRegex = /^(['"]).*\1$/;
38
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
E
Erich Gamma 已提交
39

40
export function renderExpressionValue(expressionOrValue: debug.IExpression | string, container: HTMLElement, showChanged: boolean, maxValueRenderLength?: number): void {
41
	let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value;
E
Erich Gamma 已提交
42

I
isidor 已提交
43
	// remove stale classes
E
Erich Gamma 已提交
44
	container.className = 'value';
I
isidor 已提交
45
	// when resolving expressions we represent errors from the server as a variable with name === null.
I
isidor 已提交
46
	if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) {
E
Erich Gamma 已提交
47
		dom.addClass(container, 'unavailable');
I
isidor 已提交
48
		if (value !== Expression.DEFAULT_VALUE) {
49 50
			dom.addClass(container, 'error');
		}
E
Erich Gamma 已提交
51 52 53 54 55 56 57 58
	} else if (!isNaN(+value)) {
		dom.addClass(container, 'number');
	} else if (booleanRegex.test(value)) {
		dom.addClass(container, 'boolean');
	} else if (stringRegex.test(value)) {
		dom.addClass(container, 'string');
	}

59 60 61 62
	if (showChanged && (<any>expressionOrValue).valueChanged) {
		// value changed color has priority over other colors.
		container.className = 'value changed';
	}
I
isidor 已提交
63

64 65
	if (maxValueRenderLength && value.length > maxValueRenderLength) {
		value = value.substr(0, maxValueRenderLength) + '...';
I
isidor 已提交
66
	}
E
Erich Gamma 已提交
67 68 69 70
	container.textContent = value;
	container.title = value;
}

I
isidor 已提交
71
export function renderVariable(tree: ITree, variable: Variable, data: IVariableTemplateData, showChanged: boolean): void {
72
	if (variable.available) {
73
		data.name.textContent = variable.name;
74
		data.name.title = variable.type ? variable.type : '';
75 76
	}

E
Erich Gamma 已提交
77
	if (variable.value) {
78
		data.name.textContent += ':';
79
		renderExpressionValue(variable, data.value, showChanged, MAX_VALUE_RENDER_LENGTH_IN_VIEWLET);
80
		data.value.title = variable.value;
E
Erich Gamma 已提交
81 82 83 84 85 86
	} else {
		data.value.textContent = '';
		data.value.title = '';
	}
}

I
isidor 已提交
87 88 89 90
interface IRenameBoxOptions {
	initialValue: string;
	ariaLabel: string;
	placeholder?: string;
91
	validationOptions?: IInputValidationOptions;
I
isidor 已提交
92 93
}

I
isidor 已提交
94
function renderRenameBox(debugService: debug.IDebugService, contextViewService: IContextViewService, tree: ITree, element: any, container: HTMLElement, options: IRenameBoxOptions): void {
95
	let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
96
	let inputBox = new InputBox(inputBoxContainer, contextViewService, {
I
isidor 已提交
97 98 99
		validationOptions: options.validationOptions,
		placeholder: options.placeholder,
		ariaLabel: options.ariaLabel
100 101
	});

I
isidor 已提交
102
	inputBox.value = options.initialValue ? options.initialValue : '';
103
	inputBox.focus();
104
	inputBox.select();
105

I
isidor 已提交
106 107
	let disposed = false;
	const toDispose: [lifecycle.IDisposable] = [inputBox];
108

J
Joao Moreno 已提交
109
	const wrapUp = async.once((renamed: boolean) => {
110 111
		if (!disposed) {
			disposed = true;
I
isidor 已提交
112
			if (element instanceof Expression && renamed && inputBox.value) {
113
				debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
I
isidor 已提交
114
			} else if (element instanceof Expression && !element.name) {
115
				debugService.removeWatchExpressions(element.getId());
I
isidor 已提交
116
			} else if (element instanceof FunctionBreakpoint && renamed && inputBox.value) {
117
				debugService.renameFunctionBreakpoint(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
I
isidor 已提交
118
			} else if (element instanceof FunctionBreakpoint && !element.name) {
119
				debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
I
isidor 已提交
120
			} else if (element instanceof Variable) {
121
				element.errorMessage = null;
122
				if (renamed && element.value !== inputBox.value) {
123
					element.setVariable(inputBox.value)
124 125 126
						// if everything went fine we need to refresh that tree element since his value updated
						.done(() => tree.refresh(element, false), errors.onUnexpectedError);
				}
127
			}
128

129 130
			tree.clearHighlight();
			tree.DOMFocus();
131
			tree.setFocus(element);
132

I
isidor 已提交
133
			// need to remove the input box since this template will be reused.
134
			container.removeChild(inputBoxContainer);
J
Joao Moreno 已提交
135
			lifecycle.dispose(toDispose);
136 137 138
		}
	});

A
Cleanup  
Alex Dima 已提交
139
	toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
A
Alexandru Dima 已提交
140 141
		const isEscape = e.equals(KeyCode.Escape);
		const isEnter = e.equals(KeyCode.Enter);
142 143 144 145 146 147 148 149 150
		if (isEscape || isEnter) {
			wrapUp(isEnter);
		}
	}));
	toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
		wrapUp(true);
	}));
}

151 152 153 154 155
function getSourceName(source: Source, contextService: IWorkspaceContextService): string {
	if (source.inMemory) {
		return source.name;
	}

I
isidor 已提交
156
	return getPathLabel(paths.basename(source.uri.fsPath), contextService);
157 158
}

I
isidor 已提交
159
export class BaseDebugController extends DefaultController {
E
Erich Gamma 已提交
160

I
isidor 已提交
161 162 163
	constructor(
		protected debugService: debug.IDebugService,
		private contextMenuService: IContextMenuService,
I
isidor 已提交
164
		private actionProvider: IActionProvider,
I
isidor 已提交
165 166
		private focusOnContextMenu = true
	) {
E
Erich Gamma 已提交
167 168 169
		super();

		if (isMacintosh) {
A
Alexandru Dima 已提交
170
			this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Backspace, this.onDelete.bind(this));
E
Erich Gamma 已提交
171
		} else {
A
Alexandru Dima 已提交
172 173
			this.downKeyBindingDispatcher.set(KeyCode.Delete, this.onDelete.bind(this));
			this.downKeyBindingDispatcher.set(KeyMod.Shift | KeyCode.Delete, this.onDelete.bind(this));
E
Erich Gamma 已提交
174 175 176
		}
	}

I
isidor 已提交
177
	public onContextMenu(tree: ITree, element: debug.IEnablement, event: ContextMenuEvent): boolean {
E
Erich Gamma 已提交
178 179 180 181 182 183 184 185 186 187 188 189
		if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
			return false;
		}

		event.preventDefault();
		event.stopPropagation();

		if (this.focusOnContextMenu) {
			tree.setFocus(element);
		}

		if (this.actionProvider.hasSecondaryActions(tree, element)) {
I
isidor 已提交
190
			const anchor = { x: event.posx + 1, y: event.posy };
E
Erich Gamma 已提交
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
			this.contextMenuService.showContextMenu({
				getAnchor: () => anchor,
				getActions: () => this.actionProvider.getSecondaryActions(tree, element),
				onHide: (wasCancelled?: boolean) => {
					if (wasCancelled) {
						tree.DOMFocus();
					}
				},
				getActionsContext: () => element
			});

			return true;
		}

		return false;
	}

I
isidor 已提交
208
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
E
Erich Gamma 已提交
209 210 211 212
		return false;
	}
}

I
isidor 已提交
213
// call stack
E
Erich Gamma 已提交
214

I
isidor 已提交
215
class ThreadAndProcessIds implements debug.ITreeElement {
I
isidor 已提交
216
	constructor(public processId: string, public threadId: number) { }
I
isidor 已提交
217 218 219 220

	public getId(): string {
		return `${this.processId}:${this.threadId}`;
	}
221 222
}

223 224
export class CallStackController extends BaseDebugController {

I
isidor 已提交
225
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
I
isidor 已提交
226
		if (element instanceof ThreadAndProcessIds) {
227 228
			return this.showMoreStackFrames(tree, element);
		}
I
isidor 已提交
229
		if (element instanceof StackFrame) {
230 231
			this.focusStackFrame(element, event, true);
		}
232 233 234 235

		return super.onLeftClick(tree, element, event);
	}

I
isidor 已提交
236
	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
237
		const element = tree.getFocus();
I
isidor 已提交
238
		if (element instanceof ThreadAndProcessIds) {
239 240
			return this.showMoreStackFrames(tree, element);
		}
I
isidor 已提交
241
		if (element instanceof StackFrame) {
242 243
			this.focusStackFrame(element, event, false);
		}
244 245 246 247

		return super.onEnter(tree, event);
	}

I
isidor 已提交
248
	protected onUp(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
249 250 251 252 253 254
		super.onUp(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
255
	protected onPageUp(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
256 257 258 259 260 261
		super.onPageUp(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
262
	protected onDown(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
263 264 265 266 267 268
		super.onDown(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
269
	protected onPageDown(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
270 271 272 273 274 275
		super.onPageDown(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

276
	// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
I
isidor 已提交
277
	private showMoreStackFrames(tree: ITree, threadAndProcessIds: ThreadAndProcessIds): boolean {
I
isidor 已提交
278 279
		const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === threadAndProcessIds.processId).pop();
		const thread = process && process.getThread(threadAndProcessIds.threadId);
280
		if (thread) {
281
			thread.getCallStack(true)
282 283 284 285 286
				.done(() => tree.refresh(), errors.onUnexpectedError);
		}

		return true;
	}
287

I
isidor 已提交
288
	private focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
289 290
		this.debugService.setFocusedStackFrameAndEvaluate(stackFrame).done(null, errors.onUnexpectedError);

I
isidor 已提交
291 292 293 294
		if (stackFrame) {
			const sideBySide = (event && (event.ctrlKey || event.metaKey));
			this.debugService.openOrRevealSource(stackFrame.source, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
		}
295
	}
296 297 298
}


I
isidor 已提交
299
export class CallStackActionProvider implements IActionProvider {
I
isidor 已提交
300

J
Johannes Rieken 已提交
301
	constructor( @IInstantiationService private instantiationService: IInstantiationService, @debug.IDebugService private debugService: debug.IDebugService) {
I
isidor 已提交
302 303 304
		// noop
	}

I
isidor 已提交
305
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
306 307 308
		return false;
	}

I
isidor 已提交
309
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
310 311 312
		return TPromise.as([]);
	}

I
isidor 已提交
313 314
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Thread || element instanceof StackFrame;
I
isidor 已提交
315 316
	}

I
isidor 已提交
317 318 319 320
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
		if (element instanceof Thread) {
			const thread = <Thread>element;
A
Andre Weinand 已提交
321
			if (thread.stopped) {
I
isidor 已提交
322 323 324 325
				actions.push(this.instantiationService.createInstance(ContinueAction, ContinueAction.ID, ContinueAction.LABEL));
				actions.push(this.instantiationService.createInstance(StepOverAction, StepOverAction.ID, StepOverAction.LABEL));
				actions.push(this.instantiationService.createInstance(StepIntoAction, StepIntoAction.ID, StepIntoAction.LABEL));
				actions.push(this.instantiationService.createInstance(StepOutAction, StepOutAction.ID, StepOutAction.LABEL));
A
Andre Weinand 已提交
326
			} else {
I
isidor 已提交
327
				actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL));
A
Andre Weinand 已提交
328
			}
I
isidor 已提交
329
		} else if (element instanceof StackFrame) {
I
isidor 已提交
330
			const capabilities = this.debugService.getViewModel().focusedProcess.session.configuration.capabilities;
I
isidor 已提交
331
			if (typeof capabilities.supportsRestartFrame === 'boolean' && capabilities.supportsRestartFrame) {
I
isidor 已提交
332
				actions.push(this.instantiationService.createInstance(RestartFrameAction, RestartFrameAction.ID, RestartFrameAction.LABEL));
333
			}
I
isidor 已提交
334 335 336 337 338
		}

		return TPromise.as(actions);
	}

I
isidor 已提交
339
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
340 341 342 343
		return null;
	}
}

I
isidor 已提交
344
export class CallStackDataSource implements IDataSource {
E
Erich Gamma 已提交
345

I
isidor 已提交
346
	public getId(tree: ITree, element: any): string {
347 348 349
		if (typeof element === 'string') {
			return element;
		}
I
isidor 已提交
350

E
Erich Gamma 已提交
351 352 353
		return element.getId();
	}

I
isidor 已提交
354 355
	public hasChildren(tree: ITree, element: any): boolean {
		return element instanceof Model || element instanceof Process || (element instanceof Thread && (<Thread>element).stopped);
E
Erich Gamma 已提交
356 357
	}

I
isidor 已提交
358 359
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Thread) {
I
isidor 已提交
360
			return this.getThreadChildren(element);
E
Erich Gamma 已提交
361
		}
I
isidor 已提交
362
		if (element instanceof Model) {
363
			return TPromise.as(element.getProcesses());
364 365
		}

366
		const process = <debug.IProcess>element;
I
isidor 已提交
367
		return TPromise.as(process.getAllThreads());
E
Erich Gamma 已提交
368 369
	}

I
isidor 已提交
370
	private getThreadChildren(thread: debug.IThread): TPromise<any> {
371
		return thread.getCallStack().then((callStack: any[]) => {
I
isidor 已提交
372
			if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
373 374
				return callStack.concat([thread.stoppedDetails.framesErrorMessage]);
			}
I
isidor 已提交
375
			if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
I
isidor 已提交
376
				return callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]);
I
isidor 已提交
377 378 379 380 381 382
			}

			return callStack;
		});
	}

I
isidor 已提交
383
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
384
		return TPromise.as(null);
E
Erich Gamma 已提交
385 386 387 388
	}
}

interface IThreadTemplateData {
I
isidor 已提交
389
	thread: HTMLElement;
E
Erich Gamma 已提交
390
	name: HTMLElement;
I
isidor 已提交
391 392
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
E
Erich Gamma 已提交
393 394
}

I
isidor 已提交
395 396 397 398 399
interface IProcessTemplateData {
	process: HTMLElement;
	name: HTMLElement;
}

400 401 402 403
interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
404 405 406 407
interface ILoadMoreTemplateData {
	label: HTMLElement;
}

E
Erich Gamma 已提交
408 409
interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
410 411 412 413
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
E
Erich Gamma 已提交
414 415
}

I
isidor 已提交
416
export class CallStackRenderer implements IRenderer {
E
Erich Gamma 已提交
417 418 419

	private static THREAD_TEMPLATE_ID = 'thread';
	private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
420
	private static ERROR_TEMPLATE_ID = 'error';
I
isidor 已提交
421
	private static LOAD_MORE_TEMPLATE_ID = 'loadMore';
I
isidor 已提交
422
	private static PROCESS_TEMPLATE_ID = 'process';
E
Erich Gamma 已提交
423

J
Johannes Rieken 已提交
424
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
E
Erich Gamma 已提交
425 426 427
		// noop
	}

I
isidor 已提交
428
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
429
		return 22;
E
Erich Gamma 已提交
430 431
	}

I
isidor 已提交
432 433
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Process) {
I
isidor 已提交
434 435
			return CallStackRenderer.PROCESS_TEMPLATE_ID;
		}
I
isidor 已提交
436
		if (element instanceof Thread) {
E
Erich Gamma 已提交
437 438
			return CallStackRenderer.THREAD_TEMPLATE_ID;
		}
I
isidor 已提交
439
		if (element instanceof StackFrame) {
E
Erich Gamma 已提交
440 441
			return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
		}
442 443 444
		if (typeof element === 'string') {
			return CallStackRenderer.ERROR_TEMPLATE_ID;
		}
E
Erich Gamma 已提交
445

I
isidor 已提交
446
		return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
E
Erich Gamma 已提交
447 448
	}

I
isidor 已提交
449
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
450 451 452 453 454 455 456 457
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
			let data: IProcessTemplateData = Object.create(null);
			data.process = dom.append(container, $('.process'));
			data.name = dom.append(data.process, $('.name'));

			return data;
		}

I
isidor 已提交
458 459 460 461 462 463
		if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
			let data: ILoadMoreTemplateData = Object.create(null);
			data.label = dom.append(container, $('.load-more'));

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

			return data;
		}
E
Erich Gamma 已提交
470 471
		if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			let data: IThreadTemplateData = Object.create(null);
I
isidor 已提交
472 473 474 475
			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'));
E
Erich Gamma 已提交
476 477 478 479 480 481 482 483 484 485 486 487 488 489

			return data;
		}

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

		return data;
	}

I
isidor 已提交
490
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
491
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
I
isidor 已提交
492 493
			this.renderProcess(element, templateData);
		} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
E
Erich Gamma 已提交
494
			this.renderThread(element, templateData);
I
isidor 已提交
495
		} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
E
Erich Gamma 已提交
496
			this.renderStackFrame(element, templateData);
497 498
		} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			this.renderError(element, templateData);
I
isidor 已提交
499
		} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
I
isidor 已提交
500
			this.renderLoadMore(element, templateData);
E
Erich Gamma 已提交
501 502 503
		}
	}

I
isidor 已提交
504 505 506 507 508
	private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void {
		data.process.title = nls.localize('process', "Process");
		data.name.textContent = process.name;
	}

E
Erich Gamma 已提交
509
	private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
I
isidor 已提交
510
		data.thread.title = nls.localize('thread', "Thread");
E
Erich Gamma 已提交
511
		data.name.textContent = thread.name;
I
isidor 已提交
512 513
		data.stateLabel.textContent = thread.stopped ? nls.localize('paused', "paused")
			: nls.localize({ key: 'running', comment: ['indicates state'] }, "running");
E
Erich Gamma 已提交
514 515
	}

516 517
	private renderError(element: string, data: IErrorTemplateData) {
		data.label.textContent = element;
I
isidor 已提交
518
		data.label.title = element;
519 520
	}

I
isidor 已提交
521
	private renderLoadMore(element: any, data: ILoadMoreTemplateData): void {
I
isidor 已提交
522
		data.label.textContent = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
I
isidor 已提交
523 524
	}

E
Erich Gamma 已提交
525 526 527 528
	private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void {
		stackFrame.source.available ? dom.removeClass(data.stackFrame, 'disabled') : dom.addClass(data.stackFrame, 'disabled');
		data.file.title = stackFrame.source.uri.fsPath;
		data.label.textContent = stackFrame.name;
529
		data.label.title = stackFrame.name;
530
		data.fileName.textContent = getSourceName(stackFrame.source, this.contextService);
I
isidor 已提交
531
		if (stackFrame.lineNumber !== undefined) {
532
			data.lineNumber.textContent = `${stackFrame.lineNumber}`;
I
isidor 已提交
533 534 535 536
			dom.removeClass(data.lineNumber, 'unavailable');
		} else {
			dom.addClass(data.lineNumber, 'unavailable');
		}
E
Erich Gamma 已提交
537 538
	}

I
isidor 已提交
539
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
540 541 542 543
		// noop
	}
}

I
isidor 已提交
544
export class CallstackAccessibilityProvider implements IAccessibilityProvider {
545

J
Johannes Rieken 已提交
546
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
547 548 549
		// noop
	}

I
isidor 已提交
550 551 552
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
553
		}
I
isidor 已提交
554 555
		if (element instanceof StackFrame) {
			return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", (<StackFrame>element).name, (<StackFrame>element).lineNumber, getSourceName((<StackFrame>element).source, this.contextService));
556 557 558 559 560 561
		}

		return null;
	}
}

I
isidor 已提交
562
// variables
E
Erich Gamma 已提交
563

I
isidor 已提交
564
export class VariablesActionProvider implements IActionProvider {
E
Erich Gamma 已提交
565

I
isidor 已提交
566 567
	constructor(private instantiationService: IInstantiationService) {
		// noop
E
Erich Gamma 已提交
568 569
	}

I
isidor 已提交
570
	public hasActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
571 572 573
		return false;
	}

I
isidor 已提交
574
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
575
		return TPromise.as([]);
I
isidor 已提交
576 577
	}

I
isidor 已提交
578 579
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Variable;
E
Erich Gamma 已提交
580 581
	}

I
isidor 已提交
582 583 584
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		let actions: IAction[] = [];
		const variable = <Variable>element;
585
		if (!variable.hasChildren) {
I
isidor 已提交
586
			actions.push(this.instantiationService.createInstance(SetValueAction, SetValueAction.ID, SetValueAction.LABEL, variable));
587
			actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable));
I
isidor 已提交
588
			actions.push(new Separator());
E
Erich Gamma 已提交
589 590
		}

I
isidor 已提交
591
		actions.push(this.instantiationService.createInstance(AddToWatchExpressionsAction, AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable));
A
Alex Dima 已提交
592
		return TPromise.as(actions);
E
Erich Gamma 已提交
593
	}
I
isidor 已提交
594

I
isidor 已提交
595
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
596 597
		return null;
	}
E
Erich Gamma 已提交
598 599
}

I
isidor 已提交
600
export class VariablesDataSource implements IDataSource {
E
Erich Gamma 已提交
601

I
isidor 已提交
602
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
603 604 605
		return element.getId();
	}

I
isidor 已提交
606 607
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof ViewModel || element instanceof Scope) {
E
Erich Gamma 已提交
608 609 610
			return true;
		}

I
isidor 已提交
611
		let variable = <Variable>element;
612
		return variable.hasChildren && !equalsIgnoreCase(variable.value, 'null');
E
Erich Gamma 已提交
613 614
	}

I
isidor 已提交
615 616 617
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof ViewModel) {
			const focusedStackFrame = (<ViewModel>element).focusedStackFrame;
618
			return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
E
Erich Gamma 已提交
619 620
		}

I
isidor 已提交
621
		let scope = <Scope>element;
622
		return scope.getChildren();
E
Erich Gamma 已提交
623 624
	}

I
isidor 已提交
625
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
626
		return TPromise.as(null);
E
Erich Gamma 已提交
627 628 629 630 631 632 633 634 635 636 637 638 639
	}
}

interface IScopeTemplateData {
	name: HTMLElement;
}

export interface IVariableTemplateData {
	expression: HTMLElement;
	name: HTMLElement;
	value: HTMLElement;
}

I
isidor 已提交
640
export class VariablesRenderer implements IRenderer {
E
Erich Gamma 已提交
641 642 643 644

	private static SCOPE_TEMPLATE_ID = 'scope';
	private static VARIABLE_TEMPLATE_ID = 'variable';

I
isidor 已提交
645 646 647 648 649 650 651
	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		// noop
	}

I
isidor 已提交
652
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
653
		return 22;
E
Erich Gamma 已提交
654 655
	}

I
isidor 已提交
656 657
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Scope) {
E
Erich Gamma 已提交
658 659
			return VariablesRenderer.SCOPE_TEMPLATE_ID;
		}
I
isidor 已提交
660
		if (element instanceof Variable) {
E
Erich Gamma 已提交
661 662 663 664 665 666
			return VariablesRenderer.VARIABLE_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
667
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
E
Erich Gamma 已提交
668 669 670 671 672 673 674 675
		if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
			let data: IScopeTemplateData = Object.create(null);
			data.name = dom.append(container, $('.scope'));

			return data;
		}

		let data: IVariableTemplateData = Object.create(null);
676
		data.expression = dom.append(container, $('.expression'));
E
Erich Gamma 已提交
677 678 679 680 681 682
		data.name = dom.append(data.expression, $('span.name'));
		data.value = dom.append(data.expression, $('span.value'));

		return data;
	}

I
isidor 已提交
683
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
684 685 686
		if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
			this.renderScope(element, templateData);
		} else {
I
isidor 已提交
687
			const variable = <Variable>element;
688 689 690 691 692 693 694
			if (variable === this.debugService.getViewModel().getSelectedExpression() || variable.errorMessage) {
				renderRenameBox(this.debugService, this.contextViewService, tree, variable, (<IVariableTemplateData>templateData).expression, {
					initialValue: variable.value,
					ariaLabel: nls.localize('variableValueAriaLabel', "Type new variable value"),
					validationOptions: {
						validation: (value: string) => variable.errorMessage ? ({ content: variable.errorMessage }) : null
					}
I
isidor 已提交
695
				});
I
isidor 已提交
696
			} else {
697
				renderVariable(tree, variable, templateData, true);
I
isidor 已提交
698
			}
E
Erich Gamma 已提交
699 700 701
		}
	}

I
isidor 已提交
702
	private renderScope(scope: Scope, data: IScopeTemplateData): void {
E
Erich Gamma 已提交
703 704 705
		data.name.textContent = scope.name;
	}

I
isidor 已提交
706
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
707 708 709 710
		// noop
	}
}

I
isidor 已提交
711
export class VariablesAccessibilityProvider implements IAccessibilityProvider {
712

I
isidor 已提交
713 714 715
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Scope) {
			return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", (<Scope>element).name);
716
		}
I
isidor 已提交
717 718
		if (element instanceof Variable) {
			return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", (<Variable>element).name, (<Variable>element).value);
719 720 721 722 723 724
		}

		return null;
	}
}

725 726
export class VariablesController extends BaseDebugController {

I
isidor 已提交
727
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
728
		super(debugService, contextMenuService, actionProvider);
A
Alexandru Dima 已提交
729
		this.downKeyBindingDispatcher.set(KeyCode.Enter, this.setSelectedExpression.bind(this));
730 731
	}

I
isidor 已提交
732
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
733
		// double click on primitive value: open input box to be able to set the value
I
isidor 已提交
734
		if (element instanceof Variable && event.detail === 2) {
735
			const expression = <debug.IExpression>element;
736
			if (!expression.hasChildren) {
737 738 739 740 741 742 743
				this.debugService.getViewModel().setSelectedExpression(expression);
			}
			return true;
		}

		return super.onLeftClick(tree, element, event);
	}
I
isidor 已提交
744

I
isidor 已提交
745
	protected setSelectedExpression(tree: ITree, event: KeyboardEvent): boolean {
I
isidor 已提交
746
		const element = tree.getFocus();
747
		if (element instanceof Variable && !element.hasChildren) {
I
isidor 已提交
748
			this.debugService.getViewModel().setSelectedExpression(element);
749
			return true;
I
isidor 已提交
750 751
		}

752
		return false;
I
isidor 已提交
753
	}
754 755
}

I
isidor 已提交
756
// watch expressions
E
Erich Gamma 已提交
757

I
isidor 已提交
758
export class WatchExpressionsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
759 760 761 762 763 764 765

	private instantiationService: IInstantiationService;

	constructor(instantiationService: IInstantiationService) {
		this.instantiationService = instantiationService;
	}

I
isidor 已提交
766 767
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Expression && !!element.name;
E
Erich Gamma 已提交
768 769
	}

I
isidor 已提交
770
	public hasSecondaryActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
771 772 773
		return true;
	}

I
isidor 已提交
774
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
775
		return TPromise.as(this.getExpressionActions());
E
Erich Gamma 已提交
776 777
	}

I
isidor 已提交
778 779
	public getExpressionActions(): IAction[] {
		return [this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL)];
E
Erich Gamma 已提交
780 781
	}

I
isidor 已提交
782 783 784 785 786 787
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
		if (element instanceof Expression) {
			const expression = <Expression>element;
			actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
			actions.push(this.instantiationService.createInstance(EditWatchExpressionAction, EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL, expression));
788
			if (!expression.hasChildren) {
789
				actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value));
E
Erich Gamma 已提交
790
			}
I
isidor 已提交
791
			actions.push(new Separator());
E
Erich Gamma 已提交
792

I
isidor 已提交
793 794
			actions.push(this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL));
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
795
		} else {
I
isidor 已提交
796 797 798
			actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
			if (element instanceof Variable) {
				const variable = <Variable>element;
799
				if (!variable.hasChildren) {
800
					actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable.value));
E
Erich Gamma 已提交
801
				}
I
isidor 已提交
802
				actions.push(new Separator());
E
Erich Gamma 已提交
803
			}
I
isidor 已提交
804
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
805 806
		}

A
Alex Dima 已提交
807
		return TPromise.as(actions);
E
Erich Gamma 已提交
808
	}
I
isidor 已提交
809

I
isidor 已提交
810
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
811 812
		return null;
	}
E
Erich Gamma 已提交
813 814
}

I
isidor 已提交
815
export class WatchExpressionsDataSource implements IDataSource {
E
Erich Gamma 已提交
816

I
isidor 已提交
817
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
818 819 820
		return element.getId();
	}

I
isidor 已提交
821 822
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof Model) {
E
Erich Gamma 已提交
823 824 825
			return true;
		}

I
isidor 已提交
826
		const watchExpression = <Expression>element;
827
		return watchExpression.hasChildren && !equalsIgnoreCase(watchExpression.value, 'null');
E
Erich Gamma 已提交
828 829
	}

I
isidor 已提交
830 831 832
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Model) {
			return TPromise.as((<Model>element).getWatchExpressions());
E
Erich Gamma 已提交
833 834
		}

I
isidor 已提交
835
		let expression = <Expression>element;
836
		return expression.getChildren();
E
Erich Gamma 已提交
837 838
	}

I
isidor 已提交
839
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
840
		return TPromise.as(null);
E
Erich Gamma 已提交
841 842 843 844
	}
}

interface IWatchExpressionTemplateData extends IVariableTemplateData {
I
isidor 已提交
845
	actionBar: ActionBar;
E
Erich Gamma 已提交
846 847
}

I
isidor 已提交
848
export class WatchExpressionsRenderer implements IRenderer {
E
Erich Gamma 已提交
849 850 851 852 853 854

	private static WATCH_EXPRESSION_TEMPLATE_ID = 'watchExpression';
	private static VARIABLE_TEMPLATE_ID = 'variables';
	private toDispose: lifecycle.IDisposable[];
	private actionProvider: WatchExpressionsActionProvider;

I
isidor 已提交
855
	constructor(
I
isidor 已提交
856 857
		actionProvider: IActionProvider,
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
858 859 860 861
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		this.toDispose = [];
862
		this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
E
Erich Gamma 已提交
863 864
	}

I
isidor 已提交
865
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
866
		return 22;
E
Erich Gamma 已提交
867 868
	}

I
isidor 已提交
869 870
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Expression) {
E
Erich Gamma 已提交
871 872 873 874 875 876
			return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
		}

		return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
	}

I
isidor 已提交
877
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
E
Erich Gamma 已提交
878 879
		let data: IWatchExpressionTemplateData = Object.create(null);
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
I
isidor 已提交
880
			data.actionBar = new ActionBar(container, { actionRunner: this.actionRunner });
881
			data.actionBar.push(this.actionProvider.getExpressionActions(), { icon: true, label: false });
E
Erich Gamma 已提交
882 883
		}

884
		data.expression = dom.append(container, $('.expression'));
E
Erich Gamma 已提交
885 886 887 888 889 890
		data.name = dom.append(data.expression, $('span.name'));
		data.value = dom.append(data.expression, $('span.value'));

		return data;
	}

I
isidor 已提交
891
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
892 893 894
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			this.renderWatchExpression(tree, element, templateData);
		} else {
895
			renderVariable(tree, element, templateData, true);
E
Erich Gamma 已提交
896 897 898
		}
	}

I
isidor 已提交
899
	private renderWatchExpression(tree: ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
E
Erich Gamma 已提交
900
		let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
I
isidor 已提交
901
		if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
I
isidor 已提交
902 903 904 905 906
			renderRenameBox(this.debugService, this.contextViewService, tree, watchExpression, data.expression, {
				initialValue: watchExpression.name,
				placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
				ariaLabel: nls.localize('watchExpressionInputAriaLabel', "Type watch expression")
			});
E
Erich Gamma 已提交
907 908 909
		}
		data.actionBar.context = watchExpression;

910
		data.name.textContent = watchExpression.name;
911
		if (watchExpression.value) {
912
			data.name.textContent += ':';
913
			renderExpressionValue(watchExpression, data.value, true, MAX_VALUE_RENDER_LENGTH_IN_VIEWLET);
914
			data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
E
Erich Gamma 已提交
915 916 917
		}
	}

I
isidor 已提交
918
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
919 920 921
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			(<IWatchExpressionTemplateData>templateData).actionBar.dispose();
		}
E
Erich Gamma 已提交
922 923 924
	}

	public dispose(): void {
J
Joao Moreno 已提交
925
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
926 927 928
	}
}

I
isidor 已提交
929
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
930

I
isidor 已提交
931 932 933
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Expression) {
			return nls.localize('watchExpressionAriaLabel', "{0} value {1}, watch, debug", (<Expression>element).name, (<Expression>element).value);
934
		}
I
isidor 已提交
935 936
		if (element instanceof Variable) {
			return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
937 938 939 940 941 942
		}

		return null;
	}
}

E
Erich Gamma 已提交
943 944
export class WatchExpressionsController extends BaseDebugController {

I
isidor 已提交
945
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
E
Erich Gamma 已提交
946 947 948
		super(debugService, contextMenuService, actionProvider);

		if (isMacintosh) {
A
Alexandru Dima 已提交
949
			this.downKeyBindingDispatcher.set(KeyCode.Enter, this.onRename.bind(this));
E
Erich Gamma 已提交
950
		} else {
A
Alexandru Dima 已提交
951
			this.downKeyBindingDispatcher.set(KeyCode.F2, this.onRename.bind(this));
E
Erich Gamma 已提交
952 953 954
		}
	}

I
isidor 已提交
955
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
I
isidor 已提交
956
		// double click on primitive value: open input box to be able to select and copy value.
I
isidor 已提交
957
		if (element instanceof Expression && event.detail === 2) {
958
			const expression = <debug.IExpression>element;
959
			if (!expression.hasChildren) {
E
Erich Gamma 已提交
960 961 962 963 964 965 966 967
				this.debugService.getViewModel().setSelectedExpression(expression);
			}
			return true;
		}

		return super.onLeftClick(tree, element, event);
	}

I
isidor 已提交
968
	protected onRename(tree: ITree, event: KeyboardEvent): boolean {
I
isidor 已提交
969
		const element = tree.getFocus();
I
isidor 已提交
970 971
		if (element instanceof Expression) {
			const watchExpression = <Expression>element;
972
			if (!watchExpression.hasChildren) {
E
Erich Gamma 已提交
973 974 975 976 977 978 979 980
				this.debugService.getViewModel().setSelectedExpression(watchExpression);
			}
			return true;
		}

		return false;
	}

I
isidor 已提交
981
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
982
		const element = tree.getFocus();
I
isidor 已提交
983 984
		if (element instanceof Expression) {
			const we = <Expression>element;
985
			this.debugService.removeWatchExpressions(we.getId());
E
Erich Gamma 已提交
986 987 988 989 990 991 992 993

			return true;
		}

		return false;
	}
}

I
isidor 已提交
994
// breakpoints
E
Erich Gamma 已提交
995

I
isidor 已提交
996
export class BreakpointsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
997 998

	constructor(private instantiationService: IInstantiationService) {
I
isidor 已提交
999
		// noop
E
Erich Gamma 已提交
1000 1001
	}

I
isidor 已提交
1002 1003
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint;
E
Erich Gamma 已提交
1004 1005
	}

I
isidor 已提交
1006 1007
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
E
Erich Gamma 已提交
1008 1009
	}

I
isidor 已提交
1010 1011
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
		if (element instanceof Breakpoint) {
A
Alex Dima 已提交
1012
			return TPromise.as(this.getBreakpointActions());
E
Erich Gamma 已提交
1013 1014
		}

A
Alex Dima 已提交
1015
		return TPromise.as([]);
E
Erich Gamma 已提交
1016 1017
	}

I
isidor 已提交
1018 1019
	public getBreakpointActions(): IAction[] {
		return [this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL)];
E
Erich Gamma 已提交
1020 1021
	}

I
isidor 已提交
1022 1023 1024
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [this.instantiationService.createInstance(ToggleEnablementAction, ToggleEnablementAction.ID, ToggleEnablementAction.LABEL)];
		actions.push(new Separator());
E
Erich Gamma 已提交
1025

I
isidor 已提交
1026 1027
		if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
1028
		}
I
isidor 已提交
1029 1030
		actions.push(this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL));
		actions.push(new Separator());
E
Erich Gamma 已提交
1031

I
isidor 已提交
1032 1033
		actions.push(this.instantiationService.createInstance(ToggleBreakpointsActivatedAction, ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL));
		actions.push(new Separator());
E
Erich Gamma 已提交
1034

I
isidor 已提交
1035 1036 1037
		actions.push(this.instantiationService.createInstance(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL));
		actions.push(this.instantiationService.createInstance(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL));
		actions.push(new Separator());
E
Erich Gamma 已提交
1038

I
isidor 已提交
1039 1040 1041
		actions.push(this.instantiationService.createInstance(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL));
		if (element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RenameFunctionBreakpointAction, RenameFunctionBreakpointAction.ID, RenameFunctionBreakpointAction.LABEL));
1042
		}
I
isidor 已提交
1043
		actions.push(new Separator());
I
isidor 已提交
1044

I
isidor 已提交
1045
		actions.push(this.instantiationService.createInstance(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL));
E
Erich Gamma 已提交
1046

A
Alex Dima 已提交
1047
		return TPromise.as(actions);
E
Erich Gamma 已提交
1048
	}
I
isidor 已提交
1049

I
isidor 已提交
1050
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
1051 1052
		return null;
	}
E
Erich Gamma 已提交
1053 1054
}

I
isidor 已提交
1055
export class BreakpointsDataSource implements IDataSource {
E
Erich Gamma 已提交
1056

I
isidor 已提交
1057
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
1058 1059 1060
		return element.getId();
	}

I
isidor 已提交
1061 1062
	public hasChildren(tree: ITree, element: any): boolean {
		return element instanceof Model;
E
Erich Gamma 已提交
1063 1064
	}

I
isidor 已提交
1065 1066
	public getChildren(tree: ITree, element: any): TPromise<any> {
		const model = <Model>element;
1067
		const exBreakpoints = <debug.IEnablement[]>model.getExceptionBreakpoints();
E
Erich Gamma 已提交
1068

A
Alex Dima 已提交
1069
		return TPromise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
E
Erich Gamma 已提交
1070 1071
	}

I
isidor 已提交
1072
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
1073
		return TPromise.as(null);
E
Erich Gamma 已提交
1074 1075 1076 1077
	}
}

interface IExceptionBreakpointTemplateData {
1078
	breakpoint: HTMLElement;
E
Erich Gamma 已提交
1079 1080 1081 1082 1083 1084
	name: HTMLElement;
	checkbox: HTMLInputElement;
	toDisposeBeforeRender: lifecycle.IDisposable[];
}

interface IBreakpointTemplateData extends IExceptionBreakpointTemplateData {
I
isidor 已提交
1085
	actionBar: ActionBar;
E
Erich Gamma 已提交
1086 1087 1088 1089
	lineNumber: HTMLElement;
	filePath: HTMLElement;
}

1090
interface IFunctionBreakpointTemplateData extends IExceptionBreakpointTemplateData {
I
isidor 已提交
1091
	actionBar: ActionBar;
1092 1093
}

I
isidor 已提交
1094
export class BreakpointsRenderer implements IRenderer {
E
Erich Gamma 已提交
1095 1096

	private static EXCEPTION_BREAKPOINT_TEMPLATE_ID = 'exceptionBreakpoint';
1097
	private static FUNCTION_BREAKPOINT_TEMPLATE_ID = 'functionBreakpoint';
E
Erich Gamma 已提交
1098 1099 1100 1101
	private static BREAKPOINT_TEMPLATE_ID = 'breakpoint';

	constructor(
		private actionProvider: BreakpointsActionProvider,
I
isidor 已提交
1102
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
1103
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
1104 1105
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
E
Erich Gamma 已提交
1106 1107 1108 1109
	) {
		// noop
	}

I
isidor 已提交
1110
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
1111
		return 22;
E
Erich Gamma 已提交
1112 1113
	}

I
isidor 已提交
1114 1115
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
E
Erich Gamma 已提交
1116 1117
			return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1118
		if (element instanceof FunctionBreakpoint) {
1119 1120
			return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1121
		if (element instanceof ExceptionBreakpoint) {
E
Erich Gamma 已提交
1122 1123 1124 1125 1126 1127
			return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
1128
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
1129
		const data: IBreakpointTemplateData = Object.create(null);
1130
		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
I
isidor 已提交
1131
			data.actionBar = new ActionBar(container, { actionRunner: this.actionRunner });
E
Erich Gamma 已提交
1132 1133 1134
			data.actionBar.push(this.actionProvider.getBreakpointActions(), { icon: true, label: false });
		}

1135
		data.breakpoint = dom.append(container, $('.breakpoint'));
E
Erich Gamma 已提交
1136 1137
		data.toDisposeBeforeRender = [];

1138
		data.checkbox = <HTMLInputElement>$('input');
E
Erich Gamma 已提交
1139
		data.checkbox.type = 'checkbox';
1140

1141
		dom.append(data.breakpoint, data.checkbox);
E
Erich Gamma 已提交
1142

1143
		data.name = dom.append(data.breakpoint, $('span.name'));
E
Erich Gamma 已提交
1144 1145

		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
1146 1147
			data.lineNumber = dom.append(data.breakpoint, $('span.line-number'));
			data.filePath = dom.append(data.breakpoint, $('span.file-path'));
E
Erich Gamma 已提交
1148 1149 1150 1151 1152
		}

		return data;
	}

I
isidor 已提交
1153
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
J
Joao Moreno 已提交
1154
		templateData.toDisposeBeforeRender = lifecycle.dispose(templateData.toDisposeBeforeRender);
1155
		templateData.toDisposeBeforeRender.push(dom.addStandardDisposableListener(templateData.checkbox, 'change', (e) => {
1156
			this.debugService.enableOrDisableBreakpoints(!element.enabled, element);
1157 1158
		}));

E
Erich Gamma 已提交
1159 1160
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderExceptionBreakpoint(element, templateData);
1161 1162
		} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderFunctionBreakpoint(tree, element, templateData);
E
Erich Gamma 已提交
1163 1164 1165 1166 1167 1168
		} else {
			this.renderBreakpoint(tree, element, templateData);
		}
	}

	private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IExceptionBreakpointTemplateData): void {
1169
		data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;;
1170
		data.breakpoint.title = data.name.textContent;
E
Erich Gamma 已提交
1171
		data.checkbox.checked = exceptionBreakpoint.enabled;
1172
	}
E
Erich Gamma 已提交
1173

I
isidor 已提交
1174
	private renderFunctionBreakpoint(tree: ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IFunctionBreakpointTemplateData): void {
I
isidor 已提交
1175 1176
		const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
		if (!functionBreakpoint.name || (selected && selected.getId() === functionBreakpoint.getId())) {
I
isidor 已提交
1177 1178 1179 1180 1181
			renderRenameBox(this.debugService, this.contextViewService, tree, functionBreakpoint, data.breakpoint, {
				initialValue: functionBreakpoint.name,
				placeholder: nls.localize('functionBreakpointPlaceholder', "Function to break on"),
				ariaLabel: nls.localize('functionBreakPointInputAriaLabel', "Type function breakpoint")
			});
1182 1183 1184
		} else {
			data.name.textContent = functionBreakpoint.name;
			data.checkbox.checked = functionBreakpoint.enabled;
1185 1186 1187
			data.breakpoint.title = functionBreakpoint.name;

			// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
1188
			const process = this.debugService.getViewModel().focusedProcess;
I
isidor 已提交
1189
			if ((process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
1190
				tree.addTraits('disabled', [functionBreakpoint]);
I
isidor 已提交
1191
				if (process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) {
1192 1193 1194 1195 1196
					data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
				}
			} else {
				tree.removeTraits('disabled', [functionBreakpoint]);
			}
1197
		}
1198
		data.actionBar.context = functionBreakpoint;
E
Erich Gamma 已提交
1199 1200
	}

I
isidor 已提交
1201
	private renderBreakpoint(tree: ITree, breakpoint: debug.IBreakpoint, data: IBreakpointTemplateData): void {
1202
		this.debugService.getModel().areBreakpointsActivated() ? tree.removeTraits('disabled', [breakpoint]) : tree.addTraits('disabled', [breakpoint]);
E
Erich Gamma 已提交
1203

1204
		data.name.textContent = getPathLabel(paths.basename(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1205
		data.lineNumber.textContent = breakpoint.desiredLineNumber !== breakpoint.lineNumber ? breakpoint.desiredLineNumber + ' \u2192 ' + breakpoint.lineNumber : '' + breakpoint.lineNumber;
1206
		data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1207 1208
		data.checkbox.checked = breakpoint.enabled;
		data.actionBar.context = breakpoint;
I
isidor 已提交
1209

I
isidor 已提交
1210
		const debugActive = this.debugService.state === debug.State.Running || this.debugService.state === debug.State.Stopped || this.debugService.state === debug.State.Initializing;
I
isidor 已提交
1211 1212 1213 1214 1215
		if (debugActive && !breakpoint.verified) {
			tree.addTraits('disabled', [breakpoint]);
			if (breakpoint.message) {
				data.breakpoint.title = breakpoint.message;
			}
1216 1217
		} else if (breakpoint.condition || breakpoint.hitCondition) {
			data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
1218
		}
E
Erich Gamma 已提交
1219 1220
	}

I
isidor 已提交
1221
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
1222
		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
E
Erich Gamma 已提交
1223 1224 1225 1226 1227
			templateData.actionBar.dispose();
		}
	}
}

I
isidor 已提交
1228
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
1229

J
Johannes Rieken 已提交
1230
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
1231 1232 1233
		// noop
	}

I
isidor 已提交
1234 1235
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
1236
			return nls.localize('breakpointAriaLabel', "Breakpoint line {0} {1}, breakpoints, debug", (<Breakpoint>element).lineNumber, getPathLabel(paths.basename((<Breakpoint>element).uri.fsPath), this.contextService), this.contextService);
1237
		}
I
isidor 已提交
1238 1239
		if (element instanceof FunctionBreakpoint) {
			return nls.localize('functionBreakpointAriaLabel', "Function breakpoint {0}, breakpoints, debug", (<FunctionBreakpoint>element).name);
1240
		}
I
isidor 已提交
1241 1242
		if (element instanceof ExceptionBreakpoint) {
			return nls.localize('exceptionBreakpointAriaLabel', "Exception breakpoint {0}, breakpoints, debug", (<ExceptionBreakpoint>element).filter);
1243 1244 1245 1246 1247 1248
		}

		return null;
	}
}

E
Erich Gamma 已提交
1249 1250
export class BreakpointsController extends BaseDebugController {

I
isidor 已提交
1251
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
I
isidor 已提交
1252 1253
		super(debugService, contextMenuService, actionProvider);
		if (isMacintosh) {
A
Alexandru Dima 已提交
1254
			this.downKeyBindingDispatcher.set(KeyCode.Enter, this.onRename.bind(this));
I
isidor 已提交
1255
		} else {
A
Alexandru Dima 已提交
1256
			this.downKeyBindingDispatcher.set(KeyCode.F2, this.onRename.bind(this));
I
isidor 已提交
1257 1258 1259
		}
	}

I
isidor 已提交
1260 1261
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
		if (element instanceof FunctionBreakpoint && event.detail === 2) {
I
isidor 已提交
1262 1263 1264
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1265
		if (element instanceof Breakpoint) {
1266 1267
			this.openBreakpointSource(element, event, true);
		}
I
isidor 已提交
1268 1269

		return super.onLeftClick(tree, element, event);
E
Erich Gamma 已提交
1270 1271
	}

I
isidor 已提交
1272
	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
1273
		const element = tree.getFocus();
I
isidor 已提交
1274
		if (element instanceof FunctionBreakpoint && element.name) {
1275 1276 1277
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1278
		if (element instanceof Breakpoint) {
1279 1280 1281 1282 1283 1284
			this.openBreakpointSource(element, event, false);
		}

		return super.onEnter(tree, event);
	}

I
isidor 已提交
1285
	protected onSpace(tree: ITree, event: IKeyboardEvent): boolean {
1286 1287
		super.onSpace(tree, event);
		const element = <debug.IEnablement>tree.getFocus();
1288
		this.debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
1289 1290 1291 1292

		return true;
	}

I
isidor 已提交
1293
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
1294
		const element = tree.getFocus();
I
isidor 已提交
1295 1296
		if (element instanceof Breakpoint) {
			this.debugService.removeBreakpoints((<Breakpoint>element).getId()).done(null, errors.onUnexpectedError);
1297
			return true;
I
isidor 已提交
1298 1299
		} else if (element instanceof FunctionBreakpoint) {
			const fbp = <FunctionBreakpoint>element;
1300
			this.debugService.removeFunctionBreakpoints(fbp.getId()).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
1301 1302 1303 1304 1305 1306

			return true;
		}

		return false;
	}
1307

1308
	private openBreakpointSource(breakpoint: Breakpoint, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
1309 1310 1311 1312 1313
		if (!breakpoint.source.inMemory) {
			const sideBySide = (event && (event.ctrlKey || event.metaKey));
			this.debugService.openOrRevealSource(breakpoint.source, breakpoint.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
		}
	}
E
Erich Gamma 已提交
1314
}