debugModel.ts 29.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
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, this.getChildrenInChunks ? 'indexed' : undefined)
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
	public completions(text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
I
isidor 已提交
358 359 360 361 362 363 364 365 366 367
		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 => {
368
			return response && response.body && response.body.targets ? response.body.targets.map(item => (<ISuggestion>{
I
isidor 已提交
369 370
				label: item.label,
				insertText: item.text || item.label,
371 372
				type: item.type,
				overwriteBefore: item.length || overwriteBefore
I
isidor 已提交
373 374 375
			})) : [];
		}, err => []);
	}
E
Erich Gamma 已提交
376 377
}

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

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

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

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

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

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

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

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

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

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

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

	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 已提交
467 468
}

469
export class Process implements debug.IProcess {
470

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

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

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

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

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

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

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

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

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

I
isidor 已提交
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 613
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;
	}
614 615
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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