debugViewer.ts 48.1 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';
14
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
J
Johannes Rieken 已提交
15
import { isMacintosh } from 'vs/base/common/platform';
I
isidor 已提交
16
import * as dom from 'vs/base/browser/dom';
I
isidor 已提交
17
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
I
isidor 已提交
18 19
import { getPathLabel } from 'vs/base/common/labels';
import { IAction, IActionRunner } from 'vs/base/common/actions';
I
isidor 已提交
20
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
21
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, DRAG_OVER_REJECT, IDragAndDropData, IDragOverReaction } from 'vs/base/parts/tree/browser/tree';
J
Johannes Rieken 已提交
22
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
I
isidor 已提交
23
import { DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
I
isidor 已提交
24
import { IActionProvider } from 'vs/base/parts/tree/browser/actionsRenderer';
25 26 27 28
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 { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
29 30
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
31
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
I
isidor 已提交
32 33 34
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';
35
import { ContinueAction, StepOverAction, PauseAction, ReapplyBreakpointsAction, DisableAllBreakpointsAction, RemoveBreakpointAction, RemoveWatchExpressionAction, AddWatchExpressionAction, RemoveAllBreakpointsAction, EnableAllBreakpointsAction, StepOutAction, StepIntoAction, SetValueAction, RemoveAllWatchExpressionsAction, RestartFrameAction, AddToWatchExpressionsAction, StopAction, RestartAction } from 'vs/workbench/parts/debug/browser/debugActions';
I
isidor 已提交
36
import { CopyValueAction, CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
J
Johannes Rieken 已提交
37
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
38

E
Erich Gamma 已提交
39

J
Joao Moreno 已提交
40
const $ = dom.$;
I
isidor 已提交
41 42
const booleanRegex = /^true|false$/i;
const stringRegex = /^(['"]).*\1$/;
43
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
E
Erich Gamma 已提交
44

45
export interface IRenderValueOptions {
I
isidor 已提交
46 47
	preserveWhitespace?: boolean;
	showChanged?: boolean;
48
	maxValueLength?: number;
I
isidor 已提交
49
	showHover?: boolean;
50 51 52
}

export function renderExpressionValue(expressionOrValue: debug.IExpression | string, container: HTMLElement, options: IRenderValueOptions): void {
53
	let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value;
E
Erich Gamma 已提交
54

I
isidor 已提交
55
	// remove stale classes
E
Erich Gamma 已提交
56
	container.className = 'value';
I
isidor 已提交
57
	// when resolving expressions we represent errors from the server as a variable with name === null.
I
isidor 已提交
58
	if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) {
E
Erich Gamma 已提交
59
		dom.addClass(container, 'unavailable');
I
isidor 已提交
60
		if (value !== Expression.DEFAULT_VALUE) {
61 62
			dom.addClass(container, 'error');
		}
E
Erich Gamma 已提交
63 64 65 66 67 68 69 70
	} 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');
	}

71
	if (options.showChanged && (<any>expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) {
72 73 74
		// value changed color has priority over other colors.
		container.className = 'value changed';
	}
I
isidor 已提交
75

76 77 78 79
	if (options.maxValueLength && value.length > options.maxValueLength) {
		value = value.substr(0, options.maxValueLength) + '...';
	}
	if (value && !options.preserveWhitespace) {
I
isidor 已提交
80 81
		const map = { '\n': '\\n', '\r': '\\r', '\t': '\\t' };
		container.textContent = value.replace(/[\n\r\t]/g, char => map[char]);
82 83
	} else {
		container.textContent = value;
I
isidor 已提交
84
	}
I
isidor 已提交
85 86 87
	if (options.showHover) {
		container.title = value;
	}
E
Erich Gamma 已提交
88 89
}

I
isidor 已提交
90
export function renderVariable(tree: ITree, variable: Variable, data: IVariableTemplateData, showChanged: boolean): void {
91
	if (variable.available) {
92
		data.name.textContent = variable.name;
93
		data.name.title = variable.type ? variable.type : '';
94 95
	}

E
Erich Gamma 已提交
96
	if (variable.value) {
I
isidor 已提交
97
		data.name.textContent += variable.name ? ':' : '';
98 99 100
		renderExpressionValue(variable, data.value, {
			showChanged,
			maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
I
isidor 已提交
101 102
			preserveWhitespace: false,
			showHover: true
103
		});
E
Erich Gamma 已提交
104 105 106 107 108 109
	} else {
		data.value.textContent = '';
		data.value.title = '';
	}
}

I
isidor 已提交
110 111 112 113
interface IRenameBoxOptions {
	initialValue: string;
	ariaLabel: string;
	placeholder?: string;
114
	validationOptions?: IInputValidationOptions;
I
isidor 已提交
115 116
}

I
isidor 已提交
117
function renderRenameBox(debugService: debug.IDebugService, contextViewService: IContextViewService, tree: ITree, element: any, container: HTMLElement, options: IRenameBoxOptions): void {
118
	let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
119
	let inputBox = new InputBox(inputBoxContainer, contextViewService, {
I
isidor 已提交
120 121 122
		validationOptions: options.validationOptions,
		placeholder: options.placeholder,
		ariaLabel: options.ariaLabel
123 124
	});

I
isidor 已提交
125
	inputBox.value = options.initialValue ? options.initialValue : '';
126
	inputBox.focus();
127
	inputBox.select();
128

I
isidor 已提交
129 130
	let disposed = false;
	const toDispose: [lifecycle.IDisposable] = [inputBox];
131

J
Joao Moreno 已提交
132
	const wrapUp = async.once((renamed: boolean) => {
133 134
		if (!disposed) {
			disposed = true;
I
isidor 已提交
135
			if (element instanceof Expression && renamed && inputBox.value) {
136
				debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
I
isidor 已提交
137
			} else if (element instanceof Expression && !element.name) {
138
				debugService.removeWatchExpressions(element.getId());
139 140
			} else if (element instanceof FunctionBreakpoint && inputBox.value) {
				debugService.renameFunctionBreakpoint(element.getId(), renamed ? inputBox.value : element.name).done(null, errors.onUnexpectedError);
I
isidor 已提交
141
			} else if (element instanceof FunctionBreakpoint && !element.name) {
142
				debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
I
isidor 已提交
143
			} else if (element instanceof Variable) {
144
				element.errorMessage = null;
145
				if (renamed && element.value !== inputBox.value) {
146
					element.setVariable(inputBox.value)
147
						// if everything went fine we need to refresh ui elements since the variable update can change watch and variables view
148
						.done(() => tree.refresh(element, false), errors.onUnexpectedError);
149
				}
150
			}
151

152 153
			tree.clearHighlight();
			tree.DOMFocus();
154
			tree.setFocus(element);
155

I
isidor 已提交
156
			// need to remove the input box since this template will be reused.
157
			container.removeChild(inputBoxContainer);
J
Joao Moreno 已提交
158
			lifecycle.dispose(toDispose);
159 160 161
		}
	});

A
Cleanup  
Alex Dima 已提交
162
	toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
A
Alexandru Dima 已提交
163 164
		const isEscape = e.equals(KeyCode.Escape);
		const isEnter = e.equals(KeyCode.Enter);
165 166 167 168 169 170 171 172 173
		if (isEscape || isEnter) {
			wrapUp(isEnter);
		}
	}));
	toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
		wrapUp(true);
	}));
}

174 175 176 177 178
function getSourceName(source: Source, contextService: IWorkspaceContextService): string {
	if (source.inMemory) {
		return source.name;
	}

I
isidor 已提交
179
	return getPathLabel(paths.basename(source.uri.fsPath), contextService);
180 181
}

I
isidor 已提交
182
export class BaseDebugController extends DefaultController {
E
Erich Gamma 已提交
183

184 185
	private contributedContextMenu: IMenu;

I
isidor 已提交
186
	constructor(
I
isidor 已提交
187
		private actionProvider: IActionProvider,
188
		menuId: MenuId,
189
		@debug.IDebugService protected debugService: debug.IDebugService,
190
		@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
191
		@IContextMenuService private contextMenuService: IContextMenuService,
192 193
		@IContextKeyService contextKeyService: IContextKeyService,
		@IMenuService menuService: IMenuService
I
isidor 已提交
194
	) {
E
Erich Gamma 已提交
195 196
		super();

197
		this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService);
E
Erich Gamma 已提交
198
		if (isMacintosh) {
A
Alexandru Dima 已提交
199
			this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Backspace, this.onDelete.bind(this));
E
Erich Gamma 已提交
200
		} else {
A
Alexandru Dima 已提交
201 202
			this.downKeyBindingDispatcher.set(KeyCode.Delete, this.onDelete.bind(this));
			this.downKeyBindingDispatcher.set(KeyMod.Shift | KeyCode.Delete, this.onDelete.bind(this));
E
Erich Gamma 已提交
203
		}
204 205 206 207 208
		if (isMacintosh) {
			this.downKeyBindingDispatcher.set(KeyCode.Enter, this.onRename.bind(this));
		} else {
			this.downKeyBindingDispatcher.set(KeyCode.F2, this.onRename.bind(this));
		}
E
Erich Gamma 已提交
209 210
	}

I
isidor 已提交
211
	public onContextMenu(tree: ITree, element: debug.IEnablement, event: ContextMenuEvent): boolean {
E
Erich Gamma 已提交
212 213 214 215 216 217 218
		if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
			return false;
		}

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

219
		tree.setFocus(element);
E
Erich Gamma 已提交
220 221

		if (this.actionProvider.hasSecondaryActions(tree, element)) {
I
isidor 已提交
222
			const anchor = { x: event.posx + 1, y: event.posy };
E
Erich Gamma 已提交
223 224
			this.contextMenuService.showContextMenu({
				getAnchor: () => anchor,
225 226 227 228
				getActions: () => this.actionProvider.getSecondaryActions(tree, element).then(actions => {
					fillInActions(this.contributedContextMenu, this.getContext(element), actions);
					return actions;
				}),
E
Erich Gamma 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242
				onHide: (wasCancelled?: boolean) => {
					if (wasCancelled) {
						tree.DOMFocus();
					}
				},
				getActionsContext: () => element
			});

			return true;
		}

		return false;
	}

I
isidor 已提交
243
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
E
Erich Gamma 已提交
244 245
		return false;
	}
246 247 248 249

	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
		return false;
	}
250 251 252 253

	protected getContext(element: any): any {
		return undefined;
	}
E
Erich Gamma 已提交
254 255
}

I
isidor 已提交
256
// call stack
E
Erich Gamma 已提交
257

I
isidor 已提交
258
class ThreadAndProcessIds implements debug.ITreeElement {
I
isidor 已提交
259
	constructor(public processId: string, public threadId: number) { }
I
isidor 已提交
260 261 262 263

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

266 267
export class CallStackController extends BaseDebugController {

I
isidor 已提交
268
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
I
isidor 已提交
269
		if (element instanceof ThreadAndProcessIds) {
270 271
			return this.showMoreStackFrames(tree, element);
		}
272 273 274
		if (element instanceof StackFrame) {
			this.focusStackFrame(element, event, true);
		}
275 276 277 278

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

I
isidor 已提交
279
	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
280
		const element = tree.getFocus();
I
isidor 已提交
281
		if (element instanceof ThreadAndProcessIds) {
282 283
			return this.showMoreStackFrames(tree, element);
		}
284 285 286
		if (element instanceof StackFrame) {
			this.focusStackFrame(element, event, false);
		}
287 288 289 290

		return super.onEnter(tree, event);
	}

291 292 293 294 295
	protected getContext(element: any): any {
		if (element instanceof StackFrame) {
			return element.source.uri.toString();
		}
	}
I
isidor 已提交
296

297
	// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
I
isidor 已提交
298
	private showMoreStackFrames(tree: ITree, threadAndProcessIds: ThreadAndProcessIds): boolean {
I
isidor 已提交
299 300
		const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === threadAndProcessIds.processId).pop();
		const thread = process && process.getThread(threadAndProcessIds.threadId);
301
		if (thread) {
302
			(<Thread>thread).fetchCallStack(true)
303 304 305 306 307
				.done(() => tree.refresh(), errors.onUnexpectedError);
		}

		return true;
	}
308

309 310 311
	private focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
		this.debugService.focusStackFrameAndEvaluate(stackFrame).then(() => {
			const sideBySide = (event && (event.ctrlKey || event.metaKey));
I
isidor 已提交
312
			return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide);
313
		}, errors.onUnexpectedError);
314
	}
315 316 317
}


I
isidor 已提交
318
export class CallStackActionProvider implements IActionProvider {
I
isidor 已提交
319

J
Johannes Rieken 已提交
320
	constructor( @IInstantiationService private instantiationService: IInstantiationService, @debug.IDebugService private debugService: debug.IDebugService) {
I
isidor 已提交
321 322 323
		// noop
	}

I
isidor 已提交
324
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
325 326 327
		return false;
	}

I
isidor 已提交
328
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
329 330 331
		return TPromise.as([]);
	}

I
isidor 已提交
332
	public hasSecondaryActions(tree: ITree, element: any): boolean {
333
		return element !== tree.getInput();
I
isidor 已提交
334 335
	}

I
isidor 已提交
336 337
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
338 339 340 341
		if (element instanceof Process) {
			actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL));
			actions.push(this.instantiationService.createInstance(StopAction, StopAction.ID, StopAction.LABEL));
		} else if (element instanceof Thread) {
I
isidor 已提交
342
			const thread = <Thread>element;
A
Andre Weinand 已提交
343
			if (thread.stopped) {
I
isidor 已提交
344 345 346 347
				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 已提交
348
			} else {
I
isidor 已提交
349
				actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL));
A
Andre Weinand 已提交
350
			}
I
isidor 已提交
351
		} else if (element instanceof StackFrame) {
I
isidor 已提交
352
			if (element.thread.process.session.configuration.capabilities.supportsRestartFrame) {
I
isidor 已提交
353
				actions.push(this.instantiationService.createInstance(RestartFrameAction, RestartFrameAction.ID, RestartFrameAction.LABEL));
354
			}
I
isidor 已提交
355
			actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
I
isidor 已提交
356 357 358 359 360
		}

		return TPromise.as(actions);
	}

I
isidor 已提交
361
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
362 363 364 365
		return null;
	}
}

I
isidor 已提交
366
export class CallStackDataSource implements IDataSource {
E
Erich Gamma 已提交
367

I
isidor 已提交
368
	public getId(tree: ITree, element: any): string {
369 370 371
		if (typeof element === 'string') {
			return element;
		}
I
isidor 已提交
372

E
Erich Gamma 已提交
373 374 375
		return element.getId();
	}

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

I
isidor 已提交
380 381
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Thread) {
I
isidor 已提交
382
			return this.getThreadChildren(element);
E
Erich Gamma 已提交
383
		}
I
isidor 已提交
384
		if (element instanceof Model) {
385
			return TPromise.as(element.getProcesses());
386 387
		}

388
		const process = <debug.IProcess>element;
I
isidor 已提交
389
		return TPromise.as(process.getAllThreads());
E
Erich Gamma 已提交
390 391
	}

392 393
	private getThreadChildren(thread: Thread): TPromise<any> {
		return thread.fetchCallStack().then((callStack: any[]) => {
I
isidor 已提交
394
			if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
395 396
				return callStack.concat([thread.stoppedDetails.framesErrorMessage]);
			}
I
isidor 已提交
397
			if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
I
isidor 已提交
398
				return callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]);
I
isidor 已提交
399 400 401 402 403 404
			}

			return callStack;
		});
	}

I
isidor 已提交
405
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
406
		return TPromise.as(null);
E
Erich Gamma 已提交
407 408 409 410
	}
}

interface IThreadTemplateData {
I
isidor 已提交
411
	thread: HTMLElement;
E
Erich Gamma 已提交
412
	name: HTMLElement;
I
isidor 已提交
413 414
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
E
Erich Gamma 已提交
415 416
}

I
isidor 已提交
417 418 419
interface IProcessTemplateData {
	process: HTMLElement;
	name: HTMLElement;
I
isidor 已提交
420 421
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
422 423
}

424 425 426 427
interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
428 429 430 431
interface ILoadMoreTemplateData {
	label: HTMLElement;
}

E
Erich Gamma 已提交
432 433
interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
434 435 436 437
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
E
Erich Gamma 已提交
438 439
}

I
isidor 已提交
440
export class CallStackRenderer implements IRenderer {
E
Erich Gamma 已提交
441 442 443

	private static THREAD_TEMPLATE_ID = 'thread';
	private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
444
	private static ERROR_TEMPLATE_ID = 'error';
I
isidor 已提交
445
	private static LOAD_MORE_TEMPLATE_ID = 'loadMore';
I
isidor 已提交
446
	private static PROCESS_TEMPLATE_ID = 'process';
E
Erich Gamma 已提交
447

J
Johannes Rieken 已提交
448
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
E
Erich Gamma 已提交
449 450 451
		// noop
	}

I
isidor 已提交
452
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
453
		return 22;
E
Erich Gamma 已提交
454 455
	}

I
isidor 已提交
456 457
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Process) {
I
isidor 已提交
458 459
			return CallStackRenderer.PROCESS_TEMPLATE_ID;
		}
I
isidor 已提交
460
		if (element instanceof Thread) {
E
Erich Gamma 已提交
461 462
			return CallStackRenderer.THREAD_TEMPLATE_ID;
		}
I
isidor 已提交
463
		if (element instanceof StackFrame) {
E
Erich Gamma 已提交
464 465
			return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
		}
466 467 468
		if (typeof element === 'string') {
			return CallStackRenderer.ERROR_TEMPLATE_ID;
		}
E
Erich Gamma 已提交
469

I
isidor 已提交
470
		return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
E
Erich Gamma 已提交
471 472
	}

I
isidor 已提交
473
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
474 475 476 477
		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 已提交
478 479
			data.state = dom.append(data.process, $('.state'));
			data.stateLabel = dom.append(data.state, $('span.label'));
I
isidor 已提交
480 481 482 483

			return data;
		}

I
isidor 已提交
484 485 486 487 488 489
		if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
			let data: ILoadMoreTemplateData = Object.create(null);
			data.label = dom.append(container, $('.load-more'));

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

			return data;
		}
E
Erich Gamma 已提交
496 497
		if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			let data: IThreadTemplateData = Object.create(null);
I
isidor 已提交
498 499 500 501
			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 已提交
502 503 504 505 506 507

			return data;
		}

		let data: IStackFrameTemplateData = Object.create(null);
		data.stackFrame = dom.append(container, $('.stack-frame'));
508
		data.label = dom.append(data.stackFrame, $('span.label.expression'));
E
Erich Gamma 已提交
509 510 511 512 513 514 515
		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 已提交
516
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
517
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
I
isidor 已提交
518 519
			this.renderProcess(element, templateData);
		} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
E
Erich Gamma 已提交
520
			this.renderThread(element, templateData);
I
isidor 已提交
521
		} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
E
Erich Gamma 已提交
522
			this.renderStackFrame(element, templateData);
523 524
		} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			this.renderError(element, templateData);
I
isidor 已提交
525
		} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
I
isidor 已提交
526
			this.renderLoadMore(element, templateData);
E
Erich Gamma 已提交
527 528 529
		}
	}

I
isidor 已提交
530
	private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void {
I
isidor 已提交
531
		data.process.title = nls.localize({ key: 'process', comment: ['Process is a noun'] }, "Process");
I
isidor 已提交
532
		data.name.textContent = process.name;
I
isidor 已提交
533 534 535 536
		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 已提交
537 538
	}

E
Erich Gamma 已提交
539
	private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
I
isidor 已提交
540
		data.thread.title = nls.localize('thread', "Thread");
E
Erich Gamma 已提交
541
		data.name.textContent = thread.name;
I
isidor 已提交
542

I
isidor 已提交
543
		data.stateLabel.textContent = thread.stopped ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", thread.stoppedDetails.reason)
I
isidor 已提交
544
			: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
E
Erich Gamma 已提交
545 546
	}

547 548
	private renderError(element: string, data: IErrorTemplateData) {
		data.label.textContent = element;
I
isidor 已提交
549
		data.label.title = element;
550 551
	}

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

E
Erich Gamma 已提交
556 557
	private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void {
		stackFrame.source.available ? dom.removeClass(data.stackFrame, 'disabled') : dom.addClass(data.stackFrame, 'disabled');
558
		data.file.title = stackFrame.source.raw.path || stackFrame.source.name;
E
Erich Gamma 已提交
559
		data.label.textContent = stackFrame.name;
560
		data.label.title = stackFrame.name;
561
		data.fileName.textContent = getSourceName(stackFrame.source, this.contextService);
I
isidor 已提交
562
		if (stackFrame.lineNumber !== undefined) {
563
			data.lineNumber.textContent = `${stackFrame.lineNumber}`;
I
isidor 已提交
564 565 566 567
			dom.removeClass(data.lineNumber, 'unavailable');
		} else {
			dom.addClass(data.lineNumber, 'unavailable');
		}
E
Erich Gamma 已提交
568 569
	}

I
isidor 已提交
570
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
571 572 573 574
		// noop
	}
}

I
isidor 已提交
575
export class CallstackAccessibilityProvider implements IAccessibilityProvider {
576

J
Johannes Rieken 已提交
577
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
578 579 580
		// noop
	}

I
isidor 已提交
581 582 583
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
584
		}
I
isidor 已提交
585 586
		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));
587 588 589 590 591 592
		}

		return null;
	}
}

I
isidor 已提交
593
// variables
E
Erich Gamma 已提交
594

I
isidor 已提交
595
export class VariablesActionProvider implements IActionProvider {
E
Erich Gamma 已提交
596

I
isidor 已提交
597 598
	constructor(private instantiationService: IInstantiationService) {
		// noop
E
Erich Gamma 已提交
599 600
	}

I
isidor 已提交
601
	public hasActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
602 603 604
		return false;
	}

I
isidor 已提交
605
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
606
		return TPromise.as([]);
I
isidor 已提交
607 608
	}

I
isidor 已提交
609 610
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Variable;
E
Erich Gamma 已提交
611 612
	}

I
isidor 已提交
613 614 615
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		let actions: IAction[] = [];
		const variable = <Variable>element;
616
		if (!variable.hasChildren) {
I
isidor 已提交
617
			actions.push(this.instantiationService.createInstance(SetValueAction, SetValueAction.ID, SetValueAction.LABEL, variable));
618
			actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable));
I
isidor 已提交
619
			actions.push(new Separator());
E
Erich Gamma 已提交
620 621
		}

I
isidor 已提交
622
		actions.push(this.instantiationService.createInstance(AddToWatchExpressionsAction, AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable));
A
Alex Dima 已提交
623
		return TPromise.as(actions);
E
Erich Gamma 已提交
624
	}
I
isidor 已提交
625

I
isidor 已提交
626
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
627 628
		return null;
	}
E
Erich Gamma 已提交
629 630
}

I
isidor 已提交
631
export class VariablesDataSource implements IDataSource {
E
Erich Gamma 已提交
632

I
isidor 已提交
633
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
634 635 636
		return element.getId();
	}

I
isidor 已提交
637 638
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof ViewModel || element instanceof Scope) {
E
Erich Gamma 已提交
639 640 641
			return true;
		}

I
isidor 已提交
642
		let variable = <Variable>element;
643
		return variable.hasChildren && !equalsIgnoreCase(variable.value, 'null');
E
Erich Gamma 已提交
644 645
	}

I
isidor 已提交
646 647 648
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof ViewModel) {
			const focusedStackFrame = (<ViewModel>element).focusedStackFrame;
649
			return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
E
Erich Gamma 已提交
650 651
		}

I
isidor 已提交
652
		let scope = <Scope>element;
653
		return scope.getChildren();
E
Erich Gamma 已提交
654 655
	}

I
isidor 已提交
656
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
657
		return TPromise.as(null);
E
Erich Gamma 已提交
658 659 660 661 662 663 664 665 666 667 668 669 670
	}
}

interface IScopeTemplateData {
	name: HTMLElement;
}

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

I
isidor 已提交
671
export class VariablesRenderer implements IRenderer {
E
Erich Gamma 已提交
672 673 674 675

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

I
isidor 已提交
676 677 678 679 680 681 682
	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		// noop
	}

I
isidor 已提交
683
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
684
		return 22;
E
Erich Gamma 已提交
685 686
	}

I
isidor 已提交
687 688
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Scope) {
E
Erich Gamma 已提交
689 690
			return VariablesRenderer.SCOPE_TEMPLATE_ID;
		}
I
isidor 已提交
691
		if (element instanceof Variable) {
E
Erich Gamma 已提交
692 693 694 695 696 697
			return VariablesRenderer.VARIABLE_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
698
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
E
Erich Gamma 已提交
699 700 701 702 703 704 705 706
		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);
707
		data.expression = dom.append(container, $('.expression'));
E
Erich Gamma 已提交
708 709 710 711 712 713
		data.name = dom.append(data.expression, $('span.name'));
		data.value = dom.append(data.expression, $('span.value'));

		return data;
	}

I
isidor 已提交
714
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
715 716 717
		if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
			this.renderScope(element, templateData);
		} else {
I
isidor 已提交
718
			const variable = <Variable>element;
719 720 721 722 723 724 725
			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 已提交
726
				});
I
isidor 已提交
727
			} else {
728
				renderVariable(tree, variable, templateData, true);
I
isidor 已提交
729
			}
E
Erich Gamma 已提交
730 731 732
		}
	}

I
isidor 已提交
733
	private renderScope(scope: Scope, data: IScopeTemplateData): void {
E
Erich Gamma 已提交
734 735 736
		data.name.textContent = scope.name;
	}

I
isidor 已提交
737
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
738 739 740 741
		// noop
	}
}

I
isidor 已提交
742
export class VariablesAccessibilityProvider implements IAccessibilityProvider {
743

I
isidor 已提交
744 745 746
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Scope) {
			return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", (<Scope>element).name);
747
		}
I
isidor 已提交
748 749
		if (element instanceof Variable) {
			return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", (<Variable>element).name, (<Variable>element).value);
750 751 752 753 754 755
		}

		return null;
	}
}

756 757
export class VariablesController extends BaseDebugController {

I
isidor 已提交
758
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
759
		// double click on primitive value: open input box to be able to set the value
I
isidor 已提交
760
		if (element instanceof Variable && event.detail === 2) {
761
			const expression = <debug.IExpression>element;
762
			if (!expression.hasChildren) {
763 764 765 766 767 768 769
				this.debugService.getViewModel().setSelectedExpression(expression);
			}
			return true;
		}

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

771
	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
772
		const element = tree.getFocus();
773
		if (element instanceof Variable && !element.hasChildren) {
I
isidor 已提交
774
			this.debugService.getViewModel().setSelectedExpression(element);
775
			return true;
I
isidor 已提交
776 777
		}

778
		return false;
I
isidor 已提交
779
	}
780 781
}

I
isidor 已提交
782
// watch expressions
E
Erich Gamma 已提交
783

I
isidor 已提交
784
export class WatchExpressionsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
785 786 787 788 789 790 791

	private instantiationService: IInstantiationService;

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

I
isidor 已提交
792 793
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Expression && !!element.name;
E
Erich Gamma 已提交
794 795
	}

I
isidor 已提交
796
	public hasSecondaryActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
797 798 799
		return true;
	}

I
isidor 已提交
800
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
801
		return TPromise.as([]);
E
Erich Gamma 已提交
802 803
	}

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

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

A
Alex Dima 已提交
828
		return TPromise.as(actions);
E
Erich Gamma 已提交
829
	}
I
isidor 已提交
830

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

I
isidor 已提交
836
export class WatchExpressionsDataSource implements IDataSource {
E
Erich Gamma 已提交
837

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

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

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

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

I
isidor 已提交
856
		let expression = <Expression>element;
857
		return expression.getChildren();
E
Erich Gamma 已提交
858 859
	}

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

I
isidor 已提交
865 866 867 868 869
interface IWatchExpressionTemplateData {
	watchExpression: HTMLElement;
	expression: HTMLElement;
	name: HTMLSpanElement;
	value: HTMLSpanElement;
E
Erich Gamma 已提交
870 871
}

I
isidor 已提交
872
export class WatchExpressionsRenderer implements IRenderer {
E
Erich Gamma 已提交
873 874 875 876 877 878

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

I
isidor 已提交
879
	constructor(
I
isidor 已提交
880 881
		actionProvider: IActionProvider,
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
882 883 884 885
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		this.toDispose = [];
886
		this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
E
Erich Gamma 已提交
887 888
	}

I
isidor 已提交
889
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
890
		return 22;
E
Erich Gamma 已提交
891 892
	}

I
isidor 已提交
893 894
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Expression) {
E
Erich Gamma 已提交
895 896 897 898 899 900
			return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
		}

		return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
	}

I
isidor 已提交
901
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
902 903 904 905 906 907
		const createVariableTemplate = ((data: IVariableTemplateData, container: HTMLElement) => {
			data.expression = dom.append(container, $('.expression'));
			data.name = dom.append(data.expression, $('span.name'));
			data.value = dom.append(data.expression, $('span.value'));
		});

E
Erich Gamma 已提交
908
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
I
isidor 已提交
909 910 911 912 913
			const data: IWatchExpressionTemplateData = Object.create(null);
			data.watchExpression = dom.append(container, $('.watch-expression'));
			createVariableTemplate(data, data.watchExpression);

			return data;
E
Erich Gamma 已提交
914 915
		}

I
isidor 已提交
916 917
		const data: IVariableTemplateData = Object.create(null);
		createVariableTemplate(data, container);
E
Erich Gamma 已提交
918 919 920 921

		return data;
	}

I
isidor 已提交
922
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
923 924 925
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			this.renderWatchExpression(tree, element, templateData);
		} else {
926
			renderVariable(tree, element, templateData, true);
E
Erich Gamma 已提交
927 928 929
		}
	}

I
isidor 已提交
930
	private renderWatchExpression(tree: ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
E
Erich Gamma 已提交
931
		let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
I
isidor 已提交
932
		if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
I
isidor 已提交
933 934 935 936 937
			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 已提交
938 939
		}

940
		data.name.textContent = watchExpression.name;
941
		if (watchExpression.value) {
942
			data.name.textContent += ':';
943 944 945
			renderExpressionValue(watchExpression, data.value, {
				showChanged: true,
				maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
I
isidor 已提交
946 947
				preserveWhitespace: false,
				showHover: true
948
			});
949
			data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
E
Erich Gamma 已提交
950 951 952
		}
	}

I
isidor 已提交
953
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
I
isidor 已提交
954
		// noop
E
Erich Gamma 已提交
955 956 957
	}

	public dispose(): void {
J
Joao Moreno 已提交
958
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
959 960 961
	}
}

I
isidor 已提交
962
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
963

I
isidor 已提交
964 965 966
	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);
967
		}
I
isidor 已提交
968 969
		if (element instanceof Variable) {
			return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
970 971 972 973 974 975
		}

		return null;
	}
}

E
Erich Gamma 已提交
976 977
export class WatchExpressionsController extends BaseDebugController {

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

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

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

		return false;
	}

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

			return true;
		}

		return false;
	}
}

I
isidor 已提交
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
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 {
1040
		if (target instanceof Expression || target instanceof Model) {
1041 1042 1043 1044
			return {
				accept: true,
				autoExpand: false
			};
1045 1046 1047
		}

		return DRAG_OVER_REJECT;
I
isidor 已提交
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
	}

	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 已提交
1061
// breakpoints
E
Erich Gamma 已提交
1062

I
isidor 已提交
1063
export class BreakpointsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
1064

I
isidor 已提交
1065
	constructor(private instantiationService: IInstantiationService, private debugService: debug.IDebugService) {
I
isidor 已提交
1066
		// noop
E
Erich Gamma 已提交
1067 1068
	}

I
isidor 已提交
1069
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
1070
		return false;;
E
Erich Gamma 已提交
1071 1072
	}

I
isidor 已提交
1073 1074
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
E
Erich Gamma 已提交
1075 1076
	}

I
isidor 已提交
1077
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
1078
		return TPromise.as([]);
E
Erich Gamma 已提交
1079 1080
	}

I
isidor 已提交
1081
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
1082
		const actions: IAction[] = [];
E
Erich Gamma 已提交
1083

I
isidor 已提交
1084 1085
		if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
1086
		}
I
isidor 已提交
1087 1088 1089
		if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) {
			actions.push(this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL));
			actions.push(new Separator());
E
Erich Gamma 已提交
1090

I
isidor 已提交
1091 1092
			actions.push(this.instantiationService.createInstance(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL));
			actions.push(this.instantiationService.createInstance(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL));
1093
		}
I
isidor 已提交
1094

I
isidor 已提交
1095
		actions.push(new Separator());
I
isidor 已提交
1096
		actions.push(this.instantiationService.createInstance(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL));
E
Erich Gamma 已提交
1097

A
Alex Dima 已提交
1098
		return TPromise.as(actions);
E
Erich Gamma 已提交
1099
	}
I
isidor 已提交
1100

I
isidor 已提交
1101
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
1102 1103
		return null;
	}
E
Erich Gamma 已提交
1104 1105
}

I
isidor 已提交
1106
export class BreakpointsDataSource implements IDataSource {
E
Erich Gamma 已提交
1107

I
isidor 已提交
1108
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
1109 1110 1111
		return element.getId();
	}

I
isidor 已提交
1112 1113
	public hasChildren(tree: ITree, element: any): boolean {
		return element instanceof Model;
E
Erich Gamma 已提交
1114 1115
	}

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

A
Alex Dima 已提交
1120
		return TPromise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
E
Erich Gamma 已提交
1121 1122
	}

I
isidor 已提交
1123
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
1124
		return TPromise.as(null);
E
Erich Gamma 已提交
1125 1126 1127
	}
}

1128
interface IBaseBreakpointTemplateData {
1129
	breakpoint: HTMLElement;
E
Erich Gamma 已提交
1130 1131
	name: HTMLElement;
	checkbox: HTMLInputElement;
1132 1133
	context: debug.IEnablement;
	toDispose: lifecycle.IDisposable[];
E
Erich Gamma 已提交
1134 1135
}

1136
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
E
Erich Gamma 已提交
1137 1138 1139 1140
	lineNumber: HTMLElement;
	filePath: HTMLElement;
}

I
isidor 已提交
1141
export class BreakpointsRenderer implements IRenderer {
E
Erich Gamma 已提交
1142 1143

	private static EXCEPTION_BREAKPOINT_TEMPLATE_ID = 'exceptionBreakpoint';
1144
	private static FUNCTION_BREAKPOINT_TEMPLATE_ID = 'functionBreakpoint';
E
Erich Gamma 已提交
1145 1146 1147 1148
	private static BREAKPOINT_TEMPLATE_ID = 'breakpoint';

	constructor(
		private actionProvider: BreakpointsActionProvider,
I
isidor 已提交
1149
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
1150
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
1151 1152
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
E
Erich Gamma 已提交
1153 1154 1155 1156
	) {
		// noop
	}

I
isidor 已提交
1157
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
1158
		return 22;
E
Erich Gamma 已提交
1159 1160
	}

I
isidor 已提交
1161 1162
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
E
Erich Gamma 已提交
1163 1164
			return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1165
		if (element instanceof FunctionBreakpoint) {
1166 1167
			return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1168
		if (element instanceof ExceptionBreakpoint) {
E
Erich Gamma 已提交
1169 1170 1171 1172 1173 1174
			return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
		}

		return null;
	}

I
isidor 已提交
1175
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
1176
		const data: IBreakpointTemplateData = Object.create(null);
I
isidor 已提交
1177
		data.breakpoint = dom.append(container, $('.breakpoint'));
E
Erich Gamma 已提交
1178

1179
		data.checkbox = <HTMLInputElement>$('input');
E
Erich Gamma 已提交
1180
		data.checkbox.type = 'checkbox';
1181 1182 1183 1184
		data.toDispose = [];
		data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
			this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
		}));
1185

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

1188
		data.name = dom.append(data.breakpoint, $('span.name'));
E
Erich Gamma 已提交
1189 1190

		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
1191
			data.filePath = dom.append(data.breakpoint, $('span.file-path'));
I
isidor 已提交
1192 1193 1194 1195 1196
			const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
			data.lineNumber = dom.append(lineNumberContainer, $('span.line-number'));
		}
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			dom.addClass(data.breakpoint, 'exception');
E
Erich Gamma 已提交
1197 1198 1199 1200 1201
		}

		return data;
	}

I
isidor 已提交
1202
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
1203
		templateData.context = element;
E
Erich Gamma 已提交
1204 1205
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderExceptionBreakpoint(element, templateData);
1206 1207
		} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderFunctionBreakpoint(tree, element, templateData);
E
Erich Gamma 已提交
1208 1209 1210 1211 1212
		} else {
			this.renderBreakpoint(tree, element, templateData);
		}
	}

1213
	private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IBaseBreakpointTemplateData): void {
1214
		data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;;
1215
		data.breakpoint.title = data.name.textContent;
E
Erich Gamma 已提交
1216
		data.checkbox.checked = exceptionBreakpoint.enabled;
1217
	}
E
Erich Gamma 已提交
1218

1219
	private renderFunctionBreakpoint(tree: ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IBaseBreakpointTemplateData): void {
I
isidor 已提交
1220 1221
		const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
		if (!functionBreakpoint.name || (selected && selected.getId() === functionBreakpoint.getId())) {
1222
			data.name.textContent = '';
I
isidor 已提交
1223 1224 1225 1226 1227
			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")
			});
1228 1229 1230
		} else {
			data.name.textContent = functionBreakpoint.name;
			data.checkbox.checked = functionBreakpoint.enabled;
1231 1232 1233
			data.breakpoint.title = functionBreakpoint.name;

			// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
1234
			const process = this.debugService.getViewModel().focusedProcess;
I
isidor 已提交
1235
			if ((process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
1236
				tree.addTraits('disabled', [functionBreakpoint]);
I
isidor 已提交
1237
				if (process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) {
1238 1239 1240 1241 1242
					data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
				}
			} else {
				tree.removeTraits('disabled', [functionBreakpoint]);
			}
1243
		}
E
Erich Gamma 已提交
1244 1245
	}

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

1249
		data.name.textContent = getPathLabel(paths.basename(breakpoint.uri.fsPath), this.contextService);
1250
		data.lineNumber.textContent = breakpoint.lineNumber.toString();
1251
		data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1252
		data.checkbox.checked = breakpoint.enabled;
I
isidor 已提交
1253

I
isidor 已提交
1254
		const debugActive = this.debugService.state === debug.State.Running || this.debugService.state === debug.State.Stopped || this.debugService.state === debug.State.Initializing;
I
isidor 已提交
1255 1256 1257 1258 1259
		if (debugActive && !breakpoint.verified) {
			tree.addTraits('disabled', [breakpoint]);
			if (breakpoint.message) {
				data.breakpoint.title = breakpoint.message;
			}
1260 1261
		} else if (breakpoint.condition || breakpoint.hitCondition) {
			data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
1262
		}
E
Erich Gamma 已提交
1263 1264
	}

I
isidor 已提交
1265
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
1266
		lifecycle.dispose(templateData.toDispose);
E
Erich Gamma 已提交
1267 1268 1269
	}
}

I
isidor 已提交
1270
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
1271

J
Johannes Rieken 已提交
1272
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
1273 1274 1275
		// noop
	}

I
isidor 已提交
1276 1277
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
1278
			return nls.localize('breakpointAriaLabel', "Breakpoint line {0} {1}, breakpoints, debug", (<Breakpoint>element).lineNumber, getPathLabel(paths.basename((<Breakpoint>element).uri.fsPath), this.contextService), this.contextService);
1279
		}
I
isidor 已提交
1280 1281
		if (element instanceof FunctionBreakpoint) {
			return nls.localize('functionBreakpointAriaLabel', "Function breakpoint {0}, breakpoints, debug", (<FunctionBreakpoint>element).name);
1282
		}
I
isidor 已提交
1283 1284
		if (element instanceof ExceptionBreakpoint) {
			return nls.localize('exceptionBreakpointAriaLabel', "Exception breakpoint {0}, breakpoints, debug", (<ExceptionBreakpoint>element).filter);
1285 1286 1287 1288 1289 1290
		}

		return null;
	}
}

E
Erich Gamma 已提交
1291 1292
export class BreakpointsController extends BaseDebugController {

I
isidor 已提交
1293 1294
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
		if (element instanceof FunctionBreakpoint && event.detail === 2) {
I
isidor 已提交
1295 1296 1297
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1298
		if (element instanceof Breakpoint) {
1299 1300
			this.openBreakpointSource(element, event, true);
		}
I
isidor 已提交
1301 1302

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

I
isidor 已提交
1305
	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
1306
		const element = tree.getFocus();
I
isidor 已提交
1307
		if (element instanceof FunctionBreakpoint && element.name) {
1308 1309 1310
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
1311 1312 1313 1314 1315 1316

		return false;
	}

	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
		const element = tree.getFocus();
I
isidor 已提交
1317
		if (element instanceof Breakpoint) {
1318
			this.openBreakpointSource(element, event, false);
1319
			return true;
1320 1321 1322 1323 1324
		}

		return super.onEnter(tree, event);
	}

I
isidor 已提交
1325
	protected onSpace(tree: ITree, event: IKeyboardEvent): boolean {
1326 1327
		super.onSpace(tree, event);
		const element = <debug.IEnablement>tree.getFocus();
1328
		this.debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
1329 1330 1331 1332

		return true;
	}

I
isidor 已提交
1333
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
1334
		const element = tree.getFocus();
I
isidor 已提交
1335 1336
		if (element instanceof Breakpoint) {
			this.debugService.removeBreakpoints((<Breakpoint>element).getId()).done(null, errors.onUnexpectedError);
1337
			return true;
I
isidor 已提交
1338 1339
		} else if (element instanceof FunctionBreakpoint) {
			const fbp = <FunctionBreakpoint>element;
1340
			this.debugService.removeFunctionBreakpoints(fbp.getId()).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
1341 1342 1343 1344 1345 1346

			return true;
		}

		return false;
	}
1347

1348
	private openBreakpointSource(breakpoint: Breakpoint, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
1349
		const sideBySide = (event && (event.ctrlKey || event.metaKey));
1350 1351 1352 1353
		this.editorService.openEditor({
			resource: breakpoint.uri,
			options: {
				preserveFocus,
I
isidor 已提交
1354
				selection: { startLineNumber: breakpoint.lineNumber, startColumn: 1 },
I
isidor 已提交
1355 1356
				revealIfVisible: true,
				revealInCenterIfOutsideViewport: true
1357 1358
			}
		}, sideBySide).done(undefined, errors.onUnexpectedError);
1359
	}
E
Erich Gamma 已提交
1360
}