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

I
isidor 已提交
6
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
8
import * as lifecycle from 'vs/base/common/lifecycle';
9
import { KeyCode } 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';
I
isidor 已提交
14
import * as dom from 'vs/base/browser/dom';
I
isidor 已提交
15
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
I
isidor 已提交
16 17
import { getPathLabel } from 'vs/base/common/labels';
import { IAction, IActionRunner } from 'vs/base/common/actions';
I
isidor 已提交
18
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
19
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, DRAG_OVER_REJECT, IDragAndDropData, IDragOverReaction, IActionProvider } from 'vs/base/parts/tree/browser/tree';
J
Johannes Rieken 已提交
20
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
21
import { DefaultController, DefaultDragAndDrop, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
I
isidor 已提交
22
import { Constants } from 'vs/editor/common/core/uint';
23 24 25 26
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';
27 28
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
29
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
I
isidor 已提交
30
import * as debug from 'vs/workbench/parts/debug/common/debug';
31
import { Expression, Variable, FunctionBreakpoint, StackFrame, Thread, Process, Breakpoint, ExceptionBreakpoint, Model, Scope, ThreadAndProcessIds } from 'vs/workbench/parts/debug/common/debugModel';
I
isidor 已提交
32
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
I
isidor 已提交
33
import { ContinueAction, StepOverAction, PauseAction, ReapplyBreakpointsAction, DisableAllBreakpointsAction, RemoveBreakpointAction, RemoveWatchExpressionAction, AddWatchExpressionAction, EditWatchExpressionAction, RemoveAllBreakpointsAction, EnableAllBreakpointsAction, StepOutAction, StepIntoAction, SetValueAction, RemoveAllWatchExpressionsAction, RestartFrameAction, AddToWatchExpressionsAction, StopAction, RestartAction } from 'vs/workbench/parts/debug/browser/debugActions';
I
isidor 已提交
34
import { CopyValueAction, CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
J
Johannes Rieken 已提交
35
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
J
Joao Moreno 已提交
36
import { once } from 'vs/base/common/functional';
B
Benjamin Pasero 已提交
37 38
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
39
import { IEnvironmentService } from "vs/platform/environment/common/environment";
E
Erich Gamma 已提交
40

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
122
function renderRenameBox(debugService: debug.IDebugService, contextViewService: IContextViewService, themeService: IThemeService, tree: ITree, element: any, container: HTMLElement, options: IRenameBoxOptions): void {
123
	let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
124
	let inputBox = new InputBox(inputBoxContainer, contextViewService, {
I
isidor 已提交
125 126 127
		validationOptions: options.validationOptions,
		placeholder: options.placeholder,
		ariaLabel: options.ariaLabel
128
	});
B
Benjamin Pasero 已提交
129
	const styler = attachInputBoxStyler(inputBox, themeService);
130

I
isidor 已提交
131
	tree.setHighlight();
I
isidor 已提交
132
	inputBox.value = options.initialValue ? options.initialValue : '';
133
	inputBox.focus();
134
	inputBox.select();
135

I
isidor 已提交
136
	let disposed = false;
B
Benjamin Pasero 已提交
137
	const toDispose: [lifecycle.IDisposable] = [inputBox, styler];
138

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

159 160
			tree.clearHighlight();
			tree.DOMFocus();
161
			tree.setFocus(element);
162

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

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

183
function getSourceName(source: Source, contextService: IWorkspaceContextService, environmentService?: IEnvironmentService): string {
I
isidor 已提交
184
	if (source.name) {
185 186 187
		return source.name;
	}

188
	return getPathLabel(paths.basename(source.uri.fsPath), contextService, environmentService);
189 190
}

I
isidor 已提交
191
export class BaseDebugController extends DefaultController {
E
Erich Gamma 已提交
192

193 194
	private contributedContextMenu: IMenu;

I
isidor 已提交
195
	constructor(
I
isidor 已提交
196
		private actionProvider: IActionProvider,
197
		menuId: MenuId,
198
		@debug.IDebugService protected debugService: debug.IDebugService,
199
		@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
200
		@IContextMenuService private contextMenuService: IContextMenuService,
201 202
		@IContextKeyService contextKeyService: IContextKeyService,
		@IMenuService menuService: IMenuService
I
isidor 已提交
203
	) {
204
		super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
E
Erich Gamma 已提交
205

206
		this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService);
E
Erich Gamma 已提交
207 208
	}

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

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

217
		tree.setFocus(element);
E
Erich Gamma 已提交
218 219

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

			return true;
		}

		return false;
	}

241 242 243
	protected getContext(element: any): any {
		return undefined;
	}
E
Erich Gamma 已提交
244 245
}

I
isidor 已提交
246
// call stack
E
Erich Gamma 已提交
247

248 249
export class CallStackController extends BaseDebugController {

I
isidor 已提交
250
	protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
I
isidor 已提交
251
		if (element instanceof ThreadAndProcessIds) {
252 253
			return this.showMoreStackFrames(tree, element);
		}
254
		if (element instanceof StackFrame) {
255
			super.onLeftClick(tree, element, event);
256
			this.focusStackFrame(element, event, event.detail !== 2);
257
			return true;
258
		}
259 260 261 262

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

263 264
	protected getContext(element: any): any {
		if (element instanceof StackFrame) {
265 266 267 268
			if (element.source.inMemory) {
				return element.source.raw.path || element.source.reference;
			}

269 270 271
			return element.source.uri.toString();
		}
	}
I
isidor 已提交
272

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

		return true;
	}
284

285
	public focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
286
		this.debugService.focusStackFrameAndEvaluate(stackFrame).then(() => {
I
isidor 已提交
287
			const sideBySide = (event && (event.ctrlKey || event.metaKey));
I
isidor 已提交
288
			return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide);
289
		}, errors.onUnexpectedError);
290
	}
291 292 293
}


I
isidor 已提交
294
export class CallStackActionProvider implements IActionProvider {
I
isidor 已提交
295

J
Johannes Rieken 已提交
296
	constructor( @IInstantiationService private instantiationService: IInstantiationService, @debug.IDebugService private debugService: debug.IDebugService) {
I
isidor 已提交
297 298 299
		// noop
	}

I
isidor 已提交
300
	public hasActions(tree: ITree, element: any): boolean {
I
isidor 已提交
301 302 303
		return false;
	}

I
isidor 已提交
304
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
305 306 307
		return TPromise.as([]);
	}

I
isidor 已提交
308
	public hasSecondaryActions(tree: ITree, element: any): boolean {
309
		return element !== tree.getInput();
I
isidor 已提交
310 311
	}

I
isidor 已提交
312 313
	public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
		const actions: IAction[] = [];
314 315 316 317
		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 已提交
318
			const thread = <Thread>element;
A
Andre Weinand 已提交
319
			if (thread.stopped) {
I
isidor 已提交
320 321 322 323
				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 已提交
324
			} else {
I
isidor 已提交
325
				actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL));
A
Andre Weinand 已提交
326
			}
I
isidor 已提交
327
		} else if (element instanceof StackFrame) {
328
			if (element.thread.process.session.capabilities.supportsRestartFrame) {
I
isidor 已提交
329
				actions.push(this.instantiationService.createInstance(RestartFrameAction, RestartFrameAction.ID, RestartFrameAction.LABEL));
330
			}
I
isidor 已提交
331
			actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
I
isidor 已提交
332 333 334 335 336
		}

		return TPromise.as(actions);
	}

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

I
isidor 已提交
342
export class CallStackDataSource implements IDataSource {
E
Erich Gamma 已提交
343

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

E
Erich Gamma 已提交
349 350 351
		return element.getId();
	}

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

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

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

I
isidor 已提交
368
	private getThreadChildren(thread: Thread): TPromise<any> {
I
isidor 已提交
369
		let callStack: any[] = thread.getCallStack();
I
isidor 已提交
370
		if (!callStack || !callStack.length) {
I
isidor 已提交
371
			thread.fetchCallStack().then(() => callStack = thread.getCallStack());
372
		}
373 374 375
		if (callStack.length === 1) {
			// To reduce flashing of the call stack view simply append the stale call stack
			// once we have the correct data the tree will refresh and we will no longer display it.
I
isidor 已提交
376
			return TPromise.as(callStack.concat(thread.getStaleCallStack().slice(1)));
377
		}
378

I
isidor 已提交
379
		if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
I
isidor 已提交
380
			return TPromise.as(callStack.concat([thread.stoppedDetails.framesErrorMessage]));
I
isidor 已提交
381
		}
382
		if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
I
isidor 已提交
383
			return TPromise.as(callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]));
I
isidor 已提交
384
		}
I
isidor 已提交
385

I
isidor 已提交
386
		return TPromise.as(callStack);
I
isidor 已提交
387 388
	}

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

interface IThreadTemplateData {
I
isidor 已提交
395
	thread: HTMLElement;
E
Erich Gamma 已提交
396
	name: HTMLElement;
I
isidor 已提交
397 398
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
E
Erich Gamma 已提交
399 400
}

I
isidor 已提交
401 402 403
interface IProcessTemplateData {
	process: HTMLElement;
	name: HTMLElement;
I
isidor 已提交
404 405
	state: HTMLElement;
	stateLabel: HTMLSpanElement;
I
isidor 已提交
406 407
}

408 409 410 411
interface IErrorTemplateData {
	label: HTMLElement;
}

I
isidor 已提交
412 413 414 415
interface ILoadMoreTemplateData {
	label: HTMLElement;
}

E
Erich Gamma 已提交
416 417
interface IStackFrameTemplateData {
	stackFrame: HTMLElement;
418 419 420 421
	label: HTMLElement;
	file: HTMLElement;
	fileName: HTMLElement;
	lineNumber: HTMLElement;
E
Erich Gamma 已提交
422 423
}

I
isidor 已提交
424
export class CallStackRenderer implements IRenderer {
E
Erich Gamma 已提交
425 426 427

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

432 433 434 435
	constructor(
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IEnvironmentService private environmentService: IEnvironmentService
	) {
E
Erich Gamma 已提交
436 437 438
		// noop
	}

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

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

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

I
isidor 已提交
460
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
461 462 463 464
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
			let data: IProcessTemplateData = Object.create(null);
			data.process = dom.append(container, $('.process'));
			data.name = dom.append(data.process, $('.name'));
I
isidor 已提交
465 466
			data.state = dom.append(data.process, $('.state'));
			data.stateLabel = dom.append(data.state, $('span.label'));
I
isidor 已提交
467 468 469 470

			return data;
		}

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

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

			return data;
		}
E
Erich Gamma 已提交
483 484
		if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
			let data: IThreadTemplateData = Object.create(null);
I
isidor 已提交
485 486 487 488
			data.thread = dom.append(container, $('.thread'));
			data.name = dom.append(data.thread, $('.name'));
			data.state = dom.append(data.thread, $('.state'));
			data.stateLabel = dom.append(data.state, $('span.label'));
E
Erich Gamma 已提交
489 490 491 492 493 494

			return data;
		}

		let data: IStackFrameTemplateData = Object.create(null);
		data.stackFrame = dom.append(container, $('.stack-frame'));
495
		data.label = dom.append(data.stackFrame, $('span.label.expression'));
E
Erich Gamma 已提交
496 497 498 499 500 501 502
		data.file = dom.append(data.stackFrame, $('.file'));
		data.fileName = dom.append(data.file, $('span.file-name'));
		data.lineNumber = dom.append(data.file, $('span.line-number'));

		return data;
	}

I
isidor 已提交
503
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
504
		if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
I
isidor 已提交
505 506
			this.renderProcess(element, templateData);
		} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
E
Erich Gamma 已提交
507
			this.renderThread(element, templateData);
I
isidor 已提交
508
		} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
E
Erich Gamma 已提交
509
			this.renderStackFrame(element, templateData);
510 511
		} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
			this.renderError(element, templateData);
I
isidor 已提交
512
		} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
I
isidor 已提交
513
			this.renderLoadMore(element, templateData);
E
Erich Gamma 已提交
514 515 516
		}
	}

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

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

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

I
isidor 已提交
530 531 532 533 534
		if (thread.stopped) {
			data.stateLabel.textContent = thread.stoppedDetails.description || nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", thread.stoppedDetails.reason);
		} else {
			data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
		}
E
Erich Gamma 已提交
535 536
	}

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

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

E
Erich Gamma 已提交
546
	private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void {
I
isidor 已提交
547 548 549
		dom.toggleClass(data.stackFrame, 'disabled', stackFrame.source.presentationHint === 'deemphasize');
		dom.toggleClass(data.stackFrame, 'label', stackFrame.source.presentationHint === 'label');
		dom.toggleClass(data.stackFrame, 'subtle', stackFrame.source.presentationHint === 'subtle');
550

551
		data.file.title = stackFrame.source.raw.path || stackFrame.source.name;
552 553 554
		if (stackFrame.source.raw.origin) {
			data.file.title += `\n${stackFrame.source.raw.origin}`;
		}
E
Erich Gamma 已提交
555
		data.label.textContent = stackFrame.name;
556
		data.label.title = stackFrame.name;
557
		data.fileName.textContent = getSourceName(stackFrame.source, this.contextService, this.environmentService);
I
isidor 已提交
558 559 560 561
		if (stackFrame.range.startLineNumber !== undefined) {
			data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
			if (stackFrame.range.startColumn) {
				data.lineNumber.textContent += `:${stackFrame.range.startColumn}`;
I
isidor 已提交
562
			}
I
isidor 已提交
563 564 565 566
			dom.removeClass(data.lineNumber, 'unavailable');
		} else {
			dom.addClass(data.lineNumber, 'unavailable');
		}
E
Erich Gamma 已提交
567 568
	}

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

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

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

I
isidor 已提交
580 581 582
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Thread) {
			return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
583
		}
I
isidor 已提交
584
		if (element instanceof StackFrame) {
I
isidor 已提交
585
			return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", (<StackFrame>element).name, (<StackFrame>element).range.startLineNumber, getSourceName((<StackFrame>element).source, this.contextService));
586 587 588 589 590 591
		}

		return null;
	}
}

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

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

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

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

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

I
isidor 已提交
608
	public hasSecondaryActions(tree: ITree, element: any): boolean {
609 610
		// Only show context menu on "real" variables. Not on array chunk nodes.
		return element instanceof Variable && !!element.value;
E
Erich Gamma 已提交
611 612
	}

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

A
Alex Dima 已提交
621
		return TPromise.as(actions);
E
Erich Gamma 已提交
622
	}
I
isidor 已提交
623

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

I
isidor 已提交
629
export class VariablesDataSource implements IDataSource {
E
Erich Gamma 已提交
630

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

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

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

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

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

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

interface IScopeTemplateData {
	name: HTMLElement;
}

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

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

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

I
isidor 已提交
674 675
	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
B
Benjamin Pasero 已提交
676 677
		@IContextViewService private contextViewService: IContextViewService,
		@IThemeService private themeService: IThemeService
I
isidor 已提交
678 679 680 681
	) {
		// noop
	}

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

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

		return null;
	}

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

		return data;
	}

I
isidor 已提交
713
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
E
Erich Gamma 已提交
714 715 716
		if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
			this.renderScope(element, templateData);
		} else {
I
isidor 已提交
717
			const variable = <Variable>element;
718
			if (variable === this.debugService.getViewModel().getSelectedExpression() || variable.errorMessage) {
B
Benjamin Pasero 已提交
719
				renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, variable, (<IVariableTemplateData>templateData).expression, {
720 721 722 723 724
					initialValue: variable.value,
					ariaLabel: nls.localize('variableValueAriaLabel', "Type new variable value"),
					validationOptions: {
						validation: (value: string) => variable.errorMessage ? ({ content: variable.errorMessage }) : null
					}
I
isidor 已提交
725
				});
I
isidor 已提交
726
			} else {
727
				renderVariable(tree, variable, templateData, true);
I
isidor 已提交
728
			}
E
Erich Gamma 已提交
729 730 731
		}
	}

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

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

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

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

		return null;
	}
}

755 756
export class VariablesController extends BaseDebugController {

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

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

I
isidor 已提交
769
// watch expressions
E
Erich Gamma 已提交
770

I
isidor 已提交
771
export class WatchExpressionsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
772 773 774 775 776 777 778

	private instantiationService: IInstantiationService;

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

I
isidor 已提交
779 780
	public hasActions(tree: ITree, element: any): boolean {
		return element instanceof Expression && !!element.name;
E
Erich Gamma 已提交
781 782
	}

I
isidor 已提交
783
	public hasSecondaryActions(tree: ITree, element: any): boolean {
E
Erich Gamma 已提交
784 785 786
		return true;
	}

I
isidor 已提交
787
	public getActions(tree: ITree, element: any): TPromise<IAction[]> {
I
isidor 已提交
788
		return TPromise.as([]);
E
Erich Gamma 已提交
789 790
	}

I
isidor 已提交
791 792 793 794 795
	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));
I
isidor 已提交
796
			actions.push(this.instantiationService.createInstance(EditWatchExpressionAction, EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL));
797
			if (!expression.hasChildren) {
798
				actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value));
E
Erich Gamma 已提交
799
			}
I
isidor 已提交
800
			actions.push(new Separator());
E
Erich Gamma 已提交
801

I
isidor 已提交
802 803
			actions.push(this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL));
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
804
		} else {
I
isidor 已提交
805 806 807
			actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
			if (element instanceof Variable) {
				const variable = <Variable>element;
808
				if (!variable.hasChildren) {
809
					actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable.value));
E
Erich Gamma 已提交
810
				}
I
isidor 已提交
811
				actions.push(new Separator());
E
Erich Gamma 已提交
812
			}
I
isidor 已提交
813
			actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
E
Erich Gamma 已提交
814 815
		}

A
Alex Dima 已提交
816
		return TPromise.as(actions);
E
Erich Gamma 已提交
817
	}
I
isidor 已提交
818

I
isidor 已提交
819
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
820 821
		return null;
	}
E
Erich Gamma 已提交
822 823
}

I
isidor 已提交
824
export class WatchExpressionsDataSource implements IDataSource {
E
Erich Gamma 已提交
825

I
isidor 已提交
826
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
827 828 829
		return element.getId();
	}

I
isidor 已提交
830 831
	public hasChildren(tree: ITree, element: any): boolean {
		if (element instanceof Model) {
E
Erich Gamma 已提交
832 833 834
			return true;
		}

I
isidor 已提交
835
		const watchExpression = <Expression>element;
836
		return watchExpression.hasChildren && !equalsIgnoreCase(watchExpression.value, 'null');
E
Erich Gamma 已提交
837 838
	}

I
isidor 已提交
839 840 841
	public getChildren(tree: ITree, element: any): TPromise<any> {
		if (element instanceof Model) {
			return TPromise.as((<Model>element).getWatchExpressions());
E
Erich Gamma 已提交
842 843
		}

I
isidor 已提交
844
		let expression = <Expression>element;
845
		return expression.getChildren();
E
Erich Gamma 已提交
846 847
	}

I
isidor 已提交
848
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
849
		return TPromise.as(null);
E
Erich Gamma 已提交
850 851 852
	}
}

I
isidor 已提交
853 854 855 856 857
interface IWatchExpressionTemplateData {
	watchExpression: HTMLElement;
	expression: HTMLElement;
	name: HTMLSpanElement;
	value: HTMLSpanElement;
E
Erich Gamma 已提交
858 859
}

I
isidor 已提交
860
export class WatchExpressionsRenderer implements IRenderer {
E
Erich Gamma 已提交
861 862 863 864 865 866

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

I
isidor 已提交
867
	constructor(
I
isidor 已提交
868 869
		actionProvider: IActionProvider,
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
870
		@debug.IDebugService private debugService: debug.IDebugService,
B
Benjamin Pasero 已提交
871 872
		@IContextViewService private contextViewService: IContextViewService,
		@IThemeService private themeService: IThemeService
E
Erich Gamma 已提交
873 874
	) {
		this.toDispose = [];
875
		this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
E
Erich Gamma 已提交
876 877
	}

I
isidor 已提交
878
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
879
		return 22;
E
Erich Gamma 已提交
880 881
	}

I
isidor 已提交
882 883
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Expression) {
E
Erich Gamma 已提交
884 885 886 887 888 889
			return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
		}

		return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
	}

I
isidor 已提交
890
	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
I
isidor 已提交
891 892 893 894 895 896
		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 已提交
897
		if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
I
isidor 已提交
898 899 900 901 902
			const data: IWatchExpressionTemplateData = Object.create(null);
			data.watchExpression = dom.append(container, $('.watch-expression'));
			createVariableTemplate(data, data.watchExpression);

			return data;
E
Erich Gamma 已提交
903 904
		}

I
isidor 已提交
905 906
		const data: IVariableTemplateData = Object.create(null);
		createVariableTemplate(data, container);
E
Erich Gamma 已提交
907 908 909 910

		return data;
	}

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

I
isidor 已提交
919
	private renderWatchExpression(tree: ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
E
Erich Gamma 已提交
920
		let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
I
isidor 已提交
921
		if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
B
Benjamin Pasero 已提交
922
			renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, watchExpression, data.expression, {
I
isidor 已提交
923 924 925 926
				initialValue: watchExpression.name,
				placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
				ariaLabel: nls.localize('watchExpressionInputAriaLabel', "Type watch expression")
			});
E
Erich Gamma 已提交
927 928
		}

929
		data.name.textContent = watchExpression.name;
930
		if (watchExpression.value) {
931
			data.name.textContent += ':';
932 933 934
			renderExpressionValue(watchExpression, data.value, {
				showChanged: true,
				maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
I
isidor 已提交
935 936
				preserveWhitespace: false,
				showHover: true
937
			});
938
			data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
E
Erich Gamma 已提交
939 940 941
		}
	}

I
isidor 已提交
942
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
I
isidor 已提交
943
		// noop
E
Erich Gamma 已提交
944 945 946
	}

	public dispose(): void {
J
Joao Moreno 已提交
947
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
948 949 950
	}
}

I
isidor 已提交
951
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
952

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

		return null;
	}
}

E
Erich Gamma 已提交
965 966
export class WatchExpressionsController extends BaseDebugController {

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

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

I
isidor 已提交
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
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 {
1002
		if (target instanceof Expression || target instanceof Model) {
1003 1004 1005 1006
			return {
				accept: true,
				autoExpand: false
			};
1007 1008 1009
		}

		return DRAG_OVER_REJECT;
I
isidor 已提交
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
	}

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

I
isidor 已提交
1025
export class BreakpointsActionProvider implements IActionProvider {
E
Erich Gamma 已提交
1026

I
isidor 已提交
1027
	constructor(private instantiationService: IInstantiationService, private debugService: debug.IDebugService) {
I
isidor 已提交
1028
		// noop
E
Erich Gamma 已提交
1029 1030
	}

I
isidor 已提交
1031
	public hasActions(tree: ITree, element: any): boolean {
1032
		return false;
E
Erich Gamma 已提交
1033 1034
	}

I
isidor 已提交
1035 1036
	public hasSecondaryActions(tree: ITree, element: any): boolean {
		return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
E
Erich Gamma 已提交
1037 1038
	}

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

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

I
isidor 已提交
1046 1047
		if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
			actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
1048
		}
I
isidor 已提交
1049 1050 1051
		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 已提交
1052

I
isidor 已提交
1053 1054
			actions.push(this.instantiationService.createInstance(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL));
			actions.push(this.instantiationService.createInstance(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL));
1055
		}
I
isidor 已提交
1056

I
isidor 已提交
1057
		actions.push(new Separator());
I
isidor 已提交
1058
		actions.push(this.instantiationService.createInstance(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL));
E
Erich Gamma 已提交
1059

A
Alex Dima 已提交
1060
		return TPromise.as(actions);
E
Erich Gamma 已提交
1061
	}
I
isidor 已提交
1062

I
isidor 已提交
1063
	public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
I
isidor 已提交
1064 1065
		return null;
	}
E
Erich Gamma 已提交
1066 1067
}

I
isidor 已提交
1068
export class BreakpointsDataSource implements IDataSource {
E
Erich Gamma 已提交
1069

I
isidor 已提交
1070
	public getId(tree: ITree, element: any): string {
E
Erich Gamma 已提交
1071 1072 1073
		return element.getId();
	}

I
isidor 已提交
1074 1075
	public hasChildren(tree: ITree, element: any): boolean {
		return element instanceof Model;
E
Erich Gamma 已提交
1076 1077
	}

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

A
Alex Dima 已提交
1082
		return TPromise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
E
Erich Gamma 已提交
1083 1084
	}

I
isidor 已提交
1085
	public getParent(tree: ITree, element: any): TPromise<any> {
A
Alex Dima 已提交
1086
		return TPromise.as(null);
E
Erich Gamma 已提交
1087 1088 1089
	}
}

1090
interface IBaseBreakpointTemplateData {
1091
	breakpoint: HTMLElement;
E
Erich Gamma 已提交
1092 1093
	name: HTMLElement;
	checkbox: HTMLInputElement;
1094 1095
	context: debug.IEnablement;
	toDispose: lifecycle.IDisposable[];
E
Erich Gamma 已提交
1096 1097
}

1098
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
E
Erich Gamma 已提交
1099 1100 1101 1102
	lineNumber: HTMLElement;
	filePath: HTMLElement;
}

I
isidor 已提交
1103
export class BreakpointsRenderer implements IRenderer {
E
Erich Gamma 已提交
1104 1105

	private static EXCEPTION_BREAKPOINT_TEMPLATE_ID = 'exceptionBreakpoint';
1106
	private static FUNCTION_BREAKPOINT_TEMPLATE_ID = 'functionBreakpoint';
E
Erich Gamma 已提交
1107 1108 1109 1110
	private static BREAKPOINT_TEMPLATE_ID = 'breakpoint';

	constructor(
		private actionProvider: BreakpointsActionProvider,
I
isidor 已提交
1111
		private actionRunner: IActionRunner,
E
Erich Gamma 已提交
1112
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
1113
		@debug.IDebugService private debugService: debug.IDebugService,
B
Benjamin Pasero 已提交
1114
		@IContextViewService private contextViewService: IContextViewService,
1115 1116
		@IThemeService private themeService: IThemeService,
		@IEnvironmentService private environmentService: IEnvironmentService
E
Erich Gamma 已提交
1117 1118 1119 1120
	) {
		// noop
	}

I
isidor 已提交
1121
	public getHeight(tree: ITree, element: any): number {
I
isidor 已提交
1122
		return 22;
E
Erich Gamma 已提交
1123 1124
	}

I
isidor 已提交
1125 1126
	public getTemplateId(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
E
Erich Gamma 已提交
1127 1128
			return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1129
		if (element instanceof FunctionBreakpoint) {
1130 1131
			return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
		}
I
isidor 已提交
1132
		if (element instanceof ExceptionBreakpoint) {
E
Erich Gamma 已提交
1133 1134 1135 1136 1137 1138
			return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
		}

		return null;
	}

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

1143
		data.checkbox = <HTMLInputElement>$('input');
E
Erich Gamma 已提交
1144
		data.checkbox.type = 'checkbox';
1145 1146 1147 1148
		data.toDispose = [];
		data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
			this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
		}));
1149

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

1152
		data.name = dom.append(data.breakpoint, $('span.name'));
E
Erich Gamma 已提交
1153 1154

		if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
1155
			data.filePath = dom.append(data.breakpoint, $('span.file-path'));
I
isidor 已提交
1156 1157 1158 1159 1160
			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 已提交
1161 1162 1163 1164 1165
		}

		return data;
	}

I
isidor 已提交
1166
	public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
1167
		templateData.context = element;
E
Erich Gamma 已提交
1168 1169
		if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderExceptionBreakpoint(element, templateData);
1170 1171
		} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
			this.renderFunctionBreakpoint(tree, element, templateData);
E
Erich Gamma 已提交
1172 1173 1174 1175 1176
		} else {
			this.renderBreakpoint(tree, element, templateData);
		}
	}

1177
	private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IBaseBreakpointTemplateData): void {
1178
		data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
1179
		data.breakpoint.title = data.name.textContent;
E
Erich Gamma 已提交
1180
		data.checkbox.checked = exceptionBreakpoint.enabled;
1181
	}
E
Erich Gamma 已提交
1182

1183
	private renderFunctionBreakpoint(tree: ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IBaseBreakpointTemplateData): void {
I
isidor 已提交
1184 1185
		const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
		if (!functionBreakpoint.name || (selected && selected.getId() === functionBreakpoint.getId())) {
1186
			data.name.textContent = '';
B
Benjamin Pasero 已提交
1187
			renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, functionBreakpoint, data.breakpoint, {
I
isidor 已提交
1188 1189 1190 1191
				initialValue: functionBreakpoint.name,
				placeholder: nls.localize('functionBreakpointPlaceholder', "Function to break on"),
				ariaLabel: nls.localize('functionBreakPointInputAriaLabel', "Type function breakpoint")
			});
1192 1193 1194
		} else {
			data.name.textContent = functionBreakpoint.name;
			data.checkbox.checked = functionBreakpoint.enabled;
1195 1196 1197
			data.breakpoint.title = functionBreakpoint.name;

			// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
1198
			const process = this.debugService.getViewModel().focusedProcess;
1199
			if ((process && !process.session.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
1200
				tree.addTraits('disabled', [functionBreakpoint]);
1201
				if (process && !process.session.capabilities.supportsFunctionBreakpoints) {
1202 1203 1204 1205 1206
					data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
				}
			} else {
				tree.removeTraits('disabled', [functionBreakpoint]);
			}
1207
		}
E
Erich Gamma 已提交
1208 1209
	}

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

1213
		data.name.textContent = getPathLabel(paths.basename(breakpoint.uri.fsPath), this.contextService);
1214
		data.lineNumber.textContent = breakpoint.lineNumber.toString();
I
isidor 已提交
1215 1216 1217
		if (breakpoint.column) {
			data.lineNumber.textContent += `:${breakpoint.column}`;
		}
1218
		data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService, this.environmentService);
E
Erich Gamma 已提交
1219
		data.checkbox.checked = breakpoint.enabled;
I
isidor 已提交
1220

I
isidor 已提交
1221
		const debugActive = this.debugService.state === debug.State.Running || this.debugService.state === debug.State.Stopped || this.debugService.state === debug.State.Initializing;
I
isidor 已提交
1222 1223 1224 1225 1226
		if (debugActive && !breakpoint.verified) {
			tree.addTraits('disabled', [breakpoint]);
			if (breakpoint.message) {
				data.breakpoint.title = breakpoint.message;
			}
1227 1228
		} else if (breakpoint.condition || breakpoint.hitCondition) {
			data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
1229
		}
E
Erich Gamma 已提交
1230 1231
	}

I
isidor 已提交
1232
	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
1233
		lifecycle.dispose(templateData.toDispose);
E
Erich Gamma 已提交
1234 1235 1236
	}
}

I
isidor 已提交
1237
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
1238

J
Johannes Rieken 已提交
1239
	constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
1240 1241 1242
		// noop
	}

I
isidor 已提交
1243 1244
	public getAriaLabel(tree: ITree, element: any): string {
		if (element instanceof Breakpoint) {
1245
			return nls.localize('breakpointAriaLabel', "Breakpoint line {0} {1}, breakpoints, debug", (<Breakpoint>element).lineNumber, getPathLabel(paths.basename((<Breakpoint>element).uri.fsPath), this.contextService), this.contextService);
1246
		}
I
isidor 已提交
1247 1248
		if (element instanceof FunctionBreakpoint) {
			return nls.localize('functionBreakpointAriaLabel', "Function breakpoint {0}, breakpoints, debug", (<FunctionBreakpoint>element).name);
1249
		}
I
isidor 已提交
1250 1251
		if (element instanceof ExceptionBreakpoint) {
			return nls.localize('exceptionBreakpointAriaLabel', "Exception breakpoint {0}, breakpoints, debug", (<ExceptionBreakpoint>element).filter);
1252 1253 1254 1255 1256 1257
		}

		return null;
	}
}

E
Erich Gamma 已提交
1258 1259
export class BreakpointsController extends BaseDebugController {

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

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

1274
	public openBreakpointSource(breakpoint: Breakpoint, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
1275
		const sideBySide = (event && (event.ctrlKey || event.metaKey));
1276 1277 1278 1279 1280 1281 1282
		const selection = breakpoint.endLineNumber ? {
			startLineNumber: breakpoint.lineNumber,
			endLineNumber: breakpoint.endLineNumber,
			startColumn: breakpoint.column,
			endColumn: breakpoint.endColumn
		} : {
				startLineNumber: breakpoint.lineNumber,
I
isidor 已提交
1283 1284 1285
				startColumn: breakpoint.column || 1,
				endLineNumber: breakpoint.lineNumber,
				endColumn: breakpoint.column || Constants.MAX_SAFE_SMALL_INTEGER
1286 1287
			};

1288 1289 1290 1291
		this.editorService.openEditor({
			resource: breakpoint.uri,
			options: {
				preserveFocus,
1292
				selection,
I
isidor 已提交
1293
				revealIfVisible: true,
1294 1295
				revealInCenterIfOutsideViewport: true,
				pinned: !preserveFocus
1296 1297
			}
		}, sideBySide).done(undefined, errors.onUnexpectedError);
1298
	}
E
Erich Gamma 已提交
1299
}