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

I
isidor 已提交
6
import * as nls from 'vs/nls';
7 8 9
import uri from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
I
isidor 已提交
10
import * as lifecycle from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
11
import Event, { Emitter } from 'vs/base/common/event';
I
isidor 已提交
12 13
import { generateUuid } from 'vs/base/common/uuid';
import { clone } from 'vs/base/common/objects';
E
Erich Gamma 已提交
14
import severity from 'vs/base/common/severity';
I
isidor 已提交
15 16
import { isObject, isString } from 'vs/base/common/types';
import { distinct } from 'vs/base/common/arrays';
I
isidor 已提交
17 18
import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
I
isidor 已提交
19
import * as debug from 'vs/workbench/parts/debug/common/debug';
J
Johannes Rieken 已提交
20
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
21

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

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

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

32
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
33
		// noop
E
Erich Gamma 已提交
34 35 36
	}

	public getId(): string {
J
Johannes Rieken 已提交
37
		return `outputelement:${this.id}`;
E
Erich Gamma 已提交
38 39 40 41 42
	}
}

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
43 44 45 46 47 48
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
		public counter: number = 1
	) {
I
isidor 已提交
49
		super();
E
Erich Gamma 已提交
50 51 52 53 54 55 56 57 58 59
	}
}

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 已提交
60 61
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
62 63 64 65 66 67 68 69 70 71

		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}]`;
I
isidor 已提交
72
			} else if (isObject(this.valueObj)) {
E
Erich Gamma 已提交
73
				this._valueName = 'Object';
I
isidor 已提交
74
			} else if (isString(this.valueObj)) {
E
Erich Gamma 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
				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 已提交
91
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
I
isidor 已提交
92
			} else if (isObject(this.valueObj)) {
I
isidor 已提交
93
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
94 95 96 97 98 99 100 101 102
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

103
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
104

105
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
106
	// Use chunks to support variable paging #9537
107
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
108 109 110

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

I
isidor 已提交
113
	constructor(
114
		public stackFrame: debug.IStackFrame,
I
isidor 已提交
115 116 117
		public reference: number,
		private id: string,
		private cacheChildren: boolean,
118 119
		public namedVariables: number,
		public indexedVariables: number,
120
		private startOfVariables = 0
I
isidor 已提交
121
	) {
I
isidor 已提交
122
		// noop
E
Erich Gamma 已提交
123 124
	}

125
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
126 127
		if (!this.cacheChildren || !this.children) {
			// only variables with reference > 0 have children.
128
			if (this.reference <= 0) {
I
isidor 已提交
129 130
				this.children = TPromise.as([]);
			} else {
131 132 133 134
				if (!this.getChildrenInChunks) {
					return this.fetchVariables(undefined, undefined, undefined);
				}

135
				// Check if object has named variables, fetch them independent from indexed variables #9670
136
				this.children = (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named')
137 138 139 140 141
					: 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;
142 143
						}

144 145 146 147 148 149
						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 已提交
150
								childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start));
151
							}
152

153 154 155
							return childrenArray;
						}

156
						return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed')
I
isidor 已提交
157
							.then(variables => childrenArray.concat(variables));
158
					});
I
isidor 已提交
159
			}
E
Erich Gamma 已提交
160 161 162 163
		}

		return this.children;
	}
164 165 166 167 168

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

169 170 171 172
	public get value(): string {
		return this._value;
	}

173 174 175 176
	public get hasChildren(): boolean {
		return this.reference > 0;
	}

177
	private fetchVariables(start: number, count: number, filter: 'indexed' | 'named'): TPromise<Variable[]> {
I
isidor 已提交
178
		return this.stackFrame.thread.process.session.variables({
179 180 181 182 183
			variablesReference: this.reference,
			start,
			count,
			filter
		}).then(response => {
I
isidor 已提交
184
			return response && response.body && response.body.variables ? distinct(response.body.variables.filter(v => !!v), v => v.name).map(
I
isidor 已提交
185
				v => new Variable(this.stackFrame, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.type)
186
			) : [];
I
isidor 已提交
187
		}, (e: Error) => [new Variable(this.stackFrame, this, 0, null, e.message, '', 0, 0, null, false)]);
188 189
	}

190 191
	// 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 {
192
		return !!this.indexedVariables;
193 194
	}

195 196 197 198 199 200
	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 已提交
201 202
}

203 204
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
205

206
	public available: boolean;
207
	public type: string;
E
Erich Gamma 已提交
208

I
isidor 已提交
209
	constructor(public name: string, cacheChildren: boolean, id = generateUuid()) {
210
		super(null, 0, id, cacheChildren, 0, 0);
211 212
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
213
	}
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

	public evaluate(process: debug.IProcess, stackFrame: debug.IStackFrame, context: string): TPromise<void> {
		if (!process) {
			this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate") : Expression.DEFAULT_VALUE;
			this.available = false;
			this.reference = 0;

			return TPromise.as(null);
		}

		// Create a fake stack frame which is just used as a container for the process.
		// TODO@Isidor revisit if variables should have a reference to the StackFrame or a process after all
		this.stackFrame = stackFrame || new StackFrame(new Thread(process, undefined, undefined), undefined, undefined, undefined, undefined, undefined);

		return process.session.evaluate({
			expression: this.name,
			frameId: stackFrame ? stackFrame.frameId : undefined,
			context
		}).then(response => {
			this.available = !!(response && response.body);
			if (response && response.body) {
				this.value = response.body.result;
				this.reference = response.body.variablesReference;
				this.namedVariables = response.body.namedVariables;
				this.indexedVariables = response.body.indexedVariables;
				this.type = response.body.type;
			}
		}, err => {
			this.value = err.message;
			this.available = false;
			this.reference = 0;
		});
	}
247
}
E
Erich Gamma 已提交
248

249 250
export class Variable extends ExpressionContainer implements debug.IExpression {

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

I
isidor 已提交
256
	constructor(
257
		stackFrame: debug.IStackFrame,
I
isidor 已提交
258 259 260
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
I
isidor 已提交
261
		private _evaluateName: string,
I
isidor 已提交
262
		value: string,
263 264
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
265 266
		public type: string = null,
		public available = true,
267
		startOfVariables = 0
I
isidor 已提交
268
	) {
269
		super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables);
270
		this.value = massageValue(value);
E
Erich Gamma 已提交
271
	}
272

I
isidor 已提交
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
	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;
	}

301 302 303 304
	public setVariable(value: string): TPromise<any> {
		return this.stackFrame.thread.process.session.setVariable({
			name: this.name,
			value,
305
			variablesReference: (<ExpressionContainer>this.parent).reference
306 307 308
		}).then(response => {
			if (response && response.body) {
				this.value = response.body.value;
309
				this.type = response.body.type || this.type;
310 311 312 313 314 315
			}
			// TODO@Isidor notify stackFrame that a change has happened so watch expressions get revelauted
		}, err => {
			this.errorMessage = err.message;
		});
	}
E
Erich Gamma 已提交
316 317
}

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

I
isidor 已提交
320
	constructor(
321
		stackFrame: debug.IStackFrame,
I
isidor 已提交
322 323 324
		public name: string,
		reference: number,
		public expensive: boolean,
325 326
		namedVariables: number,
		indexedVariables: number
I
isidor 已提交
327
	) {
328
		super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables);
E
Erich Gamma 已提交
329 330 331 332 333 334 335
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
336
	constructor(
337
		public thread: debug.IThread,
I
isidor 已提交
338 339 340 341 342 343
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
344 345 346 347
		this.scopes = null;
	}

	public getId(): string {
348
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
349 350
	}

351 352
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
353
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
354
				return response && response.body && response.body.scopes ?
355
					response.body.scopes.map(rs => new Scope(this, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables)) : [];
356
			}, err => []);
E
Erich Gamma 已提交
357 358 359 360
		}

		return this.scopes;
	}
I
isidor 已提交
361 362

	public restart(): TPromise<any> {
I
isidor 已提交
363
		return this.thread.process.session.restartFrame({ frameId: this.frameId });
I
isidor 已提交
364
	}
I
isidor 已提交
365

366
	public completions(text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
I
isidor 已提交
367 368 369 370 371 372 373 374 375 376
		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 => {
377
			return response && response.body && response.body.targets ? response.body.targets.map(item => (<ISuggestion>{
I
isidor 已提交
378 379
				label: item.label,
				insertText: item.text || item.label,
380 381
				type: item.type,
				overwriteBefore: item.length || overwriteBefore
I
isidor 已提交
382 383 384
			})) : [];
		}, err => []);
	}
E
Erich Gamma 已提交
385 386
}

I
isidor 已提交
387 388 389 390 391
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 已提交
392

393
	constructor(public process: debug.IProcess, public name: string, public threadId: number) {
I
isidor 已提交
394 395 396 397
		this.promisedCallStack = undefined;
		this.stoppedDetails = undefined;
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
398 399 400
	}

	public getId(): string {
401
		return `thread:${this.process.getId()}:${this.name}:${this.threadId}`;
I
isidor 已提交
402
	}
I
isidor 已提交
403

I
isidor 已提交
404 405 406
	public clearCallStack(): void {
		this.promisedCallStack = undefined;
		this.cachedCallStack = undefined;
I
isidor 已提交
407 408
	}

I
isidor 已提交
409 410
	public getCachedCallStack(): debug.IStackFrame[] {
		return this.cachedCallStack;
I
isidor 已提交
411 412
	}

I
isidor 已提交
413 414 415 416
	public getCallStack(getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
417

I
isidor 已提交
418 419 420 421 422 423 424 425 426 427 428
		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 已提交
429

I
isidor 已提交
430
		return this.promisedCallStack;
E
Erich Gamma 已提交
431 432
	}

I
isidor 已提交
433
	private getCallStackImpl(startFrame: number): TPromise<debug.IStackFrame[]> {
I
isidor 已提交
434
		return this.process.session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
I
isidor 已提交
435 436 437 438 439 440 441
			if (!response || !response.body) {
				return [];
			}

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

445
				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 已提交
446 447 448 449 450
			});
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
		});
E
Erich Gamma 已提交
451
	}
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475

	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 已提交
476 477
}

478
export class Process implements debug.IProcess {
479

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

I
isidor 已提交
482
	constructor(public name: string, private _session: debug.ISession & debug.ITreeElement) {
483 484 485
		this.threads = {};
	}

I
isidor 已提交
486 487 488 489
	public get session(): debug.ISession {
		return this._session;
	}

I
isidor 已提交
490 491 492 493 494 495 496 497
	public getThread(threadId: number): debug.IThread {
		return this.threads[threadId];
	}

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

498
	public getId(): string {
499
		return this._session.getId();
500 501 502 503 504 505
	}

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

		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
506
			this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id);
507 508 509 510 511 512 513 514 515 516
		}

		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
I
isidor 已提交
517
					this.threads[ref].stoppedDetails = clone(data.stoppedDetails);
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 557 558 559 560 561 562 563 564 565
					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 已提交
566
}
567

I
isidor 已提交
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
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;
I
isidor 已提交
588
		this.id = generateUuid();
I
isidor 已提交
589 590 591 592 593
	}

	public getId(): string {
		return this.id;
	}
594 595 596 597

	public get uri(): uri {
		return this.source.uri;
	}
I
isidor 已提交
598 599 600 601 602 603 604 605 606 607
}

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;
I
isidor 已提交
608
		this.id = generateUuid();
I
isidor 已提交
609 610 611 612 613 614 615 616 617 618 619 620
	}

	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) {
I
isidor 已提交
621
		this.id = generateUuid();
I
isidor 已提交
622 623 624 625 626
	}

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

629
export class Model implements debug.IModel {
E
Erich Gamma 已提交
630

631
	private processes: Process[];
E
Erich Gamma 已提交
632 633
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
634 635 636 637
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
638

I
isidor 已提交
639
	constructor(
640
		private breakpoints: Breakpoint[],
I
isidor 已提交
641
		private breakpointsActivated: boolean,
642 643
		private functionBreakpoints: FunctionBreakpoint[],
		private exceptionBreakpoints: ExceptionBreakpoint[],
I
isidor 已提交
644 645
		private watchExpressions: Expression[]
	) {
646
		this.processes = [];
E
Erich Gamma 已提交
647 648
		this.replElements = [];
		this.toDispose = [];
649 650 651 652
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
653 654 655 656 657 658
	}

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

659 660
	public getProcesses(): Process[] {
		return this.processes;
661 662
	}

I
isidor 已提交
663 664
	public addProcess(name: string, session: debug.ISession & debug.ITreeElement): void {
		this.processes.push(new Process(name, session));
665 666
	}

667
	public removeProcess(id: string): void {
668
		this.processes = this.processes.filter(p => p.getId() !== id);
669 670 671
		this._onDidChangeCallStack.fire();
	}

672 673 674 675 676 677 678 679 680 681 682 683
	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;
	}

684
	public get onDidChangeReplElements(): Event<void> {
685 686 687
		return this._onDidChangeREPLElements.event;
	}

688
	public rawUpdate(data: debug.IRawModelUpdate): void {
689 690 691 692
		let process = this.processes.filter(p => p.getId() === data.sessionId).pop();
		if (process) {
			process.rawUpdate(data);
			this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
693 694 695
		}
	}

696 697
	public clearThreads(id: string, removeThreads: boolean, reference: number = undefined): void {
		const process = this.processes.filter(p => p.getId() === id).pop();
698 699
		if (process) {
			process.clearThreads(removeThreads, reference);
700 701 702 703
			this._onDidChangeCallStack.fire();
		}
	}

704
	public getBreakpoints(): Breakpoint[] {
E
Erich Gamma 已提交
705 706 707
		return this.breakpoints;
	}

I
isidor 已提交
708 709 710 711
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
712 713 714 715
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

716
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
717
		if (data) {
718 719 720 721
			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);
			});
722 723 724
		}
	}

E
Erich Gamma 已提交
725 726 727 728
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

729 730
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
731
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
732 733
	}

734 735 736 737 738 739 740 741 742 743 744
	public addBreakpoints(uri: uri, rawData: debug.IRawBreakpoint[]): void {
		// Traverse the stack frames and check try to find the raw source matching the added breakpoint uri.
		// Raw source might contain data needed for the debug adapter to match the breakpoint.
		let source: Source = null;
		this.processes.forEach(p => p.getAllThreads().forEach(t => t.getCachedCallStack().forEach(sf => {
			if (sf.source.uri.toString() === uri.toString()) {
				source = sf.source;
			}
		})));
		source = source || new Source({ path: paths.normalize(uri.fsPath, true), name: paths.basename(uri.fsPath) });

745
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
746
			new Breakpoint(source, rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
747
		this.breakpointsActivated = true;
748
		this._onDidChangeBreakpoints.fire();
749
	}
E
Erich Gamma 已提交
750

751 752
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
753
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
754 755
	}

I
isidor 已提交
756
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
757 758 759
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
760
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
761
				bp.verified = bpData.verified;
762
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
763
				bp.message = bpData.message;
764
				bp.source = bpData.source ? new Source(bpData.source) : bp.source;
765 766
			}
		});
767
		this._onDidChangeBreakpoints.fire();
768 769
	}

770 771
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
772
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
773
			var breakpoint = <Breakpoint>element;
E
Erich Gamma 已提交
774
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
775
			breakpoint.verified = false;
E
Erich Gamma 已提交
776 777
		}

778
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
779 780
	}

781
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
782
		this.breakpoints.forEach(bp => {
783 784
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
785
				bp.lineNumber = bp.desiredLineNumber;
786
				bp.verified = false;
E
Erich Gamma 已提交
787 788
			}
		});
789 790
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
791

792
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
793 794
	}

I
isidor 已提交
795
	public addFunctionBreakpoint(functionName: string): void {
796
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
797
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
798 799
	}

800
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
801 802 803 804 805
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
806
				fbp.idFromAdapter = fbpData.id;
807
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
808 809 810
			}
		});

811
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
812 813
	}

814
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
815
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
816
		this._onDidChangeBreakpoints.fire();
817 818
	}

E
Erich Gamma 已提交
819 820 821 822
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

823
	public addReplExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
824
		const expression = new Expression(name, true);
825
		this.addReplElements([expression]);
826
		return expression.evaluate(process, stackFrame, 'repl')
827
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
828 829
	}

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

I
isidor 已提交
834
		// string message
E
Erich Gamma 已提交
835 836 837 838
		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 已提交
839
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
840
				lines.forEach((line, index) => {
I
isidor 已提交
841
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
842 843 844 845
				});
			}
		}

I
isidor 已提交
846
		// key-value output
E
Erich Gamma 已提交
847
		else {
848
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
849 850 851
		}

		if (elements.length) {
852
			this.addReplElements(elements);
E
Erich Gamma 已提交
853
		}
I
isidor 已提交
854
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
855 856 857
	}

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

		if (groupTogether) {
864 865 866 867 868
			// 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 已提交
869 870 871 872
		}

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

876
		this.addReplElements(elements);
877
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
878 879
	}

880 881 882 883 884 885 886
	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);
		}
	}

887
	public removeReplExpressions(): void {
888 889
		if (this.replElements.length > 0) {
			this.replElements = [];
890
			this._onDidChangeREPLElements.fire();
891
		}
E
Erich Gamma 已提交
892 893 894 895 896 897
	}

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

898
	public addWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
899
		const we = new Expression(name, false);
E
Erich Gamma 已提交
900 901
		this.watchExpressions.push(we);
		if (!name) {
902
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
903
			return TPromise.as(null);
E
Erich Gamma 已提交
904 905
		}

906
		return this.evaluateWatchExpressions(process, stackFrame, we.getId());
E
Erich Gamma 已提交
907 908
	}

909
	public renameWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
910
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
911 912
		if (filtered.length === 1) {
			filtered[0].name = newName;
913
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
914
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
915 916 917
			});
		}

A
Alex Dima 已提交
918
		return TPromise.as(null);
E
Erich Gamma 已提交
919 920
	}

921
	public evaluateWatchExpressions(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
922
		if (id) {
I
isidor 已提交
923
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
924
			if (filtered.length !== 1) {
A
Alex Dima 已提交
925
				return TPromise.as(null);
E
Erich Gamma 已提交
926 927
			}

928
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
929
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
930 931 932
			});
		}

933
		return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => {
934
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
935 936 937
		});
	}

938
	public removeWatchExpressions(id: string = null): void {
939
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
940
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
941 942
	}

I
isidor 已提交
943
	public sourceIsUnavailable(source: Source): void {
944
		this.processes.forEach(p => p.sourceIsUnavailable(source));
945
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
946 947 948
	}

	public dispose(): void {
J
Joao Moreno 已提交
949
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
950 951
	}
}