debugModel.ts 27.0 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.
 *--------------------------------------------------------------------------------------------*/

J
Johannes Rieken 已提交
6
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
7 8
import nls = require('vs/nls');
import lifecycle = require('vs/base/common/lifecycle');
J
Johannes Rieken 已提交
9
import Event, { Emitter } from 'vs/base/common/event';
E
Erich Gamma 已提交
10
import uuid = require('vs/base/common/uuid');
I
isidor 已提交
11
import objects = require('vs/base/common/objects');
E
Erich Gamma 已提交
12 13 14 15
import severity from 'vs/base/common/severity';
import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays');
import debug = require('vs/workbench/parts/debug/common/debug');
J
Johannes Rieken 已提交
16
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
17

18
const MAX_REPL_LENGTH = 10000;
I
isidor 已提交
19
const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
20

E
Erich Gamma 已提交
21 22 23 24
function massageValue(value: string): string {
	return value ? value.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') : value;
}

25 26
export function evaluateExpression(stackFrame: debug.IStackFrame, expression: Expression, context: string): TPromise<Expression> {
	if (!stackFrame || !stackFrame.thread.process) {
I
isidor 已提交
27 28 29
		expression.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate") : Expression.DEFAULT_VALUE;
		expression.available = false;
		expression.reference = 0;
A
Alex Dima 已提交
30
		return TPromise.as(expression);
I
isidor 已提交
31
	}
32
	expression.stackFrame = stackFrame;
I
isidor 已提交
33

I
isidor 已提交
34
	return stackFrame.thread.process.session.evaluate({
I
isidor 已提交
35 36 37 38
		expression: expression.name,
		frameId: stackFrame ? stackFrame.frameId : undefined,
		context
	}).then(response => {
39
		expression.available = !!(response && response.body);
I
isidor 已提交
40
		if (response && response.body) {
I
isidor 已提交
41 42
			expression.value = response.body.result;
			expression.reference = response.body.variablesReference;
43
			expression.namedVariables = response.body.namedVariables;
44
			expression.indexedVariables = response.body.indexedVariables;
45
			expression.type = response.body.type;
I
isidor 已提交
46
		}
I
isidor 已提交
47 48 49 50 51 52 53 54 55 56 57

		return expression;
	}, err => {
		expression.value = err.message;
		expression.available = false;
		expression.reference = 0;

		return expression;
	});
}

I
isidor 已提交
58 59
const notPropertySyntax = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const arrayElementSyntax = /\[.*\]$/;
I
isidor 已提交
60

61 62 63
export function getFullExpressionName(expression: debug.IExpression, sessionType: string): string {
	let names = [expression.name];
	if (expression instanceof Variable) {
J
Johannes Rieken 已提交
64
		let v = (<Variable>expression).parent;
65
		while (v instanceof Variable || v instanceof Expression) {
J
Johannes Rieken 已提交
66 67
			names.push((<Variable>v).name);
			v = (<Variable>v).parent;
68 69 70 71 72 73 74 75
		}
	}
	names = names.reverse();

	let result = null;
	names.forEach(name => {
		if (!result) {
			result = name;
I
isidor 已提交
76
		} else if (arrayElementSyntax.test(name) || (sessionType === 'node' && !notPropertySyntax.test(name))) {
I
isidor 已提交
77
			// use safe way to access node properties a['property_name']. Also handles array elements.
J
Johannes Rieken 已提交
78
			result = name && name.indexOf('[') === 0 ? `${result}${name}` : `${result}['${name}']`;
79
		} else {
J
Johannes Rieken 已提交
80
			result = `${result}.${name}`;
81 82 83 84 85 86
		}
	});

	return result;
}

E
Erich Gamma 已提交
87
export class OutputElement implements debug.ITreeElement {
88
	private static ID_COUNTER = 0;
E
Erich Gamma 已提交
89

90
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
91
		// noop
E
Erich Gamma 已提交
92 93 94
	}

	public getId(): string {
J
Johannes Rieken 已提交
95
		return `outputelement:${this.id}`;
E
Erich Gamma 已提交
96 97 98 99 100
	}
}

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
101 102 103 104 105 106
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
		public counter: number = 1
	) {
I
isidor 已提交
107
		super();
E
Erich Gamma 已提交
108 109 110 111 112 113 114 115 116 117
	}
}

export class KeyValueOutputElement extends OutputElement {

	private static MAX_CHILDREN = 1000; // upper bound of children per value

	private children: debug.ITreeElement[];
	private _valueName: string;

I
isidor 已提交
118 119
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148

		this._valueName = null;
	}

	public get value(): string {
		if (this._valueName === null) {
			if (this.valueObj === null) {
				this._valueName = 'null';
			} else if (Array.isArray(this.valueObj)) {
				this._valueName = `Array[${this.valueObj.length}]`;
			} else if (types.isObject(this.valueObj)) {
				this._valueName = 'Object';
			} else if (types.isString(this.valueObj)) {
				this._valueName = `"${massageValue(this.valueObj)}"`;
			} else {
				this._valueName = String(this.valueObj);
			}

			if (!this._valueName) {
				this._valueName = '';
			}
		}

		return this._valueName;
	}

	public getChildren(): debug.ITreeElement[] {
		if (!this.children) {
			if (Array.isArray(this.valueObj)) {
I
isidor 已提交
149
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
150
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
151
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
152 153 154 155 156 157 158 159 160
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

161
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
162

163
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
164
	// Use chunks to support variable paging #9537
165
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
166 167 168

	public valueChanged: boolean;
	private children: TPromise<debug.IExpression[]>;
169
	private _value: string;
E
Erich Gamma 已提交
170

I
isidor 已提交
171
	constructor(
172
		public stackFrame: debug.IStackFrame,
I
isidor 已提交
173 174 175
		public reference: number,
		private id: string,
		private cacheChildren: boolean,
176 177
		public namedVariables: number,
		public indexedVariables: number,
178
		private startOfVariables = 0
I
isidor 已提交
179
	) {
I
isidor 已提交
180
		// noop
E
Erich Gamma 已提交
181 182
	}

183
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
184 185
		if (!this.cacheChildren || !this.children) {
			// only variables with reference > 0 have children.
186
			if (this.reference <= 0) {
I
isidor 已提交
187 188
				this.children = TPromise.as([]);
			} else {
189
				// Check if object has named variables, fetch them independent from indexed variables #9670
190
				this.children = (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named')
191 192 193 194 195
					: TPromise.as([])).then(childrenArray => {
						// Use a dynamic chunk size based on the number of elements #9774
						let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;
						while (this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {
							chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;
196 197
						}

198 199 200 201 202 203
						if (this.indexedVariables > chunkSize) {
							// There are a lot of children, create fake intermediate values that represent chunks #9537
							const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);
							for (let i = 0; i < numberOfChunks; i++) {
								const start = this.startOfVariables + i * chunkSize;
								const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);
204
								childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', null, count, null, true, start));
205
							}
206

207 208 209 210 211
							return childrenArray;
						}

						const start = this.getChildrenInChunks ? this.startOfVariables : undefined;
						const count = this.getChildrenInChunks ? this.indexedVariables : undefined;
212
						return this.fetchVariables(start, count, 'indexed')
213 214
							.then(variables => arrays.distinct(childrenArray.concat(variables), child => child.name));
					});
I
isidor 已提交
215
			}
E
Erich Gamma 已提交
216 217 218 219
		}

		return this.children;
	}
220 221 222 223 224

	public getId(): string {
		return this.id;
	}

225 226 227 228
	public get value(): string {
		return this._value;
	}

229
	private fetchVariables(start: number, count: number, filter: 'indexed' | 'named'): TPromise<Variable[]> {
I
isidor 已提交
230
		return this.stackFrame.thread.process.session.variables({
231 232 233 234 235
			variablesReference: this.reference,
			start,
			count,
			filter
		}).then(response => {
236
			return response && response.body && response.body.variables ? arrays.distinct(response.body.variables.filter(v => !!v), v => v.name).map(
237
				v => new Variable(this.stackFrame, this, v.variablesReference, v.name, v.value, v.namedVariables, v.indexedVariables, v.type)
238
			) : [];
239
		}, (e: Error) => [new Variable(this.stackFrame, this, 0, null, e.message, 0, 0, null, false)]);
240 241
	}

242 243
	// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.
	private get getChildrenInChunks(): boolean {
244
		return !!this.indexedVariables;
245 246
	}

247 248 249 250 251 252
	public set value(value: string) {
		this._value = massageValue(value);
		this.valueChanged = ExpressionContainer.allValues[this.getId()] &&
			ExpressionContainer.allValues[this.getId()] !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues[this.getId()] !== value;
		ExpressionContainer.allValues[this.getId()] = value;
	}
E
Erich Gamma 已提交
253 254
}

255 256
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
257

258
	public available: boolean;
259
	public type: string;
E
Erich Gamma 已提交
260

261
	constructor(public name: string, cacheChildren: boolean, id = uuid.generateUuid()) {
262
		super(null, 0, id, cacheChildren, 0, 0);
263 264
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
265
	}
266
}
E
Erich Gamma 已提交
267

268 269
export class Variable extends ExpressionContainer implements debug.IExpression {

270 271 272
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;

I
isidor 已提交
273
	constructor(
274
		stackFrame: debug.IStackFrame,
I
isidor 已提交
275 276 277 278
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
		value: string,
279 280
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
281 282
		public type: string = null,
		public available = true,
283
		startOfVariables = 0
I
isidor 已提交
284
	) {
285
		super(stackFrame, reference, `variable:${stackFrame.getId()}:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables);
286
		this.value = massageValue(value);
E
Erich Gamma 已提交
287 288 289
	}
}

290
export class Scope extends ExpressionContainer implements debug.IScope {
E
Erich Gamma 已提交
291

I
isidor 已提交
292
	constructor(
293
		stackFrame: debug.IStackFrame,
I
isidor 已提交
294 295 296
		public name: string,
		reference: number,
		public expensive: boolean,
297 298
		namedVariables: number,
		indexedVariables: number
I
isidor 已提交
299
	) {
300
		super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables);
E
Erich Gamma 已提交
301 302 303 304 305 306 307
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
308
	constructor(
309
		public thread: debug.IThread,
I
isidor 已提交
310 311 312 313 314 315
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
316 317 318 319
		this.scopes = null;
	}

	public getId(): string {
320
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
321 322
	}

323 324
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
325
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
326
				return response && response.body && response.body.scopes ?
327
					response.body.scopes.map(rs => new Scope(this, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables)) : [];
328
			}, err => []);
E
Erich Gamma 已提交
329 330 331 332
		}

		return this.scopes;
	}
I
isidor 已提交
333 334

	public restart(): TPromise<any> {
I
isidor 已提交
335
		return this.thread.process.session.restartFrame({ frameId: this.frameId });
I
isidor 已提交
336
	}
E
Erich Gamma 已提交
337 338
}

I
isidor 已提交
339 340 341 342 343
export class Thread implements debug.IThread {
	private promisedCallStack: TPromise<debug.IStackFrame[]>;
	private cachedCallStack: debug.IStackFrame[];
	public stoppedDetails: debug.IRawStoppedDetails;
	public stopped: boolean;
E
Erich Gamma 已提交
344

345
	constructor(public process: debug.IProcess, public name: string, public threadId: number) {
I
isidor 已提交
346 347 348 349
		this.promisedCallStack = undefined;
		this.stoppedDetails = undefined;
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
350 351 352
	}

	public getId(): string {
353
		return `thread:${this.process.getId()}:${this.name}:${this.threadId}`;
I
isidor 已提交
354
	}
I
isidor 已提交
355

I
isidor 已提交
356 357 358
	public clearCallStack(): void {
		this.promisedCallStack = undefined;
		this.cachedCallStack = undefined;
I
isidor 已提交
359 360
	}

I
isidor 已提交
361 362
	public getCachedCallStack(): debug.IStackFrame[] {
		return this.cachedCallStack;
I
isidor 已提交
363 364
	}

I
isidor 已提交
365 366 367 368
	public getCallStack(getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
369

I
isidor 已提交
370 371 372 373 374 375 376 377 378 379 380
		if (!this.promisedCallStack) {
			this.promisedCallStack = this.getCallStackImpl(0).then(callStack => {
				this.cachedCallStack = callStack;
				return callStack;
			});
		} else if (getAdditionalStackFrames) {
			this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(callStackFirstPart.length).then(callStackSecondPart => {
				this.cachedCallStack = callStackFirstPart.concat(callStackSecondPart);
				return this.cachedCallStack;
			}));
		}
E
Erich Gamma 已提交
381

I
isidor 已提交
382
		return this.promisedCallStack;
E
Erich Gamma 已提交
383 384
	}

I
isidor 已提交
385
	private getCallStackImpl(startFrame: number): TPromise<debug.IStackFrame[]> {
I
isidor 已提交
386
		return this.process.session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
I
isidor 已提交
387 388 389 390 391 392 393
			if (!response || !response.body) {
				return [];
			}

			this.stoppedDetails.totalFrames = response.body.totalFrames;
			return response.body.stackFrames.map((rsf, level) => {
				if (!rsf) {
394
					return new StackFrame(this, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
I
isidor 已提交
395 396
				}

397
				return new StackFrame(this, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: UNKNOWN_SOURCE_LABEL }, false), rsf.name, rsf.line, rsf.column);
I
isidor 已提交
398 399 400 401 402
			});
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
		});
E
Erich Gamma 已提交
403 404 405
	}
}

406
export class Process implements debug.IProcess {
407

I
isidor 已提交
408
	private threads: { [reference: number]: debug.IThread; };
409

I
isidor 已提交
410
	constructor(private _session: debug.ISession & debug.ITreeElement) {
411 412 413
		this.threads = {};
	}

I
isidor 已提交
414 415 416 417
	public get session(): debug.ISession {
		return this._session;
	}

I
isidor 已提交
418 419 420 421 422 423 424 425
	public getThread(threadId: number): debug.IThread {
		return this.threads[threadId];
	}

	public getAllThreads(): debug.IThread[] {
		return Object.keys(this.threads).map(key => this.threads[key]);
	}

426
	public getId(): string {
I
isidor 已提交
427
		return this._session.getId();;
428 429 430 431 432 433
	}

	public rawUpdate(data: debug.IRawModelUpdate): void {

		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
434
			this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id);
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
		}

		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
			if (data.allThreadsStopped) {
				Object.keys(this.threads).forEach(ref => {
					// Only update the details if all the threads are stopped
					// because we don't want to overwrite the details of other
					// threads that have stopped for a different reason
					this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
					this.threads[ref].stopped = true;
					this.threads[ref].clearCallStack();
				});
			} else {
				// One thread is stopped, only update that thread.
				this.threads[data.threadId].stoppedDetails = data.stoppedDetails;
				this.threads[data.threadId].clearCallStack();
				this.threads[data.threadId].stopped = true;
			}
		}
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
			if (this.threads[reference]) {
				this.threads[reference].clearCallStack();
				this.threads[reference].stoppedDetails = undefined;
				this.threads[reference].stopped = false;

				if (removeThreads) {
					delete this.threads[reference];
				}
			}
		} else {
			Object.keys(this.threads).forEach(ref => {
				this.threads[ref].clearCallStack();
				this.threads[ref].stoppedDetails = undefined;
				this.threads[ref].stopped = false;
			});

			if (removeThreads) {
				this.threads = {};
				ExpressionContainer.allValues = {};
			}
		}
	}

	public sourceIsUnavailable(source: Source): void {
		Object.keys(this.threads).forEach(key => {
			if (this.threads[key].getCachedCallStack()) {
				this.threads[key].getCachedCallStack().forEach(stackFrame => {
					if (stackFrame.source.uri.toString() === source.uri.toString()) {
						stackFrame.source.available = false;
					}
				});
			}
		});
	}
I
isidor 已提交
494
}
495

I
isidor 已提交
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
export class Breakpoint implements debug.IBreakpoint {

	public lineNumber: number;
	public verified: boolean;
	public idFromAdapter: number;
	public message: string;
	private id: string;

	constructor(
		public source: Source,
		public desiredLineNumber: number,
		public enabled: boolean,
		public condition: string,
		public hitCondition: string
	) {
		if (enabled === undefined) {
			this.enabled = true;
		}
		this.lineNumber = this.desiredLineNumber;
		this.verified = false;
		this.id = uuid.generateUuid();
	}

	public getId(): string {
		return this.id;
	}
}

export class FunctionBreakpoint implements debug.IFunctionBreakpoint {

	private id: string;
	public verified: boolean;
	public idFromAdapter: number;

	constructor(public name: string, public enabled: boolean, public hitCondition: string) {
		this.verified = false;
		this.id = uuid.generateUuid();
	}

	public getId(): string {
		return this.id;
	}
}

export class ExceptionBreakpoint implements debug.IExceptionBreakpoint {

	private id: string;

	constructor(public filter: string, public label: string, public enabled: boolean) {
		this.id = uuid.generateUuid();
	}

	public getId(): string {
		return this.id;
	}
551 552
}

553
export class Model implements debug.IModel {
E
Erich Gamma 已提交
554

555
	private processes: Process[];
E
Erich Gamma 已提交
556 557
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
558 559 560 561
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
562

I
isidor 已提交
563 564 565 566 567 568 569
	constructor(
		private breakpoints: debug.IBreakpoint[],
		private breakpointsActivated: boolean,
		private functionBreakpoints: debug.IFunctionBreakpoint[],
		private exceptionBreakpoints: debug.IExceptionBreakpoint[],
		private watchExpressions: Expression[]
	) {
570
		this.processes = [];
E
Erich Gamma 已提交
571 572
		this.replElements = [];
		this.toDispose = [];
573 574 575 576
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
577 578 579 580 581 582
	}

	public getId(): string {
		return 'root';
	}

583 584
	public getProcesses(): Process[] {
		return this.processes;
585 586
	}

587
	public removeProcess(id: string): void {
588
		this.processes = this.processes.filter(p => p.getId() !== id);
589 590 591
		this._onDidChangeCallStack.fire();
	}

592 593 594 595 596 597 598 599 600 601 602 603
	public get onDidChangeBreakpoints(): Event<void> {
		return this._onDidChangeBreakpoints.event;
	}

	public get onDidChangeCallStack(): Event<void> {
		return this._onDidChangeCallStack.event;
	}

	public get onDidChangeWatchExpressions(): Event<debug.IExpression> {
		return this._onDidChangeWatchExpressions.event;
	}

604
	public get onDidChangeReplElements(): Event<void> {
605 606 607
		return this._onDidChangeREPLElements.event;
	}

608
	public rawUpdate(data: debug.IRawModelUpdate): void {
609 610 611 612
		let process = this.processes.filter(p => p.getId() === data.rawSession.getId()).pop();
		if (!process) {
			process = new Process(data.rawSession);
			this.processes.push(process);
E
Erich Gamma 已提交
613
		}
614
		process.rawUpdate(data);
E
Erich Gamma 已提交
615

616
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
617 618
	}

619 620
	public clearThreads(processId: string, removeThreads: boolean, reference: number = undefined): void {
		const process = this.processes.filter(p => p.getId() === processId).pop();
621 622
		if (process) {
			process.clearThreads(removeThreads, reference);
623 624 625 626
			this._onDidChangeCallStack.fire();
		}
	}

E
Erich Gamma 已提交
627 628 629 630
	public getBreakpoints(): debug.IBreakpoint[] {
		return this.breakpoints;
	}

I
isidor 已提交
631 632 633 634
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
635 636 637 638
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

639
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
640
		if (data) {
641 642 643 644
			this.exceptionBreakpoints = data.map(d => {
				const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop();
				return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : d.default);
			});
645 646 647
		}
	}

E
Erich Gamma 已提交
648 649 650 651
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

652 653
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
654
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
655 656
	}

657
	public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
658
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
659
			new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
660
		this.breakpointsActivated = true;
661
		this._onDidChangeBreakpoints.fire();
662
	}
E
Erich Gamma 已提交
663

664 665
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
666
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
667 668
	}

I
isidor 已提交
669
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
670 671 672
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
673
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
674
				bp.verified = bpData.verified;
675
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
676
				bp.message = bpData.message;
677 678
			}
		});
679
		this._onDidChangeBreakpoints.fire();
680 681
	}

682 683
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
684
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
685
			var breakpoint = <Breakpoint>element;
E
Erich Gamma 已提交
686
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
687
			breakpoint.verified = false;
E
Erich Gamma 已提交
688 689
		}

690
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
691 692
	}

693
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
694
		this.breakpoints.forEach(bp => {
695 696
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
697
				bp.lineNumber = bp.desiredLineNumber;
698
				bp.verified = false;
E
Erich Gamma 已提交
699 700
			}
		});
701 702
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
703

704
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
705 706
	}

I
isidor 已提交
707
	public addFunctionBreakpoint(functionName: string): void {
708
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
709
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
710 711
	}

712
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
713 714 715 716 717
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
718
				fbp.idFromAdapter = fbpData.id;
719
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
720 721 722
			}
		});

723
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
724 725
	}

726
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
727
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
728
		this._onDidChangeBreakpoints.fire();
729 730
	}

E
Erich Gamma 已提交
731 732 733 734
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

735
	public addReplExpression(stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
736
		const expression = new Expression(name, true);
737
		this.addReplElements([expression]);
738
		return evaluateExpression(stackFrame, expression, 'repl')
739
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
740 741
	}

742
	public logToRepl(value: string | { [key: string]: any }, severity?: severity): void {
J
Johannes Rieken 已提交
743
		let elements: OutputElement[] = [];
E
Erich Gamma 已提交
744 745
		let previousOutput = this.replElements.length && (<ValueOutputElement>this.replElements[this.replElements.length - 1]);

I
isidor 已提交
746
		// string message
E
Erich Gamma 已提交
747 748 749 750
		if (typeof value === 'string') {
			if (value && value.trim() && previousOutput && previousOutput.value === value && previousOutput.severity === severity) {
				previousOutput.counter++; // we got the same output (but not an empty string when trimmed) so we just increment the counter
			} else {
I
isidor 已提交
751
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
752
				lines.forEach((line, index) => {
I
isidor 已提交
753
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
754 755 756 757
				});
			}
		}

I
isidor 已提交
758
		// key-value output
E
Erich Gamma 已提交
759
		else {
760
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
761 762 763
		}

		if (elements.length) {
764
			this.addReplElements(elements);
E
Erich Gamma 已提交
765
		}
I
isidor 已提交
766
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
767 768 769
	}

	public appendReplOutput(value: string, severity?: severity): void {
I
isidor 已提交
770
		const elements: OutputElement[] = [];
E
Erich Gamma 已提交
771
		let previousOutput = this.replElements.length && (<ValueOutputElement>this.replElements[this.replElements.length - 1]);
772 773
		let lines = value.split('\n');
		let groupTogether = !!previousOutput && (previousOutput.category === 'output' && severity === previousOutput.severity);
E
Erich Gamma 已提交
774 775

		if (groupTogether) {
776 777 778 779 780
			// append to previous line if same group
			previousOutput.value += lines.shift();
		} else if (previousOutput && previousOutput.value === '') {
			// remove potential empty lines between different output types
			this.replElements.pop();
E
Erich Gamma 已提交
781 782 783 784
		}

		// fill in lines as output value elements
		lines.forEach((line, index) => {
I
isidor 已提交
785
			elements.push(new ValueOutputElement(line, severity, 'output'));
E
Erich Gamma 已提交
786 787
		});

788
		this.addReplElements(elements);
789
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
790 791
	}

792 793 794 795 796 797 798
	private addReplElements(newElements: debug.ITreeElement[]): void {
		this.replElements.push(...newElements);
		if (this.replElements.length > MAX_REPL_LENGTH) {
			this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
		}
	}

799
	public removeReplExpressions(): void {
800 801
		if (this.replElements.length > 0) {
			this.replElements = [];
802
			this._onDidChangeREPLElements.fire();
803
		}
E
Erich Gamma 已提交
804 805 806 807 808 809
	}

	public getWatchExpressions(): Expression[] {
		return this.watchExpressions;
	}

810
	public addWatchExpression(stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
811
		const we = new Expression(name, false);
E
Erich Gamma 已提交
812 813
		this.watchExpressions.push(we);
		if (!name) {
814
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
815
			return TPromise.as(null);
E
Erich Gamma 已提交
816 817
		}

818
		return this.evaluateWatchExpressions(stackFrame, we.getId());
E
Erich Gamma 已提交
819 820
	}

821
	public renameWatchExpression(stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
822
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
823 824
		if (filtered.length === 1) {
			filtered[0].name = newName;
825
			return evaluateExpression(stackFrame, filtered[0], 'watch').then(() => {
826
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
827 828 829
			});
		}

A
Alex Dima 已提交
830
		return TPromise.as(null);
E
Erich Gamma 已提交
831 832
	}

833
	public evaluateWatchExpressions(stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
834
		if (id) {
I
isidor 已提交
835
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
836
			if (filtered.length !== 1) {
A
Alex Dima 已提交
837
				return TPromise.as(null);
E
Erich Gamma 已提交
838 839
			}

840
			return evaluateExpression(stackFrame, filtered[0], 'watch').then(() => {
841
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
842 843 844
			});
		}

845
		return TPromise.join(this.watchExpressions.map(we => evaluateExpression(stackFrame, we, 'watch'))).then(() => {
846
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
847 848 849 850 851 852 853 854 855 856
		});
	}

	public clearWatchExpressionValues(): void {
		this.watchExpressions.forEach(we => {
			we.value = Expression.DEFAULT_VALUE;
			we.available = false;
			we.reference = 0;
		});

857
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
858 859
	}

860
	public removeWatchExpressions(id: string = null): void {
861
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
862
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
863 864
	}

I
isidor 已提交
865
	public sourceIsUnavailable(source: Source): void {
866
		this.processes.forEach(p => p.sourceIsUnavailable(source));
867
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
868 869 870
	}

	public dispose(): void {
J
Joao Moreno 已提交
871
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
872 873
	}
}