debugModel.ts 28.8 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
import severity from 'vs/base/common/severity';
import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays');
I
isidor 已提交
15 16
import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
E
Erich Gamma 已提交
17
import debug = require('vs/workbench/parts/debug/common/debug');
J
Johannes Rieken 已提交
18
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
19

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

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

27 28
export function evaluateExpression(stackFrame: debug.IStackFrame, expression: Expression, context: string): TPromise<Expression> {
	if (!stackFrame || !stackFrame.thread.process) {
I
isidor 已提交
29 30 31
		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 已提交
32
		return TPromise.as(expression);
I
isidor 已提交
33
	}
34
	expression.stackFrame = stackFrame;
I
isidor 已提交
35

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

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

		return expression;
	});
}

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

63
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
64
		// noop
E
Erich Gamma 已提交
65 66 67
	}

	public getId(): string {
J
Johannes Rieken 已提交
68
		return `outputelement:${this.id}`;
E
Erich Gamma 已提交
69 70 71 72 73
	}
}

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
74 75 76 77 78 79
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
		public counter: number = 1
	) {
I
isidor 已提交
80
		super();
E
Erich Gamma 已提交
81 82 83 84 85 86 87 88 89 90
	}
}

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 已提交
91 92
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

		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 已提交
122
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
123
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
124
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
125 126 127 128 129 130 131 132 133
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

134
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
135

136
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
137
	// Use chunks to support variable paging #9537
138
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
139 140 141

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

I
isidor 已提交
144
	constructor(
145
		public stackFrame: debug.IStackFrame,
I
isidor 已提交
146 147 148
		public reference: number,
		private id: string,
		private cacheChildren: boolean,
149 150
		public namedVariables: number,
		public indexedVariables: number,
151
		private startOfVariables = 0
I
isidor 已提交
152
	) {
I
isidor 已提交
153
		// noop
E
Erich Gamma 已提交
154 155
	}

156
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
157 158
		if (!this.cacheChildren || !this.children) {
			// only variables with reference > 0 have children.
159
			if (this.reference <= 0) {
I
isidor 已提交
160 161
				this.children = TPromise.as([]);
			} else {
162
				// Check if object has named variables, fetch them independent from indexed variables #9670
163
				this.children = (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named')
164 165 166 167 168
					: 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;
169 170
						}

171 172 173 174 175 176
						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);
I
isidor 已提交
177
								childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start));
178
							}
179

180 181 182 183 184
							return childrenArray;
						}

						const start = this.getChildrenInChunks ? this.startOfVariables : undefined;
						const count = this.getChildrenInChunks ? this.indexedVariables : undefined;
185
						return this.fetchVariables(start, count, 'indexed')
I
isidor 已提交
186
							.then(variables => childrenArray.concat(variables));
187
					});
I
isidor 已提交
188
			}
E
Erich Gamma 已提交
189 190 191 192
		}

		return this.children;
	}
193 194 195 196 197

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

198 199 200 201
	public get value(): string {
		return this._value;
	}

202
	private fetchVariables(start: number, count: number, filter: 'indexed' | 'named'): TPromise<Variable[]> {
I
isidor 已提交
203
		return this.stackFrame.thread.process.session.variables({
204 205 206 207 208
			variablesReference: this.reference,
			start,
			count,
			filter
		}).then(response => {
209
			return response && response.body && response.body.variables ? arrays.distinct(response.body.variables.filter(v => !!v), v => v.name).map(
I
isidor 已提交
210
				v => new Variable(this.stackFrame, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.type)
211
			) : [];
I
isidor 已提交
212
		}, (e: Error) => [new Variable(this.stackFrame, this, 0, null, e.message, '', 0, 0, null, false)]);
213 214
	}

215 216
	// 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 {
217
		return !!this.indexedVariables;
218 219
	}

220 221 222 223 224 225
	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 已提交
226 227
}

228 229
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
230

231
	public available: boolean;
232
	public type: string;
E
Erich Gamma 已提交
233

234
	constructor(public name: string, cacheChildren: boolean, id = uuid.generateUuid()) {
235
		super(null, 0, id, cacheChildren, 0, 0);
236 237
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
238
	}
239
}
E
Erich Gamma 已提交
240

241 242
export class Variable extends ExpressionContainer implements debug.IExpression {

243 244
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;
I
isidor 已提交
245 246
	private static NOT_PROPERTY_SYNTAX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
	private static ARRAY_ELEMENT_SYNTAX = /\[.*\]$/;
247

I
isidor 已提交
248
	constructor(
249
		stackFrame: debug.IStackFrame,
I
isidor 已提交
250 251 252
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
I
isidor 已提交
253
		private _evaluateName: string,
I
isidor 已提交
254
		value: string,
255 256
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
257 258
		public type: string = null,
		public available = true,
259
		startOfVariables = 0
I
isidor 已提交
260
	) {
261
		super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables);
262
		this.value = massageValue(value);
E
Erich Gamma 已提交
263
	}
264

I
isidor 已提交
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
	public get evaluateName(): string {
		if (this._evaluateName) {
			return this._evaluateName;
		}

		let names = [this.name];
		let v = this.parent;
		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;
			} else if (Variable.ARRAY_ELEMENT_SYNTAX.test(name) || (this.stackFrame.thread.process.session.configuration.type === 'node' && !Variable.NOT_PROPERTY_SYNTAX.test(name))) {
				// use safe way to access node properties a['property_name']. Also handles array elements.
				result = name && name.indexOf('[') === 0 ? `${result}${name}` : `${result}['${name}']`;
			} else {
				result = `${result}.${name}`;
			}
		});

		return result;
	}

293 294 295 296 297 298 299 300 301 302 303 304 305 306
	public setVariable(value: string): TPromise<any> {
		return this.stackFrame.thread.process.session.setVariable({
			name: this.name,
			value,
			variablesReference: this.parent.reference
		}).then(response => {
			if (response && response.body) {
				this.value = response.body.value;
			}
			// TODO@Isidor notify stackFrame that a change has happened so watch expressions get revelauted
		}, err => {
			this.errorMessage = err.message;
		});
	}
E
Erich Gamma 已提交
307 308
}

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

I
isidor 已提交
311
	constructor(
312
		stackFrame: debug.IStackFrame,
I
isidor 已提交
313 314 315
		public name: string,
		reference: number,
		public expensive: boolean,
316 317
		namedVariables: number,
		indexedVariables: number
I
isidor 已提交
318
	) {
319
		super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables);
E
Erich Gamma 已提交
320 321 322 323 324 325 326
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
327
	constructor(
328
		public thread: debug.IThread,
I
isidor 已提交
329 330 331 332 333 334
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
335 336 337 338
		this.scopes = null;
	}

	public getId(): string {
339
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
340 341
	}

342 343
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
344
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
345
				return response && response.body && response.body.scopes ?
346
					response.body.scopes.map(rs => new Scope(this, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables)) : [];
347
			}, err => []);
E
Erich Gamma 已提交
348 349 350 351
		}

		return this.scopes;
	}
I
isidor 已提交
352 353

	public restart(): TPromise<any> {
I
isidor 已提交
354
		return this.thread.process.session.restartFrame({ frameId: this.frameId });
I
isidor 已提交
355
	}
I
isidor 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

	public completions(text: string, position: Position): TPromise<ISuggestion[]> {
		if (!this.thread.process.session.configuration.capabilities.supportsCompletionsRequest) {
			return TPromise.as([]);
		}

		return this.thread.process.session.completions({
			frameId: this.frameId,
			text,
			column: position.column,
			line: position.lineNumber
		}).then(response => {
			return response && response.body && response.body.targets ? response.body.targets.map(item => ({
				label: item.label,
				insertText: item.text || item.label,
				type: item.type
			})) : [];
		}, err => []);
	}
E
Erich Gamma 已提交
375 376
}

I
isidor 已提交
377 378 379 380 381
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 已提交
382

383
	constructor(public process: debug.IProcess, public name: string, public threadId: number) {
I
isidor 已提交
384 385 386 387
		this.promisedCallStack = undefined;
		this.stoppedDetails = undefined;
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
388 389 390
	}

	public getId(): string {
391
		return `thread:${this.process.getId()}:${this.name}:${this.threadId}`;
I
isidor 已提交
392
	}
I
isidor 已提交
393

I
isidor 已提交
394 395 396
	public clearCallStack(): void {
		this.promisedCallStack = undefined;
		this.cachedCallStack = undefined;
I
isidor 已提交
397 398
	}

I
isidor 已提交
399 400
	public getCachedCallStack(): debug.IStackFrame[] {
		return this.cachedCallStack;
I
isidor 已提交
401 402
	}

I
isidor 已提交
403 404 405 406
	public getCallStack(getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
407

I
isidor 已提交
408 409 410 411 412 413 414 415 416 417 418
		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 已提交
419

I
isidor 已提交
420
		return this.promisedCallStack;
E
Erich Gamma 已提交
421 422
	}

I
isidor 已提交
423
	private getCallStackImpl(startFrame: number): TPromise<debug.IStackFrame[]> {
I
isidor 已提交
424
		return this.process.session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
I
isidor 已提交
425 426 427 428 429 430 431
			if (!response || !response.body) {
				return [];
			}

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

435
				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 已提交
436 437 438 439 440
			});
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
		});
E
Erich Gamma 已提交
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

	public next(): TPromise<any> {
		return this.process.session.next({ threadId: this.threadId });
	}

	public stepIn(): TPromise<any> {
		return this.process.session.stepIn({ threadId: this.threadId });
	}

	public stepOut(): TPromise<any> {
		return this.process.session.stepOut({ threadId: this.threadId });
	}

	public stepBack(): TPromise<any> {
		return this.process.session.stepBack({ threadId: this.threadId });
	}

	public continue(): TPromise<any> {
		return this.process.session.continue({ threadId: this.threadId });
	}

	public pause(): TPromise<any> {
		return this.process.session.pause({ threadId: this.threadId });
	}
E
Erich Gamma 已提交
466 467
}

468
export class Process implements debug.IProcess {
469

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

I
isidor 已提交
472
	constructor(public name: string, private _session: debug.ISession & debug.ITreeElement) {
473 474 475
		this.threads = {};
	}

I
isidor 已提交
476 477 478 479
	public get session(): debug.ISession {
		return this._session;
	}

I
isidor 已提交
480 481 482 483 484 485 486 487
	public getThread(threadId: number): debug.IThread {
		return this.threads[threadId];
	}

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

488
	public getId(): string {
I
isidor 已提交
489
		return this._session.getId();;
490 491 492 493 494 495
	}

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

		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
496
			this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id);
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 551 552 553 554 555
		}

		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 已提交
556
}
557

I
isidor 已提交
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
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;
	}
613 614
}

615
export class Model implements debug.IModel {
E
Erich Gamma 已提交
616

617
	private processes: Process[];
E
Erich Gamma 已提交
618 619
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
620 621 622 623
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
624

I
isidor 已提交
625 626 627 628 629 630 631
	constructor(
		private breakpoints: debug.IBreakpoint[],
		private breakpointsActivated: boolean,
		private functionBreakpoints: debug.IFunctionBreakpoint[],
		private exceptionBreakpoints: debug.IExceptionBreakpoint[],
		private watchExpressions: Expression[]
	) {
632
		this.processes = [];
E
Erich Gamma 已提交
633 634
		this.replElements = [];
		this.toDispose = [];
635 636 637 638
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
639 640 641 642 643 644
	}

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

645 646
	public getProcesses(): Process[] {
		return this.processes;
647 648
	}

I
isidor 已提交
649 650
	public addProcess(name: string, session: debug.ISession & debug.ITreeElement): void {
		this.processes.push(new Process(name, session));
651 652
	}

653
	public removeProcess(id: string): void {
654
		this.processes = this.processes.filter(p => p.getId() !== id);
655 656 657
		this._onDidChangeCallStack.fire();
	}

658 659 660 661 662 663 664 665 666 667 668 669
	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;
	}

670
	public get onDidChangeReplElements(): Event<void> {
671 672 673
		return this._onDidChangeREPLElements.event;
	}

674
	public rawUpdate(data: debug.IRawModelUpdate): void {
675 676 677 678
		let process = this.processes.filter(p => p.getId() === data.sessionId).pop();
		if (process) {
			process.rawUpdate(data);
			this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
679 680 681
		}
	}

682 683
	public clearThreads(id: string, removeThreads: boolean, reference: number = undefined): void {
		const process = this.processes.filter(p => p.getId() === id).pop();
684 685
		if (process) {
			process.clearThreads(removeThreads, reference);
686 687 688 689
			this._onDidChangeCallStack.fire();
		}
	}

E
Erich Gamma 已提交
690 691 692 693
	public getBreakpoints(): debug.IBreakpoint[] {
		return this.breakpoints;
	}

I
isidor 已提交
694 695 696 697
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
698 699 700 701
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

702
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
703
		if (data) {
704 705 706 707
			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);
			});
708 709 710
		}
	}

E
Erich Gamma 已提交
711 712 713 714
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

715 716
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
717
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
718 719
	}

720
	public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
721
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
722
			new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
723
		this.breakpointsActivated = true;
724
		this._onDidChangeBreakpoints.fire();
725
	}
E
Erich Gamma 已提交
726

727 728
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
729
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
730 731
	}

I
isidor 已提交
732
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
733 734 735
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
736
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
737
				bp.verified = bpData.verified;
738
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
739
				bp.message = bpData.message;
740 741
			}
		});
742
		this._onDidChangeBreakpoints.fire();
743 744
	}

745 746
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
747
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
748
			var breakpoint = <Breakpoint>element;
E
Erich Gamma 已提交
749
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
750
			breakpoint.verified = false;
E
Erich Gamma 已提交
751 752
		}

753
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
754 755
	}

756
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
757
		this.breakpoints.forEach(bp => {
758 759
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
760
				bp.lineNumber = bp.desiredLineNumber;
761
				bp.verified = false;
E
Erich Gamma 已提交
762 763
			}
		});
764 765
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
766

767
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
768 769
	}

I
isidor 已提交
770
	public addFunctionBreakpoint(functionName: string): void {
771
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
772
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
773 774
	}

775
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
776 777 778 779 780
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
781
				fbp.idFromAdapter = fbpData.id;
782
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
783 784 785
			}
		});

786
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
787 788
	}

789
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
790
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
791
		this._onDidChangeBreakpoints.fire();
792 793
	}

E
Erich Gamma 已提交
794 795 796 797
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

798
	public addReplExpression(stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
799
		const expression = new Expression(name, true);
800
		this.addReplElements([expression]);
801
		return evaluateExpression(stackFrame, expression, 'repl')
802
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
803 804
	}

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

I
isidor 已提交
809
		// string message
E
Erich Gamma 已提交
810 811 812 813
		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 已提交
814
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
815
				lines.forEach((line, index) => {
I
isidor 已提交
816
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
817 818 819 820
				});
			}
		}

I
isidor 已提交
821
		// key-value output
E
Erich Gamma 已提交
822
		else {
823
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
824 825 826
		}

		if (elements.length) {
827
			this.addReplElements(elements);
E
Erich Gamma 已提交
828
		}
I
isidor 已提交
829
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
830 831 832
	}

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

		if (groupTogether) {
839 840 841 842 843
			// 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 已提交
844 845 846 847
		}

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

851
		this.addReplElements(elements);
852
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
853 854
	}

855 856 857 858 859 860 861
	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);
		}
	}

862
	public removeReplExpressions(): void {
863 864
		if (this.replElements.length > 0) {
			this.replElements = [];
865
			this._onDidChangeREPLElements.fire();
866
		}
E
Erich Gamma 已提交
867 868 869 870 871 872
	}

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

873
	public addWatchExpression(stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
874
		const we = new Expression(name, false);
E
Erich Gamma 已提交
875 876
		this.watchExpressions.push(we);
		if (!name) {
877
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
878
			return TPromise.as(null);
E
Erich Gamma 已提交
879 880
		}

881
		return this.evaluateWatchExpressions(stackFrame, we.getId());
E
Erich Gamma 已提交
882 883
	}

884
	public renameWatchExpression(stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
885
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
886 887
		if (filtered.length === 1) {
			filtered[0].name = newName;
888
			return evaluateExpression(stackFrame, filtered[0], 'watch').then(() => {
889
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
890 891 892
			});
		}

A
Alex Dima 已提交
893
		return TPromise.as(null);
E
Erich Gamma 已提交
894 895
	}

896
	public evaluateWatchExpressions(stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
897
		if (id) {
I
isidor 已提交
898
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
899
			if (filtered.length !== 1) {
A
Alex Dima 已提交
900
				return TPromise.as(null);
E
Erich Gamma 已提交
901 902
			}

903
			return evaluateExpression(stackFrame, filtered[0], 'watch').then(() => {
904
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
905 906 907
			});
		}

908
		return TPromise.join(this.watchExpressions.map(we => evaluateExpression(stackFrame, we, 'watch'))).then(() => {
909
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
910 911 912 913 914 915 916 917 918 919
		});
	}

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

920
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
921 922
	}

923
	public removeWatchExpressions(id: string = null): void {
924
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
925
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
926 927
	}

I
isidor 已提交
928
	public sourceIsUnavailable(source: Source): void {
929
		this.processes.forEach(p => p.sourceIsUnavailable(source));
930
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
931 932 933
	}

	public dispose(): void {
J
Joao Moreno 已提交
934
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
935 936
	}
}