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

I
isidor 已提交
6
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
7 8
import nls = require('vs/nls');
import lifecycle = require('vs/base/common/lifecycle');
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');
I
isidor 已提交
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;
}

I
isidor 已提交
25 26 27 28 29
export function evaluateExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, expression: Expression, context: string): TPromise<Expression> {
	if (!session) {
		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 33 34 35 36 37
	}

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

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

		return expression;
	});
}

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

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

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

	return result;
}

E
Erich Gamma 已提交
85
export class Thread implements debug.IThread {
86 87
	private promisedCallStack: TPromise<debug.IStackFrame[]>;
	private cachedCallStack: debug.IStackFrame[];
I
isidor 已提交
88
	public stoppedDetails: debug.IRawStoppedDetails;
89
	public stopped: boolean;
E
Erich Gamma 已提交
90

I
isidor 已提交
91
	constructor(public name: string, public threadId: number) {
92
		this.promisedCallStack = undefined;
I
isidor 已提交
93
		this.stoppedDetails = undefined;
94 95
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
96 97 98 99 100
	}

	public getId(): string {
		return `thread:${ this.name }:${ this.threadId }`;
	}
101 102 103 104 105 106 107 108 109 110

	public clearCallStack(): void {
		this.promisedCallStack = undefined;
		this.cachedCallStack = undefined;
	}

	public getCachedCallStack(): debug.IStackFrame[] {
		return this.cachedCallStack;
	}

I
isidor 已提交
111
	public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
112 113 114
		if (!this.stopped) {
			return TPromise.as([]);
		}
I
isidor 已提交
115

116
		if (!this.promisedCallStack) {
I
isidor 已提交
117 118 119 120 121 122 123 124 125
			this.promisedCallStack = this.getCallStackImpl(debugService, 0).then(callStack => {
				this.cachedCallStack = callStack;
				return callStack;
			});
		} else if (getAdditionalStackFrames) {
			this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(debugService, callStackFirstPart.length).then(callStackSecondPart => {
				this.cachedCallStack = callStackFirstPart.concat(callStackSecondPart);
				return this.cachedCallStack;
			}));
126 127 128 129 130
		}

		return this.promisedCallStack;
	}

I
isidor 已提交
131
	private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise<debug.IStackFrame[]> {
132
		let session = debugService.getActiveSession();
I
isidor 已提交
133
		return session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
134
			this.stoppedDetails.totalFrames = response.body.totalFrames;
135 136
			return response.body.stackFrames.map((rsf, level) => {
				if (!rsf) {
I
isidor 已提交
137
					return new StackFrame(this.threadId, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
138 139
				}

I
isidor 已提交
140
				return new StackFrame(this.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: UNKNOWN_SOURCE_LABEL }, false), rsf.name, rsf.line, rsf.column);
141
			});
142 143 144
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
145 146
		});
	}
E
Erich Gamma 已提交
147 148 149
}

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

152
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
153
		// noop
E
Erich Gamma 已提交
154 155 156
	}

	public getId(): string {
I
isidor 已提交
157
		return `outputelement:${ this.id }`;
E
Erich Gamma 已提交
158 159 160 161 162
	}
}

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
163 164 165 166 167 168
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
		public counter: number = 1
	) {
I
isidor 已提交
169
		super();
E
Erich Gamma 已提交
170 171 172 173 174 175 176 177 178 179
	}
}

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 已提交
180 181
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

		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 已提交
211
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
212
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
213
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
214 215 216 217 218 219 220 221 222
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

223
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
224

225
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
226 227 228 229 230
	// Use chunks to support variable paging #9537
	private static CHUNK_SIZE = 100;

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

I
isidor 已提交
233 234 235 236 237 238 239
	constructor(
		public reference: number,
		private id: string,
		private cacheChildren: boolean,
		public childrenCount: number,
		private chunkIndex = 0
	) {
I
isidor 已提交
240
		// noop
E
Erich Gamma 已提交
241 242 243
	}

	public getChildren(debugService: debug.IDebugService): TPromise<debug.IExpression[]> {
I
isidor 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
		if (!this.cacheChildren || !this.children) {
			const session = debugService.getActiveSession();
			// only variables with reference > 0 have children.
			if (!session || this.reference <= 0) {
				this.children = TPromise.as([]);
			} else {
				if (this.childrenCount > ExpressionContainer.CHUNK_SIZE) {
					// There are a lot of children, create fake intermediate values that represent chunks #9537
					const chunks = [];
					const numberOfChunks = this.childrenCount / ExpressionContainer.CHUNK_SIZE;
					for (let i = 0; i < numberOfChunks; i++) {
						const chunkName = `${i * ExpressionContainer.CHUNK_SIZE}..${(i + 1) * ExpressionContainer.CHUNK_SIZE - 1}`;
						chunks.push(new Variable(this, this.reference, chunkName, '', ExpressionContainer.CHUNK_SIZE, null, true, i));
					}
					this.children = TPromise.as(chunks);
				} else {
					this.children = session.variables({
						variablesReference: this.reference,
						start: this.chunkIndex * ExpressionContainer.CHUNK_SIZE,
						count: ExpressionContainer.CHUNK_SIZE
					}).then(response => {
						return arrays.distinct(response.body.variables.filter(v => !!v), v => v.name).map(
							v => new Variable(this, v.variablesReference, v.name, v.value, v.totalCount, v.type)
						);
					}, (e: Error) => [new Variable(this, 0, null, e.message, 0, null, false)]);
				}
			}
E
Erich Gamma 已提交
271 272 273 274
		}

		return this.children;
	}
275 276 277 278 279

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

280 281 282 283 284 285 286 287 288 289
	public get value(): string {
		return this._value;
	}

	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 已提交
290 291
}

292 293
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
294

295
	public available: boolean;
296
	public type: string;
E
Erich Gamma 已提交
297

298
	constructor(public name: string, cacheChildren: boolean, id = uuid.generateUuid()) {
I
isidor 已提交
299
		super(0, id, cacheChildren, 0);
300 301
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
302
	}
303
}
E
Erich Gamma 已提交
304

305 306
export class Variable extends ExpressionContainer implements debug.IExpression {

307 308 309
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;

I
isidor 已提交
310 311 312 313 314 315 316 317 318 319
	constructor(
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
		value: string,
		childrenCount: number,
		public type: string = null,
		public available = true,
		chunkIndex = 0
	) {
I
isidor 已提交
320
		super(reference, `variable:${ parent.getId() }:${ name }`, true, childrenCount, chunkIndex);
321
		this.value = massageValue(value);
E
Erich Gamma 已提交
322 323 324
	}
}

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

I
isidor 已提交
327 328 329 330 331 332 333
	constructor(
		private threadId: number,
		public name: string,
		reference: number,
		public expensive: boolean,
		childrenCount: number
	) {
I
isidor 已提交
334
		super(reference, `scope:${threadId}:${name}:${reference}`, true, childrenCount);
E
Erich Gamma 已提交
335 336 337 338 339 340 341
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
342 343 344 345 346 347 348 349
	constructor(
		public threadId: number,
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
350 351 352 353
		this.scopes = null;
	}

	public getId(): string {
354
		return `stackframe:${ this.threadId }:${ this.frameId }`;
E
Erich Gamma 已提交
355 356 357 358 359
	}

	public getScopes(debugService: debug.IDebugService): TPromise<debug.IScope[]> {
		if (!this.scopes) {
			this.scopes = debugService.getActiveSession().scopes({ frameId: this.frameId }).then(response => {
I
isidor 已提交
360
				return response.body.scopes.map(rs => new Scope(this.threadId, rs.name, rs.variablesReference, rs.expensive, rs.totalCount));
361
			}, err => []);
E
Erich Gamma 已提交
362 363 364 365 366 367 368 369 370
		}

		return this.scopes;
	}
}

export class Breakpoint implements debug.IBreakpoint {

	public lineNumber: number;
371
	public verified: boolean;
372
	public idFromAdapter: number;
I
isidor 已提交
373
	public message: string;
E
Erich Gamma 已提交
374 375
	private id: string;

I
isidor 已提交
376 377 378 379 380 381
	constructor(
		public source: Source,
		public desiredLineNumber: number,
		public enabled: boolean,
		public condition: string
	) {
382 383 384
		if (enabled === undefined) {
			this.enabled = true;
		}
E
Erich Gamma 已提交
385
		this.lineNumber = this.desiredLineNumber;
386
		this.verified = false;
E
Erich Gamma 已提交
387 388 389 390 391 392 393 394
		this.id = uuid.generateUuid();
	}

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

I
isidor 已提交
395 396 397
export class FunctionBreakpoint implements debug.IFunctionBreakpoint {

	private id: string;
I
isidor 已提交
398
	public verified: boolean;
399
	public idFromAdapter: number;
I
isidor 已提交
400

401
	constructor(public name: string, public enabled: boolean) {
I
isidor 已提交
402
		this.verified = false;
I
isidor 已提交
403 404 405 406 407 408 409 410
		this.id = uuid.generateUuid();
	}

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

E
Erich Gamma 已提交
411 412 413 414
export class ExceptionBreakpoint implements debug.IExceptionBreakpoint {

	private id: string;

415
	constructor(public filter: string, public label: string, public enabled: boolean) {
E
Erich Gamma 已提交
416 417 418 419 420 421 422 423
		this.id = uuid.generateUuid();
	}

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

424
export class Model implements debug.IModel {
E
Erich Gamma 已提交
425 426 427 428

	private threads: { [reference: number]: debug.IThread; };
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
429 430 431 432
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
433

I
isidor 已提交
434 435 436 437 438 439 440
	constructor(
		private breakpoints: debug.IBreakpoint[],
		private breakpointsActivated: boolean,
		private functionBreakpoints: debug.IFunctionBreakpoint[],
		private exceptionBreakpoints: debug.IExceptionBreakpoint[],
		private watchExpressions: Expression[]
	) {
E
Erich Gamma 已提交
441 442 443
		this.threads = {};
		this.replElements = [];
		this.toDispose = [];
444 445 446 447
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
448 449 450 451 452 453
	}

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

454 455 456 457 458 459 460 461 462 463 464 465
	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;
	}

466
	public get onDidChangeReplElements(): Event<void> {
467 468 469
		return this._onDidChangeREPLElements.event;
	}

E
Erich Gamma 已提交
470 471 472 473 474 475
	public getThreads(): { [reference: number]: debug.IThread; } {
		return this.threads;
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
476 477 478 479
			if (this.threads[reference]) {
				this.threads[reference].clearCallStack();
				this.threads[reference].stoppedDetails = undefined;
				this.threads[reference].stopped = false;
I
isidor 已提交
480

481 482 483
				if (removeThreads) {
					delete this.threads[reference];
				}
E
Erich Gamma 已提交
484 485
			}
		} else {
I
isidor 已提交
486 487 488
			Object.keys(this.threads).forEach(ref => {
				this.threads[ref].clearCallStack();
				this.threads[ref].stoppedDetails = undefined;
489
				this.threads[ref].stopped = false;
I
isidor 已提交
490 491
			});

E
Erich Gamma 已提交
492 493
			if (removeThreads) {
				this.threads = {};
494
				ExpressionContainer.allValues = {};
E
Erich Gamma 已提交
495 496 497
			}
		}

498
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
499 500 501 502 503 504
	}

	public getBreakpoints(): debug.IBreakpoint[] {
		return this.breakpoints;
	}

I
isidor 已提交
505 506 507 508
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
509 510 511 512
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

513
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
514
		if (data) {
515 516 517 518
			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);
			});
519 520 521
		}
	}

E
Erich Gamma 已提交
522 523 524 525
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

526 527
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
528
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
529 530
	}

531
	public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
532 533
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
			new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition)));
534
		this.breakpointsActivated = true;
535
		this._onDidChangeBreakpoints.fire();
536
	}
E
Erich Gamma 已提交
537

538 539
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
540
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
541 542
	}

I
isidor 已提交
543
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
544 545 546
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
547
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
548
				bp.verified = bpData.verified;
549
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
550
				bp.message = bpData.message;
551 552
			}
		});
553
		this._onDidChangeBreakpoints.fire();
554 555
	}

556 557
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
558 559 560
		if (element instanceof Breakpoint && !element.enabled) {
			var breakpoint = <Breakpoint> element;
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
561
			breakpoint.verified = false;
E
Erich Gamma 已提交
562 563
		}

564
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
565 566
	}

567
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
568
		this.breakpoints.forEach(bp => {
569 570
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
571
				bp.lineNumber = bp.desiredLineNumber;
572
				bp.verified = false;
E
Erich Gamma 已提交
573 574
			}
		});
575 576
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
577

578
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
579 580
	}

I
isidor 已提交
581 582
	public addFunctionBreakpoint(functionName: string): void {
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true));
583
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
584 585
	}

586
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number } }): void {
I
isidor 已提交
587 588 589 590 591
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
592
				fbp.idFromAdapter = fbpData.id;
I
isidor 已提交
593 594 595
			}
		});

596
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
597 598
	}

599
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
600
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
601
		this._onDidChangeBreakpoints.fire();
602 603
	}

E
Erich Gamma 已提交
604 605 606 607
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

I
isidor 已提交
608
	public addReplExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
609
		const expression = new Expression(name, true);
610
		this.addReplElements([expression]);
611 612
		return evaluateExpression(session, stackFrame, expression, 'repl')
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
613 614
	}

615
	public logToRepl(value: string | { [key: string]: any }, severity?: severity): void {
E
Erich Gamma 已提交
616 617 618
		let elements:OutputElement[] = [];
		let previousOutput = this.replElements.length && (<ValueOutputElement>this.replElements[this.replElements.length - 1]);

I
isidor 已提交
619
		// string message
E
Erich Gamma 已提交
620 621 622 623
		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 已提交
624
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
625
				lines.forEach((line, index) => {
I
isidor 已提交
626
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
627 628 629 630
				});
			}
		}

I
isidor 已提交
631
		// key-value output
E
Erich Gamma 已提交
632
		else {
633
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
634 635 636
		}

		if (elements.length) {
637
			this.addReplElements(elements);
E
Erich Gamma 已提交
638
		}
I
isidor 已提交
639
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
640 641 642
	}

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

		if (groupTogether) {
649 650 651 652 653
			// 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 已提交
654 655 656 657
		}

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

661
		this.addReplElements(elements);
662
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
663 664
	}

665 666 667 668 669 670 671
	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);
		}
	}

672
	public removeReplExpressions(): void {
673 674
		if (this.replElements.length > 0) {
			this.replElements = [];
675
			this._onDidChangeREPLElements.fire();
676
		}
E
Erich Gamma 已提交
677 678 679 680 681 682
	}

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

I
isidor 已提交
683
	public addWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
684
		const we = new Expression(name, false);
E
Erich Gamma 已提交
685 686
		this.watchExpressions.push(we);
		if (!name) {
687
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
688
			return TPromise.as(null);
E
Erich Gamma 已提交
689 690 691 692 693
		}

		return this.evaluateWatchExpressions(session, stackFrame, we.getId());
	}

I
isidor 已提交
694
	public renameWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
695
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
696 697
		if (filtered.length === 1) {
			filtered[0].name = newName;
I
isidor 已提交
698
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
699
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
700 701 702
			});
		}

A
Alex Dima 已提交
703
		return TPromise.as(null);
E
Erich Gamma 已提交
704 705
	}

I
isidor 已提交
706
	public evaluateWatchExpressions(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
707
		if (id) {
I
isidor 已提交
708
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
709
			if (filtered.length !== 1) {
A
Alex Dima 已提交
710
				return TPromise.as(null);
E
Erich Gamma 已提交
711 712
			}

I
isidor 已提交
713
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
714
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
715 716 717
			});
		}

I
isidor 已提交
718
		return TPromise.join(this.watchExpressions.map(we => evaluateExpression(session, stackFrame, we, 'watch'))).then(() => {
719
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
720 721 722 723 724 725 726 727 728 729
		});
	}

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

730
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
731 732
	}

733
	public removeWatchExpressions(id: string = null): void {
734
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
735
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
736 737
	}

I
isidor 已提交
738
	public sourceIsUnavailable(source: Source): void {
739
		Object.keys(this.threads).forEach(key => {
740 741 742 743 744 745 746
			if (this.threads[key].getCachedCallStack()) {
				this.threads[key].getCachedCallStack().forEach(stackFrame => {
					if (stackFrame.source.uri.toString() === source.uri.toString()) {
						stackFrame.source.available = false;
					}
				});
			}
747 748
		});

749
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
750 751 752
	}

	public rawUpdate(data: debug.IRawModelUpdate): void {
I
isidor 已提交
753 754
		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
755
			this.threads[data.threadId] = new Thread(data.thread.name, data.thread.id);
E
Erich Gamma 已提交
756 757
		}

758 759 760
		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
I
isidor 已提交
761 762 763 764 765
			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
I
isidor 已提交
766
					this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
I
isidor 已提交
767
					this.threads[ref].stopped = true;
768
					this.threads[ref].clearCallStack();
I
isidor 已提交
769 770 771 772 773 774
				});
			} 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;
775
			}
E
Erich Gamma 已提交
776 777
		}

778
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
779 780 781 782 783 784
	}

	public dispose(): void {
		this.threads = null;
		this.breakpoints = null;
		this.exceptionBreakpoints = null;
I
isidor 已提交
785
		this.functionBreakpoints = null;
E
Erich Gamma 已提交
786 787
		this.watchExpressions = null;
		this.replElements = null;
J
Joao Moreno 已提交
788
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
789 790
	}
}