debugViewer.ts 49.5 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';
I
isidor 已提交
16
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
I
isidor 已提交
17 18 19
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';
I
isidor 已提交
20
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, DRAG_OVER_ACCEPT, IDragAndDropData, IDragOverReaction } from 'vs/base/parts/tree/browser/tree';
J
Johannes Rieken 已提交
21
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
I
isidor 已提交
22
import { DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
I
isidor 已提交
23 24 25 26 27
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
	if (showChanged && (<any>expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) {
60 61 62
		// 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);
		}
229
		this.focusStackFrame(element, event, true);
230 231 232 233

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

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

		return super.onEnter(tree, event);
	}

I
isidor 已提交
244
	protected onUp(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
245 246 247 248 249 250
		super.onUp(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
251
	protected onPageUp(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
252 253 254 255 256 257
		super.onPageUp(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
258
	protected onDown(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
259 260 261 262 263 264
		super.onDown(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

I
isidor 已提交
265
	protected onPageDown(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
266 267 268 269 270 271
		super.onPageDown(tree, event);
		this.focusStackFrame(tree.getFocus(), event, true);

		return true;
	}

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

		return true;
	}
283

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
	private focusStackFrame(element: any, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
		let stackFrame: debug.IStackFrame;
		let process: debug.IProcess;
		if (element instanceof StackFrame) {
			stackFrame = element;
			process = element.thread.process;
		}
		if (element instanceof Thread) {
			process = element.process;
		}
		if (element instanceof Process) {
			process = element;
		}

		this.debugService.setFocusedStackFrameAndEvaluate(stackFrame, process).done(null, errors.onUnexpectedError);
299

I
isidor 已提交
300 301
		if (stackFrame) {
			const sideBySide = (event && (event.ctrlKey || event.metaKey));
302
			this.debugService.openOrRevealSource(stackFrame.source.uri, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
I
isidor 已提交
303
		}
304
	}
305 306 307
}


I
isidor 已提交
308
export class CallStackActionProvider implements IActionProvider {
I
isidor 已提交
309

J
Johannes Rieken 已提交
310
	constructor( @IInstantiationService private instantiationService: IInstantiationService, @debug.IDebugService private debugService: debug.IDebugService) {
I
isidor 已提交
311 312 313
		// noop
	}

I
isidor 已提交
314
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
315 316 317
		return false;
	}

I
isidor 已提交
318
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
319 320 321
		return TPromise.as([]);
	}

I
isidor 已提交
322 323
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Thread || element instanceof StackFrame;
I
isidor 已提交
324 325
	}

I
isidor 已提交
326 327 328 329
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
		if (element instanceof Thread) {
			const thread = <Thread>element;
A
Andre Weinand 已提交
330
			if (thread.stopped) {
I
isidor 已提交
331 332 333 334
				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 已提交
335
			} else {
I
isidor 已提交
336
				actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL));
A
Andre Weinand 已提交
337
			}
I
isidor 已提交
338
		} else if (element instanceof StackFrame) {
I
isidor 已提交
339
			const capabilities = this.debugService.getViewModel().focusedProcess.session.configuration.capabilities;
I
isidor 已提交
340
			if (typeof capabilities.supportsRestartFrame === 'boolean' && capabilities.supportsRestartFrame) {
I
isidor 已提交
341
				actions.push(this.instantiationService.createInstance(RestartFrameAction, RestartFrameAction.ID, RestartFrameAction.LABEL));
342
			}
I
isidor 已提交
343 344 345 346 347
		}

		return TPromise.as(actions);
	}

I
isidor 已提交
348
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
349 350 351 352
		return null;
	}
}

I
isidor 已提交
353
export class CallStackDataSource implements IDataSource {
E
Erich Gamma 已提交
354

I
isidor 已提交
355
	public getId(tree: ITree, element: any): string {
356 357 358
		if (typeof element === 'string') {
			return element;
		}
I
isidor 已提交
359

E
Erich Gamma 已提交
360 361 362
		return element.getId();
	}

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

I
isidor 已提交
367 368
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Thread) {
I
isidor 已提交
369
			return this.getThreadChildren(element);
E
Erich Gamma 已提交
370
		}
I
isidor 已提交
371
		if (element instanceof Model) {
372
			return TPromise.as(element.getProcesses());
373 374
		}

375
		const process = <debug.IProcess>element;
I
isidor 已提交
376
		return TPromise.as(process.getAllThreads());
E
Erich Gamma 已提交
377 378
	}

I
isidor 已提交
379
	private getThreadChildren(thread: debug.IThread): TPromise<any> {
380
		return thread.getCallStack().then((callStack: any[]) => {
I
isidor 已提交
381
			if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
382 383
				return callStack.concat([thread.stoppedDetails.framesErrorMessage]);
			}
I
isidor 已提交
384
			if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
I
isidor 已提交
385
				return callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]);
I
isidor 已提交
386 387 388 389 390 391
			}

			return callStack;
		});
	}

I
isidor 已提交
392
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
393
		return TPromise.as(null);
E
Erich Gamma 已提交
394 395 396 397
	}
}

interface IThreadTemplateData {
I
isidor 已提交
398
	thread: HTMLElement;
E
Erich Gamma 已提交
399
	name: HTMLElement;
I
isidor 已提交
400 401
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
E
Erich Gamma 已提交
402 403
}

I
isidor 已提交
404 405 406
interface IProcessTemplateData {
	process: HTMLElement;
	name: HTMLElement;
I
isidor 已提交
407 408
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
409 410
}

411 412 413 414
interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
415 416 417 418
interface ILoadMoreTemplateData {
	label: HTMLElement;
}

E
Erich Gamma 已提交
419 420
interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
421 422 423 424
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
E
Erich Gamma 已提交
425 426
}

I
isidor 已提交
427
export class CallStackRenderer implements IRenderer {
E
Erich Gamma 已提交
428 429 430

	private static THREAD_TEMPLATE_ID = 'thread';
	private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
431
	private static ERROR_TEMPLATE_ID = 'error';
I
isidor 已提交
432
	private static LOAD_MORE_TEMPLATE_ID = 'loadMore';
I
isidor 已提交
433
	private static PROCESS_TEMPLATE_ID = 'process';
E
Erich Gamma 已提交
434

J
Johannes Rieken 已提交
435
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
E
Erich Gamma 已提交
436 437 438
		// noop
	}

I
isidor 已提交
439
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
440
		return 22;
E
Erich Gamma 已提交
441 442
	}

I
isidor 已提交
443 444
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Process) {
I
isidor 已提交
445 446
			return CallStackRenderer.PROCESS_TEMPLATE_ID;
		}
I
isidor 已提交
447
		if (element instanceof Thread) {
E
Erich Gamma 已提交
448 449
			return CallStackRenderer.THREAD_TEMPLATE_ID;
		}
I
isidor 已提交
450
		if (element instanceof StackFrame) {
E
Erich Gamma 已提交
451 452
			return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
		}
453 454 455
		if (typeof element === 'string') {
			return CallStackRenderer.ERROR_TEMPLATE_ID;
		}
E
Erich Gamma 已提交
456

I
isidor 已提交
457
		return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
E
Erich Gamma 已提交
458 459
	}

I
isidor 已提交
460
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
461 462 463 464
		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'));
I
isidor 已提交
465 466
			data.state = dom.append(data.process, $('.state'));
			data.stateLabel = dom.append(data.state, $('span.label'));
I
isidor 已提交
467 468 469 470

			return data;
		}

I
isidor 已提交
471 472 473 474 475 476
		if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
			let data: ILoadMoreTemplateData = Object.create(null);
			data.label = dom.append(container, $('.load-more'));

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

			return data;
		}
E
Erich Gamma 已提交
483 484
		if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			let data: IThreadTemplateData = Object.create(null);
I
isidor 已提交
485 486 487 488
			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 已提交
489 490 491 492 493 494

			return data;
		}

		let data: IStackFrameTemplateData = Object.create(null);
		data.stackFrame = dom.append(container, $('.stack-frame'));
495
		data.label = dom.append(data.stackFrame, $('span.label.expression'));
E
Erich Gamma 已提交
496 497 498 499 500 501 502
		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 已提交
503
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
504
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
I
isidor 已提交
505 506
			this.renderProcess(element, templateData);
		} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
E
Erich Gamma 已提交
507
			this.renderThread(element, templateData);
I
isidor 已提交
508
		} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
E
Erich Gamma 已提交
509
			this.renderStackFrame(element, templateData);
510 511
		} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			this.renderError(element, templateData);
I
isidor 已提交
512
		} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
I
isidor 已提交
513
			this.renderLoadMore(element, templateData);
E
Erich Gamma 已提交
514 515 516
		}
	}

I
isidor 已提交
517
	private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void {
I
isidor 已提交
518
		data.process.title = nls.localize({ key: 'process', comment: ['Process is a noun'] }, "Process");
I
isidor 已提交
519
		data.name.textContent = process.name;
I
isidor 已提交
520 521 522 523
		const stoppedThread = process.getAllThreads().filter(t => t.stopped).pop();

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

E
Erich Gamma 已提交
526
	private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
I
isidor 已提交
527
		data.thread.title = nls.localize('thread', "Thread");
E
Erich Gamma 已提交
528
		data.name.textContent = thread.name;
I
isidor 已提交
529 530 531

		data.stateLabel.textContent = thread.stopped ? nls.localize('pausedOn', "Paused on {0}", thread.stoppedDetails.reason)
			: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
E
Erich Gamma 已提交
532 533
	}

534 535
	private renderError(element: string, data: IErrorTemplateData) {
		data.label.textContent = element;
I
isidor 已提交
536
		data.label.title = element;
537 538
	}

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

E
Erich Gamma 已提交
543 544 545 546
	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;
547
		data.label.title = stackFrame.name;
548
		data.fileName.textContent = getSourceName(stackFrame.source, this.contextService);
I
isidor 已提交
549
		if (stackFrame.lineNumber !== undefined) {
550
			data.lineNumber.textContent = `${stackFrame.lineNumber}`;
I
isidor 已提交
551 552 553 554
			dom.removeClass(data.lineNumber, 'unavailable');
		} else {
			dom.addClass(data.lineNumber, 'unavailable');
		}
E
Erich Gamma 已提交
555 556
	}

I
isidor 已提交
557
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
558 559 560 561
		// noop
	}
}

I
isidor 已提交
562
export class CallstackAccessibilityProvider implements IAccessibilityProvider {
563

J
Johannes Rieken 已提交
564
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
565 566 567
		// noop
	}

I
isidor 已提交
568 569 570
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
571
		}
I
isidor 已提交
572 573
		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));
574 575 576 577 578 579
		}

		return null;
	}
}

I
isidor 已提交
580
// variables
E
Erich Gamma 已提交
581

I
isidor 已提交
582
export class VariablesActionProvider implements IActionProvider {
E
Erich Gamma 已提交
583

I
isidor 已提交
584 585
	constructor(private instantiationService: IInstantiationService) {
		// noop
E
Erich Gamma 已提交
586 587
	}

I
isidor 已提交
588
	public hasActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
589 590 591
		return false;
	}

I
isidor 已提交
592
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
593
		return TPromise.as([]);
I
isidor 已提交
594 595
	}

I
isidor 已提交
596 597
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Variable;
E
Erich Gamma 已提交
598 599
	}

I
isidor 已提交
600 601 602
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		let actions: IAction[] = [];
		const variable = <Variable>element;
603
		if (!variable.hasChildren) {
I
isidor 已提交
604
			actions.push(this.instantiationService.createInstance(SetValueAction, SetValueAction.ID, SetValueAction.LABEL, variable));
605
			actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable));
I
isidor 已提交
606
			actions.push(new Separator());
E
Erich Gamma 已提交
607 608
		}

I
isidor 已提交
609
		actions.push(this.instantiationService.createInstance(AddToWatchExpressionsAction, AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable));
A
Alex Dima 已提交
610
		return TPromise.as(actions);
E
Erich Gamma 已提交
611
	}
I
isidor 已提交
612

I
isidor 已提交
613
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
614 615
		return null;
	}
E
Erich Gamma 已提交
616 617
}

I
isidor 已提交
618
export class VariablesDataSource implements IDataSource {
E
Erich Gamma 已提交
619

I
isidor 已提交
620
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
621 622 623
		return element.getId();
	}

I
isidor 已提交
624 625
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof ViewModel || element instanceof Scope) {
E
Erich Gamma 已提交
626 627 628
			return true;
		}

I
isidor 已提交
629
		let variable = <Variable>element;
630
		return variable.hasChildren && !equalsIgnoreCase(variable.value, 'null');
E
Erich Gamma 已提交
631 632
	}

I
isidor 已提交
633 634 635
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof ViewModel) {
			const focusedStackFrame = (<ViewModel>element).focusedStackFrame;
636
			return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
E
Erich Gamma 已提交
637 638
		}

I
isidor 已提交
639
		let scope = <Scope>element;
640
		return scope.getChildren();
E
Erich Gamma 已提交
641 642
	}

I
isidor 已提交
643
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
644
		return TPromise.as(null);
E
Erich Gamma 已提交
645 646 647 648 649 650 651 652 653 654 655 656 657
	}
}

interface IScopeTemplateData {
	name: HTMLElement;
}

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

I
isidor 已提交
658
export class VariablesRenderer implements IRenderer {
E
Erich Gamma 已提交
659 660 661 662

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

I
isidor 已提交
663 664 665 666 667 668 669
	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		// noop
	}

I
isidor 已提交
670
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
671
		return 22;
E
Erich Gamma 已提交
672 673
	}

I
isidor 已提交
674 675
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Scope) {
E
Erich Gamma 已提交
676 677
			return VariablesRenderer.SCOPE_TEMPLATE_ID;
		}
I
isidor 已提交
678
		if (element instanceof Variable) {
E
Erich Gamma 已提交
679 680 681 682 683 684
			return VariablesRenderer.VARIABLE_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
685
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
E
Erich Gamma 已提交
686 687 688 689 690 691 692 693
		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);
694
		data.expression = dom.append(container, $('.expression'));
E
Erich Gamma 已提交
695 696 697 698 699 700
		data.name = dom.append(data.expression, $('span.name'));
		data.value = dom.append(data.expression, $('span.value'));

		return data;
	}

I
isidor 已提交
701
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
702 703 704
		if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
			this.renderScope(element, templateData);
		} else {
I
isidor 已提交
705
			const variable = <Variable>element;
706 707 708 709 710 711 712
			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 已提交
713
				});
I
isidor 已提交
714
			} else {
715
				renderVariable(tree, variable, templateData, true);
I
isidor 已提交
716
			}
E
Erich Gamma 已提交
717 718 719
		}
	}

I
isidor 已提交
720
	private renderScope(scope: Scope, data: IScopeTemplateData): void {
E
Erich Gamma 已提交
721 722 723
		data.name.textContent = scope.name;
	}

I
isidor 已提交
724
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
725 726 727 728
		// noop
	}
}

I
isidor 已提交
729
export class VariablesAccessibilityProvider implements IAccessibilityProvider {
730

I
isidor 已提交
731 732 733
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Scope) {
			return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", (<Scope>element).name);
734
		}
I
isidor 已提交
735 736
		if (element instanceof Variable) {
			return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", (<Variable>element).name, (<Variable>element).value);
737 738 739 740 741 742
		}

		return null;
	}
}

743 744
export class VariablesController extends BaseDebugController {

I
isidor 已提交
745
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
746
		super(debugService, contextMenuService, actionProvider);
A
Alexandru Dima 已提交
747
		this.downKeyBindingDispatcher.set(KeyCode.Enter, this.setSelectedExpression.bind(this));
748 749
	}

I
isidor 已提交
750
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
751
		// double click on primitive value: open input box to be able to set the value
I
isidor 已提交
752
		if (element instanceof Variable && event.detail === 2) {
753
			const expression = <debug.IExpression>element;
754
			if (!expression.hasChildren) {
755 756 757 758 759 760 761
				this.debugService.getViewModel().setSelectedExpression(expression);
			}
			return true;
		}

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

I
isidor 已提交
763
	protected setSelectedExpression(tree: ITree, event: KeyboardEvent): boolean {
I
isidor 已提交
764
		const element = tree.getFocus();
765
		if (element instanceof Variable && !element.hasChildren) {
I
isidor 已提交
766
			this.debugService.getViewModel().setSelectedExpression(element);
767
			return true;
I
isidor 已提交
768 769
		}

770
		return false;
I
isidor 已提交
771
	}
772 773
}

I
isidor 已提交
774
// watch expressions
E
Erich Gamma 已提交
775

I
isidor 已提交
776
export class WatchExpressionsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
777 778 779 780 781 782 783

	private instantiationService: IInstantiationService;

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

I
isidor 已提交
784 785
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Expression && !!element.name;
E
Erich Gamma 已提交
786 787
	}

I
isidor 已提交
788
	public hasSecondaryActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
789 790 791
		return true;
	}

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

I
isidor 已提交
796 797
	public getExpressionActions(): IAction[] {
		return [this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL)];
E
Erich Gamma 已提交
798 799
	}

I
isidor 已提交
800 801 802 803 804 805
	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));
806
			if (!expression.hasChildren) {
807
				actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value));
E
Erich Gamma 已提交
808
			}
I
isidor 已提交
809
			actions.push(new Separator());
E
Erich Gamma 已提交
810

I
isidor 已提交
811 812
			actions.push(this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL));
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
813
		} else {
I
isidor 已提交
814 815 816
			actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
			if (element instanceof Variable) {
				const variable = <Variable>element;
817
				if (!variable.hasChildren) {
818
					actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable.value));
E
Erich Gamma 已提交
819
				}
I
isidor 已提交
820
				actions.push(new Separator());
E
Erich Gamma 已提交
821
			}
I
isidor 已提交
822
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
823 824
		}

A
Alex Dima 已提交
825
		return TPromise.as(actions);
E
Erich Gamma 已提交
826
	}
I
isidor 已提交
827

I
isidor 已提交
828
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
829 830
		return null;
	}
E
Erich Gamma 已提交
831 832
}

I
isidor 已提交
833
export class WatchExpressionsDataSource implements IDataSource {
E
Erich Gamma 已提交
834

I
isidor 已提交
835
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
836 837 838
		return element.getId();
	}

I
isidor 已提交
839 840
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof Model) {
E
Erich Gamma 已提交
841 842 843
			return true;
		}

I
isidor 已提交
844
		const watchExpression = <Expression>element;
845
		return watchExpression.hasChildren && !equalsIgnoreCase(watchExpression.value, 'null');
E
Erich Gamma 已提交
846 847
	}

I
isidor 已提交
848 849 850
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Model) {
			return TPromise.as((<Model>element).getWatchExpressions());
E
Erich Gamma 已提交
851 852
		}

I
isidor 已提交
853
		let expression = <Expression>element;
854
		return expression.getChildren();
E
Erich Gamma 已提交
855 856
	}

I
isidor 已提交
857
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
858
		return TPromise.as(null);
E
Erich Gamma 已提交
859 860 861 862
	}
}

interface IWatchExpressionTemplateData extends IVariableTemplateData {
I
isidor 已提交
863
	actionBar: ActionBar;
E
Erich Gamma 已提交
864 865
}

I
isidor 已提交
866
export class WatchExpressionsRenderer implements IRenderer {
E
Erich Gamma 已提交
867 868 869 870 871 872

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

I
isidor 已提交
873
	constructor(
I
isidor 已提交
874 875
		actionProvider: IActionProvider,
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
876 877 878 879
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		this.toDispose = [];
880
		this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
E
Erich Gamma 已提交
881 882
	}

I
isidor 已提交
883
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
884
		return 22;
E
Erich Gamma 已提交
885 886
	}

I
isidor 已提交
887 888
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Expression) {
E
Erich Gamma 已提交
889 890 891 892 893 894
			return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
		}

		return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
	}

I
isidor 已提交
895
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
E
Erich Gamma 已提交
896 897
		let data: IWatchExpressionTemplateData = Object.create(null);
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
I
isidor 已提交
898
			data.actionBar = new ActionBar(container, { actionRunner: this.actionRunner });
899
			data.actionBar.push(this.actionProvider.getExpressionActions(), { icon: true, label: false });
E
Erich Gamma 已提交
900 901
		}

902
		data.expression = dom.append(container, $('.expression'));
E
Erich Gamma 已提交
903 904 905 906 907 908
		data.name = dom.append(data.expression, $('span.name'));
		data.value = dom.append(data.expression, $('span.value'));

		return data;
	}

I
isidor 已提交
909
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
910 911 912
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			this.renderWatchExpression(tree, element, templateData);
		} else {
913
			renderVariable(tree, element, templateData, true);
E
Erich Gamma 已提交
914 915 916
		}
	}

I
isidor 已提交
917
	private renderWatchExpression(tree: ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
E
Erich Gamma 已提交
918
		let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
I
isidor 已提交
919
		if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
I
isidor 已提交
920 921 922 923 924
			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 已提交
925 926 927
		}
		data.actionBar.context = watchExpression;

928
		data.name.textContent = watchExpression.name;
929
		if (watchExpression.value) {
930
			data.name.textContent += ':';
931
			renderExpressionValue(watchExpression, data.value, true, MAX_VALUE_RENDER_LENGTH_IN_VIEWLET);
932
			data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
E
Erich Gamma 已提交
933 934 935
		}
	}

I
isidor 已提交
936
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
937 938 939
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			(<IWatchExpressionTemplateData>templateData).actionBar.dispose();
		}
E
Erich Gamma 已提交
940 941 942
	}

	public dispose(): void {
J
Joao Moreno 已提交
943
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
944 945 946
	}
}

I
isidor 已提交
947
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
948

I
isidor 已提交
949 950 951
	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);
952
		}
I
isidor 已提交
953 954
		if (element instanceof Variable) {
			return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
955 956 957 958 959 960
		}

		return null;
	}
}

E
Erich Gamma 已提交
961 962
export class WatchExpressionsController extends BaseDebugController {

I
isidor 已提交
963
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
E
Erich Gamma 已提交
964 965 966
		super(debugService, contextMenuService, actionProvider);

		if (isMacintosh) {
A
Alexandru Dima 已提交
967
			this.downKeyBindingDispatcher.set(KeyCode.Enter, this.onRename.bind(this));
E
Erich Gamma 已提交
968
		} else {
A
Alexandru Dima 已提交
969
			this.downKeyBindingDispatcher.set(KeyCode.F2, this.onRename.bind(this));
E
Erich Gamma 已提交
970 971 972
		}
	}

I
isidor 已提交
973
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
I
isidor 已提交
974
		// double click on primitive value: open input box to be able to select and copy value.
I
isidor 已提交
975
		if (element instanceof Expression && event.detail === 2) {
976
			const expression = <debug.IExpression>element;
977
			if (!expression.hasChildren) {
E
Erich Gamma 已提交
978 979 980 981 982 983 984 985
				this.debugService.getViewModel().setSelectedExpression(expression);
			}
			return true;
		}

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

I
isidor 已提交
986
	protected onRename(tree: ITree, event: KeyboardEvent): boolean {
I
isidor 已提交
987
		const element = tree.getFocus();
I
isidor 已提交
988 989
		if (element instanceof Expression) {
			const watchExpression = <Expression>element;
990
			if (!watchExpression.hasChildren) {
E
Erich Gamma 已提交
991 992 993 994 995 996 997 998
				this.debugService.getViewModel().setSelectedExpression(watchExpression);
			}
			return true;
		}

		return false;
	}

I
isidor 已提交
999
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
1000
		const element = tree.getFocus();
I
isidor 已提交
1001 1002
		if (element instanceof Expression) {
			const we = <Expression>element;
1003
			this.debugService.removeWatchExpressions(we.getId());
E
Erich Gamma 已提交
1004 1005 1006 1007 1008 1009 1010 1011

			return true;
		}

		return false;
	}
}

I
isidor 已提交
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
export class WatchExpressionsDragAndDrop extends DefaultDragAndDrop {

	constructor( @debug.IDebugService private debugService: debug.IDebugService) {
		super();
	}

	public getDragURI(tree: ITree, element: Expression): string {
		if (!(element instanceof Expression)) {
			return null;
		}

		return element.getId();
	}

	public getDragLabel(tree: ITree, elements: Expression[]): string {
		if (elements.length > 1) {
			return String(elements.length);
		}

		return elements[0].name;
	}

	public onDragOver(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): IDragOverReaction {
		return DRAG_OVER_ACCEPT;
	}

	public drop(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): void {
		const draggedData = data.getData();
		if (Array.isArray(draggedData)) {
			const draggedElement = <Expression>draggedData[0];
			const watches = this.debugService.getModel().getWatchExpressions();
			const position = target instanceof Model ? watches.length - 1 : watches.indexOf(target);
			this.debugService.moveWatchExpression(draggedElement.getId(), position);
		}
	}
}

I
isidor 已提交
1049
// breakpoints
E
Erich Gamma 已提交
1050

I
isidor 已提交
1051
export class BreakpointsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
1052 1053

	constructor(private instantiationService: IInstantiationService) {
I
isidor 已提交
1054
		// noop
E
Erich Gamma 已提交
1055 1056
	}

I
isidor 已提交
1057 1058
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint;
E
Erich Gamma 已提交
1059 1060
	}

I
isidor 已提交
1061 1062
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
E
Erich Gamma 已提交
1063 1064
	}

I
isidor 已提交
1065 1066
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
		if (element instanceof Breakpoint) {
A
Alex Dima 已提交
1067
			return TPromise.as(this.getBreakpointActions());
E
Erich Gamma 已提交
1068 1069
		}

A
Alex Dima 已提交
1070
		return TPromise.as([]);
E
Erich Gamma 已提交
1071 1072
	}

I
isidor 已提交
1073 1074
	public getBreakpointActions(): IAction[] {
		return [this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL)];
E
Erich Gamma 已提交
1075 1076
	}

I
isidor 已提交
1077 1078 1079
	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 已提交
1080

I
isidor 已提交
1081 1082
		if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
1083
		}
I
isidor 已提交
1084 1085
		actions.push(this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL));
		actions.push(new Separator());
E
Erich Gamma 已提交
1086

I
isidor 已提交
1087 1088
		actions.push(this.instantiationService.createInstance(ToggleBreakpointsActivatedAction, ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL));
		actions.push(new Separator());
E
Erich Gamma 已提交
1089

I
isidor 已提交
1090 1091 1092
		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 已提交
1093

I
isidor 已提交
1094 1095 1096
		actions.push(this.instantiationService.createInstance(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL));
		if (element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RenameFunctionBreakpointAction, RenameFunctionBreakpointAction.ID, RenameFunctionBreakpointAction.LABEL));
1097
		}
I
isidor 已提交
1098
		actions.push(new Separator());
I
isidor 已提交
1099

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

A
Alex Dima 已提交
1102
		return TPromise.as(actions);
E
Erich Gamma 已提交
1103
	}
I
isidor 已提交
1104

I
isidor 已提交
1105
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
1106 1107
		return null;
	}
E
Erich Gamma 已提交
1108 1109
}

I
isidor 已提交
1110
export class BreakpointsDataSource implements IDataSource {
E
Erich Gamma 已提交
1111

I
isidor 已提交
1112
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
1113 1114 1115
		return element.getId();
	}

I
isidor 已提交
1116 1117
	public hasChildren(tree: ITree, element: any): boolean {
		return element instanceof Model;
E
Erich Gamma 已提交
1118 1119
	}

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

A
Alex Dima 已提交
1124
		return TPromise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
E
Erich Gamma 已提交
1125 1126
	}

I
isidor 已提交
1127
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
1128
		return TPromise.as(null);
E
Erich Gamma 已提交
1129 1130 1131 1132
	}
}

interface IExceptionBreakpointTemplateData {
1133
	breakpoint: HTMLElement;
E
Erich Gamma 已提交
1134 1135 1136 1137 1138 1139
	name: HTMLElement;
	checkbox: HTMLInputElement;
	toDisposeBeforeRender: lifecycle.IDisposable[];
}

interface IBreakpointTemplateData extends IExceptionBreakpointTemplateData {
I
isidor 已提交
1140
	actionBar: ActionBar;
E
Erich Gamma 已提交
1141 1142 1143 1144
	lineNumber: HTMLElement;
	filePath: HTMLElement;
}

1145
interface IFunctionBreakpointTemplateData extends IExceptionBreakpointTemplateData {
I
isidor 已提交
1146
	actionBar: ActionBar;
1147 1148
}

I
isidor 已提交
1149
export class BreakpointsRenderer implements IRenderer {
E
Erich Gamma 已提交
1150 1151

	private static EXCEPTION_BREAKPOINT_TEMPLATE_ID = 'exceptionBreakpoint';
1152
	private static FUNCTION_BREAKPOINT_TEMPLATE_ID = 'functionBreakpoint';
E
Erich Gamma 已提交
1153 1154 1155 1156
	private static BREAKPOINT_TEMPLATE_ID = 'breakpoint';

	constructor(
		private actionProvider: BreakpointsActionProvider,
I
isidor 已提交
1157
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
1158
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
1159 1160
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
E
Erich Gamma 已提交
1161 1162 1163 1164
	) {
		// noop
	}

I
isidor 已提交
1165
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
1166
		return 22;
E
Erich Gamma 已提交
1167 1168
	}

I
isidor 已提交
1169 1170
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
E
Erich Gamma 已提交
1171 1172
			return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1173
		if (element instanceof FunctionBreakpoint) {
1174 1175
			return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1176
		if (element instanceof ExceptionBreakpoint) {
E
Erich Gamma 已提交
1177 1178 1179 1180 1181 1182
			return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
1183
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
1184
		const data: IBreakpointTemplateData = Object.create(null);
1185
		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
I
isidor 已提交
1186
			data.actionBar = new ActionBar(container, { actionRunner: this.actionRunner });
E
Erich Gamma 已提交
1187 1188 1189
			data.actionBar.push(this.actionProvider.getBreakpointActions(), { icon: true, label: false });
		}

1190
		data.breakpoint = dom.append(container, $('.breakpoint'));
E
Erich Gamma 已提交
1191 1192
		data.toDisposeBeforeRender = [];

1193
		data.checkbox = <HTMLInputElement>$('input');
E
Erich Gamma 已提交
1194
		data.checkbox.type = 'checkbox';
1195

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

1198
		data.name = dom.append(data.breakpoint, $('span.name'));
E
Erich Gamma 已提交
1199 1200

		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
1201 1202
			data.lineNumber = dom.append(data.breakpoint, $('span.line-number'));
			data.filePath = dom.append(data.breakpoint, $('span.file-path'));
E
Erich Gamma 已提交
1203 1204 1205 1206 1207
		}

		return data;
	}

I
isidor 已提交
1208
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
J
Joao Moreno 已提交
1209
		templateData.toDisposeBeforeRender = lifecycle.dispose(templateData.toDisposeBeforeRender);
1210
		templateData.toDisposeBeforeRender.push(dom.addStandardDisposableListener(templateData.checkbox, 'change', (e) => {
1211
			this.debugService.enableOrDisableBreakpoints(!element.enabled, element);
1212 1213
		}));

E
Erich Gamma 已提交
1214 1215
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderExceptionBreakpoint(element, templateData);
1216 1217
		} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderFunctionBreakpoint(tree, element, templateData);
E
Erich Gamma 已提交
1218 1219 1220 1221 1222 1223
		} else {
			this.renderBreakpoint(tree, element, templateData);
		}
	}

	private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IExceptionBreakpointTemplateData): void {
1224
		data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;;
1225
		data.breakpoint.title = data.name.textContent;
E
Erich Gamma 已提交
1226
		data.checkbox.checked = exceptionBreakpoint.enabled;
1227
	}
E
Erich Gamma 已提交
1228

I
isidor 已提交
1229
	private renderFunctionBreakpoint(tree: ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IFunctionBreakpointTemplateData): void {
I
isidor 已提交
1230 1231
		const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
		if (!functionBreakpoint.name || (selected && selected.getId() === functionBreakpoint.getId())) {
I
isidor 已提交
1232 1233 1234 1235 1236
			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")
			});
1237 1238 1239
		} else {
			data.name.textContent = functionBreakpoint.name;
			data.checkbox.checked = functionBreakpoint.enabled;
1240 1241 1242
			data.breakpoint.title = functionBreakpoint.name;

			// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
1243
			const process = this.debugService.getViewModel().focusedProcess;
I
isidor 已提交
1244
			if ((process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
1245
				tree.addTraits('disabled', [functionBreakpoint]);
I
isidor 已提交
1246
				if (process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) {
1247 1248 1249 1250 1251
					data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
				}
			} else {
				tree.removeTraits('disabled', [functionBreakpoint]);
			}
1252
		}
1253
		data.actionBar.context = functionBreakpoint;
E
Erich Gamma 已提交
1254 1255
	}

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

1259
		data.name.textContent = getPathLabel(paths.basename(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1260
		data.lineNumber.textContent = breakpoint.desiredLineNumber !== breakpoint.lineNumber ? breakpoint.desiredLineNumber + ' \u2192 ' + breakpoint.lineNumber : '' + breakpoint.lineNumber;
1261
		data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1262 1263
		data.checkbox.checked = breakpoint.enabled;
		data.actionBar.context = breakpoint;
I
isidor 已提交
1264

I
isidor 已提交
1265
		const debugActive = this.debugService.state === debug.State.Running || this.debugService.state === debug.State.Stopped || this.debugService.state === debug.State.Initializing;
I
isidor 已提交
1266 1267 1268 1269 1270
		if (debugActive && !breakpoint.verified) {
			tree.addTraits('disabled', [breakpoint]);
			if (breakpoint.message) {
				data.breakpoint.title = breakpoint.message;
			}
1271 1272
		} else if (breakpoint.condition || breakpoint.hitCondition) {
			data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
1273
		}
E
Erich Gamma 已提交
1274 1275
	}

I
isidor 已提交
1276
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
1277
		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
E
Erich Gamma 已提交
1278 1279 1280 1281 1282
			templateData.actionBar.dispose();
		}
	}
}

I
isidor 已提交
1283
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
1284

J
Johannes Rieken 已提交
1285
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
1286 1287 1288
		// noop
	}

I
isidor 已提交
1289 1290
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
1291
			return nls.localize('breakpointAriaLabel', "Breakpoint line {0} {1}, breakpoints, debug", (<Breakpoint>element).lineNumber, getPathLabel(paths.basename((<Breakpoint>element).uri.fsPath), this.contextService), this.contextService);
1292
		}
I
isidor 已提交
1293 1294
		if (element instanceof FunctionBreakpoint) {
			return nls.localize('functionBreakpointAriaLabel', "Function breakpoint {0}, breakpoints, debug", (<FunctionBreakpoint>element).name);
1295
		}
I
isidor 已提交
1296 1297
		if (element instanceof ExceptionBreakpoint) {
			return nls.localize('exceptionBreakpointAriaLabel', "Exception breakpoint {0}, breakpoints, debug", (<ExceptionBreakpoint>element).filter);
1298 1299 1300 1301 1302 1303
		}

		return null;
	}
}

E
Erich Gamma 已提交
1304 1305
export class BreakpointsController extends BaseDebugController {

I
isidor 已提交
1306
	constructor(debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: IActionProvider) {
I
isidor 已提交
1307 1308
		super(debugService, contextMenuService, actionProvider);
		if (isMacintosh) {
A
Alexandru Dima 已提交
1309
			this.downKeyBindingDispatcher.set(KeyCode.Enter, this.onRename.bind(this));
I
isidor 已提交
1310
		} else {
A
Alexandru Dima 已提交
1311
			this.downKeyBindingDispatcher.set(KeyCode.F2, this.onRename.bind(this));
I
isidor 已提交
1312 1313 1314
		}
	}

I
isidor 已提交
1315 1316
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
		if (element instanceof FunctionBreakpoint && event.detail === 2) {
I
isidor 已提交
1317 1318 1319
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1320
		if (element instanceof Breakpoint) {
1321 1322
			this.openBreakpointSource(element, event, true);
		}
I
isidor 已提交
1323 1324

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

I
isidor 已提交
1327
	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
1328
		const element = tree.getFocus();
I
isidor 已提交
1329
		if (element instanceof FunctionBreakpoint && element.name) {
1330 1331 1332
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1333
		if (element instanceof Breakpoint) {
1334 1335 1336 1337 1338 1339
			this.openBreakpointSource(element, event, false);
		}

		return super.onEnter(tree, event);
	}

I
isidor 已提交
1340
	protected onSpace(tree: ITree, event: IKeyboardEvent): boolean {
1341 1342
		super.onSpace(tree, event);
		const element = <debug.IEnablement>tree.getFocus();
1343
		this.debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
1344 1345 1346 1347

		return true;
	}

I
isidor 已提交
1348
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
1349
		const element = tree.getFocus();
I
isidor 已提交
1350 1351
		if (element instanceof Breakpoint) {
			this.debugService.removeBreakpoints((<Breakpoint>element).getId()).done(null, errors.onUnexpectedError);
1352
			return true;
I
isidor 已提交
1353 1354
		} else if (element instanceof FunctionBreakpoint) {
			const fbp = <FunctionBreakpoint>element;
1355
			this.debugService.removeFunctionBreakpoints(fbp.getId()).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
1356 1357 1358 1359 1360 1361

			return true;
		}

		return false;
	}
1362

1363
	private openBreakpointSource(breakpoint: Breakpoint, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
1364 1365
		const sideBySide = (event && (event.ctrlKey || event.metaKey));
		this.debugService.openOrRevealSource(breakpoint.uri, breakpoint.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
1366
	}
E
Erich Gamma 已提交
1367
}