debugViewer.ts 48.3 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
import * as paths from 'vs/base/common/paths';
import * as errors from 'vs/base/common/errors';
import { equalsIgnoreCase } from 'vs/base/common/strings';
13
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
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
import { getPathLabel } from 'vs/base/common/labels';
import { IAction, IActionRunner } from 'vs/base/common/actions';
I
isidor 已提交
19
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
20
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, DRAG_OVER_REJECT, 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
import { IActionProvider } from 'vs/base/parts/tree/browser/actionsRenderer';
24 25 26 27
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';
28 29
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
30
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
I
isidor 已提交
31 32 33
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';
34
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 已提交
35
import { CopyValueAction, CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
J
Johannes Rieken 已提交
36
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
J
Joao Moreno 已提交
37
import { once } from 'vs/base/common/functional';
E
Erich Gamma 已提交
38

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

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

I
isidor 已提交
51 52 53 54 55
function replaceWhitespace(value: string): string {
	const map = { '\n': '\\n', '\r': '\\r', '\t': '\\t' };
	return value.replace(/[\n\r\t]/g, char => map[char]);
}

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

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

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

80 81 82 83
	if (options.maxValueLength && value.length > options.maxValueLength) {
		value = value.substr(0, options.maxValueLength) + '...';
	}
	if (value && !options.preserveWhitespace) {
I
isidor 已提交
84
		container.textContent = replaceWhitespace(value);
85 86
	} else {
		container.textContent = value;
I
isidor 已提交
87
	}
I
isidor 已提交
88 89 90
	if (options.showHover) {
		container.title = value;
	}
E
Erich Gamma 已提交
91 92
}

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

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

I
isidor 已提交
113 114 115 116
interface IRenameBoxOptions {
	initialValue: string;
	ariaLabel: string;
	placeholder?: string;
117
	validationOptions?: IInputValidationOptions;
I
isidor 已提交
118 119
}

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

I
isidor 已提交
128
	inputBox.value = options.initialValue ? options.initialValue : '';
129
	inputBox.focus();
130
	inputBox.select();
131

I
isidor 已提交
132 133
	let disposed = false;
	const toDispose: [lifecycle.IDisposable] = [inputBox];
134

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

155 156
			tree.clearHighlight();
			tree.DOMFocus();
157
			tree.setFocus(element);
158

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

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

177
function getSourceName(source: Source, contextService: IWorkspaceContextService): string {
I
isidor 已提交
178
	if (source.name) {
179 180 181
		return source.name;
	}

I
isidor 已提交
182
	return getPathLabel(paths.basename(source.uri.fsPath), contextService);
183 184
}

I
isidor 已提交
185
export class BaseDebugController extends DefaultController {
E
Erich Gamma 已提交
186

187 188
	private contributedContextMenu: IMenu;

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

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

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

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

222
		tree.setFocus(element);
E
Erich Gamma 已提交
223 224

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

			return true;
		}

		return false;
	}

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

	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
		return false;
	}
253 254 255 256

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

I
isidor 已提交
259
// call stack
E
Erich Gamma 已提交
260

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

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

269 270
export class CallStackController extends BaseDebugController {

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

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

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

		return super.onEnter(tree, event);
	}

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

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

		return true;
	}
311

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


I
isidor 已提交
321
export class CallStackActionProvider implements IActionProvider {
I
isidor 已提交
322

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

I
isidor 已提交
327
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
328 329 330
		return false;
	}

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

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

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

		return TPromise.as(actions);
	}

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

I
isidor 已提交
369
export class CallStackDataSource implements IDataSource {
E
Erich Gamma 已提交
370

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

E
Erich Gamma 已提交
376 377 378
		return element.getId();
	}

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

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

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

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

			return callStack;
		});
	}

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

interface IThreadTemplateData {
I
isidor 已提交
414
	thread: HTMLElement;
E
Erich Gamma 已提交
415
	name: HTMLElement;
I
isidor 已提交
416 417
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
E
Erich Gamma 已提交
418 419
}

I
isidor 已提交
420 421 422
interface IProcessTemplateData {
	process: HTMLElement;
	name: HTMLElement;
I
isidor 已提交
423 424
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
425 426
}

427 428 429 430
interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
431 432 433 434
interface ILoadMoreTemplateData {
	label: HTMLElement;
}

E
Erich Gamma 已提交
435 436
interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
437 438 439 440
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
E
Erich Gamma 已提交
441 442
}

I
isidor 已提交
443
export class CallStackRenderer implements IRenderer {
E
Erich Gamma 已提交
444 445 446

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

J
Johannes Rieken 已提交
451
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
E
Erich Gamma 已提交
452 453 454
		// noop
	}

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

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

I
isidor 已提交
473
		return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
E
Erich Gamma 已提交
474 475
	}

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

			return data;
		}

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

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

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

			return data;
		}

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

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

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

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

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

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

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

I
isidor 已提交
576
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
577 578 579 580
		// noop
	}
}

I
isidor 已提交
581
export class CallstackAccessibilityProvider implements IAccessibilityProvider {
582

J
Johannes Rieken 已提交
583
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
584 585 586
		// noop
	}

I
isidor 已提交
587 588 589
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
590
		}
I
isidor 已提交
591 592
		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));
593 594 595 596 597 598
		}

		return null;
	}
}

I
isidor 已提交
599
// variables
E
Erich Gamma 已提交
600

I
isidor 已提交
601
export class VariablesActionProvider implements IActionProvider {
E
Erich Gamma 已提交
602

I
isidor 已提交
603 604
	constructor(private instantiationService: IInstantiationService) {
		// noop
E
Erich Gamma 已提交
605 606
	}

I
isidor 已提交
607
	public hasActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
608 609 610
		return false;
	}

I
isidor 已提交
611
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
A
Alex Dima 已提交
612
		return TPromise.as([]);
I
isidor 已提交
613 614
	}

I
isidor 已提交
615 616
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Variable;
E
Erich Gamma 已提交
617 618
	}

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

I
isidor 已提交
628
		actions.push(this.instantiationService.createInstance(AddToWatchExpressionsAction, AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable));
A
Alex Dima 已提交
629
		return TPromise.as(actions);
E
Erich Gamma 已提交
630
	}
I
isidor 已提交
631

I
isidor 已提交
632
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
633 634
		return null;
	}
E
Erich Gamma 已提交
635 636
}

I
isidor 已提交
637
export class VariablesDataSource implements IDataSource {
E
Erich Gamma 已提交
638

I
isidor 已提交
639
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
640 641 642
		return element.getId();
	}

I
isidor 已提交
643 644
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof ViewModel || element instanceof Scope) {
E
Erich Gamma 已提交
645 646 647
			return true;
		}

I
isidor 已提交
648
		let variable = <Variable>element;
649
		return variable.hasChildren && !equalsIgnoreCase(variable.value, 'null');
E
Erich Gamma 已提交
650 651
	}

I
isidor 已提交
652 653 654
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof ViewModel) {
			const focusedStackFrame = (<ViewModel>element).focusedStackFrame;
655
			return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
E
Erich Gamma 已提交
656 657
		}

I
isidor 已提交
658
		let scope = <Scope>element;
659
		return scope.getChildren();
E
Erich Gamma 已提交
660 661
	}

I
isidor 已提交
662
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
663
		return TPromise.as(null);
E
Erich Gamma 已提交
664 665 666 667 668 669 670 671 672 673 674 675 676
	}
}

interface IScopeTemplateData {
	name: HTMLElement;
}

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

I
isidor 已提交
677
export class VariablesRenderer implements IRenderer {
E
Erich Gamma 已提交
678 679 680 681

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

I
isidor 已提交
682 683 684 685 686 687 688
	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		// noop
	}

I
isidor 已提交
689
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
690
		return 22;
E
Erich Gamma 已提交
691 692
	}

I
isidor 已提交
693 694
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Scope) {
E
Erich Gamma 已提交
695 696
			return VariablesRenderer.SCOPE_TEMPLATE_ID;
		}
I
isidor 已提交
697
		if (element instanceof Variable) {
E
Erich Gamma 已提交
698 699 700 701 702 703
			return VariablesRenderer.VARIABLE_TEMPLATE_ID;
		}

		return null;
	}

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

		return data;
	}

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

I
isidor 已提交
739
	private renderScope(scope: Scope, data: IScopeTemplateData): void {
E
Erich Gamma 已提交
740 741 742
		data.name.textContent = scope.name;
	}

I
isidor 已提交
743
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
744 745 746 747
		// noop
	}
}

I
isidor 已提交
748
export class VariablesAccessibilityProvider implements IAccessibilityProvider {
749

I
isidor 已提交
750 751 752
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Scope) {
			return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", (<Scope>element).name);
753
		}
I
isidor 已提交
754 755
		if (element instanceof Variable) {
			return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", (<Variable>element).name, (<Variable>element).value);
756 757 758 759 760 761
		}

		return null;
	}
}

762 763
export class VariablesController extends BaseDebugController {

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

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

777
	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
778
		const element = tree.getFocus();
779
		if (element instanceof Variable && !element.hasChildren) {
I
isidor 已提交
780
			this.debugService.getViewModel().setSelectedExpression(element);
781
			return true;
I
isidor 已提交
782 783
		}

784
		return false;
I
isidor 已提交
785
	}
786 787
}

I
isidor 已提交
788
// watch expressions
E
Erich Gamma 已提交
789

I
isidor 已提交
790
export class WatchExpressionsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
791 792 793 794 795 796 797

	private instantiationService: IInstantiationService;

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

I
isidor 已提交
798 799
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Expression && !!element.name;
E
Erich Gamma 已提交
800 801
	}

I
isidor 已提交
802
	public hasSecondaryActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
803 804 805
		return true;
	}

I
isidor 已提交
806
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
807
		return TPromise.as([]);
E
Erich Gamma 已提交
808 809
	}

I
isidor 已提交
810 811 812 813 814
	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));
815
			if (!expression.hasChildren) {
816
				actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value));
E
Erich Gamma 已提交
817
			}
I
isidor 已提交
818
			actions.push(new Separator());
E
Erich Gamma 已提交
819

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

A
Alex Dima 已提交
834
		return TPromise.as(actions);
E
Erich Gamma 已提交
835
	}
I
isidor 已提交
836

I
isidor 已提交
837
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
838 839
		return null;
	}
E
Erich Gamma 已提交
840 841
}

I
isidor 已提交
842
export class WatchExpressionsDataSource implements IDataSource {
E
Erich Gamma 已提交
843

I
isidor 已提交
844
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
845 846 847
		return element.getId();
	}

I
isidor 已提交
848 849
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof Model) {
E
Erich Gamma 已提交
850 851 852
			return true;
		}

I
isidor 已提交
853
		const watchExpression = <Expression>element;
854
		return watchExpression.hasChildren && !equalsIgnoreCase(watchExpression.value, 'null');
E
Erich Gamma 已提交
855 856
	}

I
isidor 已提交
857 858 859
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Model) {
			return TPromise.as((<Model>element).getWatchExpressions());
E
Erich Gamma 已提交
860 861
		}

I
isidor 已提交
862
		let expression = <Expression>element;
863
		return expression.getChildren();
E
Erich Gamma 已提交
864 865
	}

I
isidor 已提交
866
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
867
		return TPromise.as(null);
E
Erich Gamma 已提交
868 869 870
	}
}

I
isidor 已提交
871 872 873 874 875
interface IWatchExpressionTemplateData {
	watchExpression: HTMLElement;
	expression: HTMLElement;
	name: HTMLSpanElement;
	value: HTMLSpanElement;
E
Erich Gamma 已提交
876 877
}

I
isidor 已提交
878
export class WatchExpressionsRenderer implements IRenderer {
E
Erich Gamma 已提交
879 880 881 882 883 884

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

I
isidor 已提交
885
	constructor(
I
isidor 已提交
886 887
		actionProvider: IActionProvider,
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
888 889 890 891
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextViewService private contextViewService: IContextViewService
	) {
		this.toDispose = [];
892
		this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
E
Erich Gamma 已提交
893 894
	}

I
isidor 已提交
895
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
896
		return 22;
E
Erich Gamma 已提交
897 898
	}

I
isidor 已提交
899 900
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Expression) {
E
Erich Gamma 已提交
901 902 903 904 905 906
			return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
		}

		return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
	}

I
isidor 已提交
907
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
908 909 910 911 912 913
		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 已提交
914
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
I
isidor 已提交
915 916 917 918 919
			const data: IWatchExpressionTemplateData = Object.create(null);
			data.watchExpression = dom.append(container, $('.watch-expression'));
			createVariableTemplate(data, data.watchExpression);

			return data;
E
Erich Gamma 已提交
920 921
		}

I
isidor 已提交
922 923
		const data: IVariableTemplateData = Object.create(null);
		createVariableTemplate(data, container);
E
Erich Gamma 已提交
924 925 926 927

		return data;
	}

I
isidor 已提交
928
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
929 930 931
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
			this.renderWatchExpression(tree, element, templateData);
		} else {
932
			renderVariable(tree, element, templateData, true);
E
Erich Gamma 已提交
933 934 935
		}
	}

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

946
		data.name.textContent = watchExpression.name;
947
		if (watchExpression.value) {
948
			data.name.textContent += ':';
949 950 951
			renderExpressionValue(watchExpression, data.value, {
				showChanged: true,
				maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
I
isidor 已提交
952 953
				preserveWhitespace: false,
				showHover: true
954
			});
955
			data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
E
Erich Gamma 已提交
956 957 958
		}
	}

I
isidor 已提交
959
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
I
isidor 已提交
960
		// noop
E
Erich Gamma 已提交
961 962 963
	}

	public dispose(): void {
J
Joao Moreno 已提交
964
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
965 966 967
	}
}

I
isidor 已提交
968
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
969

I
isidor 已提交
970 971 972
	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);
973
		}
I
isidor 已提交
974 975
		if (element instanceof Variable) {
			return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
976 977 978 979 980 981
		}

		return null;
	}
}

E
Erich Gamma 已提交
982 983
export class WatchExpressionsController extends BaseDebugController {

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

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

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

		return false;
	}

I
isidor 已提交
1010
	protected onDelete(tree: ITree, event: IKeyboardEvent): boolean {
I
isidor 已提交
1011
		const element = tree.getFocus();
I
isidor 已提交
1012 1013
		if (element instanceof Expression) {
			const we = <Expression>element;
1014
			this.debugService.removeWatchExpressions(we.getId());
E
Erich Gamma 已提交
1015 1016 1017 1018 1019 1020 1021 1022

			return true;
		}

		return false;
	}
}

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

		return DRAG_OVER_REJECT;
I
isidor 已提交
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
	}

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

I
isidor 已提交
1069
export class BreakpointsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
1070

I
isidor 已提交
1071
	constructor(private instantiationService: IInstantiationService, private debugService: debug.IDebugService) {
I
isidor 已提交
1072
		// noop
E
Erich Gamma 已提交
1073 1074
	}

I
isidor 已提交
1075
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
1076
		return false;;
E
Erich Gamma 已提交
1077 1078
	}

I
isidor 已提交
1079 1080
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
E
Erich Gamma 已提交
1081 1082
	}

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

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

I
isidor 已提交
1090 1091
		if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
1092
		}
I
isidor 已提交
1093 1094 1095
		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 已提交
1096

I
isidor 已提交
1097 1098
			actions.push(this.instantiationService.createInstance(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL));
			actions.push(this.instantiationService.createInstance(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL));
1099
		}
I
isidor 已提交
1100

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

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

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

I
isidor 已提交
1112
export class BreakpointsDataSource implements IDataSource {
E
Erich Gamma 已提交
1113

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

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

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

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

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

1134
interface IBaseBreakpointTemplateData {
1135
	breakpoint: HTMLElement;
E
Erich Gamma 已提交
1136 1137
	name: HTMLElement;
	checkbox: HTMLInputElement;
1138 1139
	context: debug.IEnablement;
	toDispose: lifecycle.IDisposable[];
E
Erich Gamma 已提交
1140 1141
}

1142
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
E
Erich Gamma 已提交
1143 1144 1145 1146
	lineNumber: HTMLElement;
	filePath: HTMLElement;
}

I
isidor 已提交
1147
export class BreakpointsRenderer implements IRenderer {
E
Erich Gamma 已提交
1148 1149

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

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

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

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

		return null;
	}

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

1185
		data.checkbox = <HTMLInputElement>$('input');
E
Erich Gamma 已提交
1186
		data.checkbox.type = 'checkbox';
1187 1188 1189 1190
		data.toDispose = [];
		data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
			this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
		}));
1191

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

1194
		data.name = dom.append(data.breakpoint, $('span.name'));
E
Erich Gamma 已提交
1195 1196

		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
1197
			data.filePath = dom.append(data.breakpoint, $('span.file-path'));
I
isidor 已提交
1198 1199 1200 1201 1202
			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 已提交
1203 1204 1205 1206 1207
		}

		return data;
	}

I
isidor 已提交
1208
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
1209
		templateData.context = element;
E
Erich Gamma 已提交
1210 1211
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderExceptionBreakpoint(element, templateData);
1212 1213
		} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderFunctionBreakpoint(tree, element, templateData);
E
Erich Gamma 已提交
1214 1215 1216 1217 1218
		} else {
			this.renderBreakpoint(tree, element, templateData);
		}
	}

1219
	private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IBaseBreakpointTemplateData): void {
1220
		data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;;
1221
		data.breakpoint.title = data.name.textContent;
E
Erich Gamma 已提交
1222
		data.checkbox.checked = exceptionBreakpoint.enabled;
1223
	}
E
Erich Gamma 已提交
1224

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

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

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

1255
		data.name.textContent = getPathLabel(paths.basename(breakpoint.uri.fsPath), this.contextService);
1256
		data.lineNumber.textContent = breakpoint.lineNumber.toString();
1257
		data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService);
E
Erich Gamma 已提交
1258
		data.checkbox.checked = breakpoint.enabled;
I
isidor 已提交
1259

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

I
isidor 已提交
1271
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
1272
		lifecycle.dispose(templateData.toDispose);
E
Erich Gamma 已提交
1273 1274 1275
	}
}

I
isidor 已提交
1276
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
1277

J
Johannes Rieken 已提交
1278
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
1279 1280 1281
		// noop
	}

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

		return null;
	}
}

E
Erich Gamma 已提交
1297 1298
export class BreakpointsController extends BaseDebugController {

I
isidor 已提交
1299 1300
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
		if (element instanceof FunctionBreakpoint && event.detail === 2) {
I
isidor 已提交
1301 1302 1303
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
I
isidor 已提交
1304
		if (element instanceof Breakpoint) {
1305 1306
			this.openBreakpointSource(element, event, true);
		}
I
isidor 已提交
1307 1308

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

I
isidor 已提交
1311
	protected onRename(tree: ITree, event: IKeyboardEvent): boolean {
1312
		const element = tree.getFocus();
I
isidor 已提交
1313
		if (element instanceof FunctionBreakpoint && element.name) {
1314 1315 1316
			this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
			return true;
		}
1317 1318 1319 1320 1321 1322

		return false;
	}

	protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
		const element = tree.getFocus();
I
isidor 已提交
1323
		if (element instanceof Breakpoint) {
1324
			this.openBreakpointSource(element, event, false);
1325
			return true;
1326 1327 1328 1329 1330
		}

		return super.onEnter(tree, event);
	}

I
isidor 已提交
1331
	protected onSpace(tree: ITree, event: IKeyboardEvent): boolean {
1332 1333
		super.onSpace(tree, event);
		const element = <debug.IEnablement>tree.getFocus();
1334
		this.debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
1335 1336 1337 1338

		return true;
	}

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

			return true;
		}

		return false;
	}
1353

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