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;
	});
}

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

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

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

	return result;
}

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

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

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

export class ValueOutputElement extends OutputElement {

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

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 已提交
120 121
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

		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 已提交
151
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
152
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
153
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
154 155 156 157 158 159 160 161 162
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

163
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
164

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

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

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

185
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
186 187
		if (!this.cacheChildren || !this.children) {
			// only variables with reference > 0 have children.
188
			if (this.reference <= 0) {
I
isidor 已提交
189 190
				this.children = TPromise.as([]);
			} else {
191
				// Check if object has named variables, fetch them independent from indexed variables #9670
192
				this.children = (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named')
193 194 195 196 197
					: 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;
198 199
						}

200 201 202 203 204 205
						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);
206
								childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', null, count, null, true, start));
207
							}
208

209 210 211 212 213
							return childrenArray;
						}

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

		return this.children;
	}
222 223 224 225 226

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

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

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

244 245
	// 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 {
246
		return !!this.indexedVariables;
247 248
	}

249 250 251 252 253 254
	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 已提交
255 256
}

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

260
	public available: boolean;
261
	public type: string;
E
Erich Gamma 已提交
262

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

270 271
export class Variable extends ExpressionContainer implements debug.IExpression {

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

I
isidor 已提交
275
	constructor(
276
		stackFrame: debug.IStackFrame,
I
isidor 已提交
277 278 279 280
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
		value: string,
281 282
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
283 284
		public type: string = null,
		public available = true,
285
		startOfVariables = 0
I
isidor 已提交
286
	) {
287
		super(stackFrame, reference, `variable:${stackFrame.getId()}:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables);
288
		this.value = massageValue(value);
E
Erich Gamma 已提交
289
	}
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304

	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 已提交
305 306
}

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

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

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

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

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

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

		return this.scopes;
	}
I
isidor 已提交
350 351

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

	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 已提交
373 374
}

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

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

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

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

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

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

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

I
isidor 已提交
418
		return this.promisedCallStack;
E
Erich Gamma 已提交
419 420
	}

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

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

433
				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 已提交
434 435 436 437 438
			});
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
		});
E
Erich Gamma 已提交
439
	}
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463

	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 已提交
464 465
}

466
export class Process implements debug.IProcess {
467

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

I
isidor 已提交
470
	constructor(private _session: debug.ISession & debug.ITreeElement) {
471 472 473
		this.threads = {};
	}

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

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

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

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

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

		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
494
			this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id);
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
		}

		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 已提交
554
}
555

I
isidor 已提交
556 557 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
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;
	}
611 612
}

613
export class Model implements debug.IModel {
E
Erich Gamma 已提交
614

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

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

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

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

647 648 649 650
	public addProcess(session: debug.ISession & debug.ITreeElement): void {
		this.processes.push(new Process(session));
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

751
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
752 753
	}

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

765
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
766 767
	}

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

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

784
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
785 786
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

918
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
919 920
	}

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

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

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