debugModel.ts 29.6 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
import uri from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
9
import * as lifecycle from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
10
import Event, { Emitter } from 'vs/base/common/event';
I
isidor 已提交
11 12
import { generateUuid } from 'vs/base/common/uuid';
import { clone } from 'vs/base/common/objects';
E
Erich Gamma 已提交
13
import severity from 'vs/base/common/severity';
I
isidor 已提交
14 15
import { isObject, isString } from 'vs/base/common/types';
import { distinct } from 'vs/base/common/arrays';
I
isidor 已提交
16 17
import { IRange } from 'vs/editor/common/editorCommon';
import { Range } from 'vs/editor/common/core/range';
I
isidor 已提交
18 19
import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
I
isidor 已提交
20
import * as debug from 'vs/workbench/parts/debug/common/debug';
J
Johannes Rieken 已提交
21
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
22

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

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

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

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

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
40 41 42 43
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
44
		public counter = 1
I
isidor 已提交
45
	) {
I
isidor 已提交
46
		super();
E
Erich Gamma 已提交
47 48 49 50 51 52 53
	}
}

export class KeyValueOutputElement extends OutputElement {

	private static MAX_CHILDREN = 1000; // upper bound of children per value

I
isidor 已提交
54 55
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
56 57 58
	}

	public get value(): string {
59 60 61 62 63 64 65
		if (this.valueObj === null) {
			return 'null';
		} else if (Array.isArray(this.valueObj)) {
			return `Array[${this.valueObj.length}]`;
		} else if (isObject(this.valueObj)) {
			return 'Object';
		} else if (isString(this.valueObj)) {
66
			return this.valueObj;
E
Erich Gamma 已提交
67 68
		}

69
		return String(this.valueObj) || '';
E
Erich Gamma 已提交
70 71 72
	}

	public getChildren(): debug.ITreeElement[] {
73 74 75 76 77 78
		if (Array.isArray(this.valueObj)) {
			return (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN)
				.map((v, index) => new KeyValueOutputElement(String(index), v));
		} else if (isObject(this.valueObj)) {
			return Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN)
				.map(key => new KeyValueOutputElement(key, this.valueObj[key]));
E
Erich Gamma 已提交
79 80
		}

81
		return [];
E
Erich Gamma 已提交
82 83 84
	}
}

85
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
86

87
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
88
	// Use chunks to support variable paging #9537
89
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
90 91

	public valueChanged: boolean;
92
	private _value: string;
E
Erich Gamma 已提交
93

I
isidor 已提交
94
	constructor(
95
		public stackFrame: debug.IStackFrame,
I
isidor 已提交
96 97
		public reference: number,
		private id: string,
98 99
		public namedVariables: number,
		public indexedVariables: number,
100
		private startOfVariables = 0
101
	) { }
E
Erich Gamma 已提交
102

103
	public getChildren(): TPromise<debug.IExpression[]> {
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
		// only variables with reference > 0 have children.
		if (this.reference <= 0) {
			return TPromise.as([]);
		}

		if (!this.getChildrenInChunks) {
			return this.fetchVariables(undefined, undefined, undefined);
		}

		// Check if object has named variables, fetch them independent from indexed variables #9670
		return (!!this.namedVariables ? this.fetchVariables(undefined, undefined, 'named') : 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;
			}

			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);
					childrenArray.push(new Variable(this.stackFrame, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start));
128 129
				}

130
				return childrenArray;
I
isidor 已提交
131
			}
E
Erich Gamma 已提交
132

133 134 135
			return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed')
				.then(variables => childrenArray.concat(variables));
		});
E
Erich Gamma 已提交
136
	}
137 138 139 140 141

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

142 143 144 145
	public get value(): string {
		return this._value;
	}

146 147 148 149
	public get hasChildren(): boolean {
		return this.reference > 0;
	}

150
	private fetchVariables(start: number, count: number, filter: 'indexed' | 'named'): TPromise<Variable[]> {
I
isidor 已提交
151
		return this.stackFrame.thread.process.session.variables({
152 153 154 155 156
			variablesReference: this.reference,
			start,
			count,
			filter
		}).then(response => {
I
isidor 已提交
157
			return response && response.body && response.body.variables ? distinct(response.body.variables.filter(v => !!v), v => v.name).map(
I
isidor 已提交
158
				v => new Variable(this.stackFrame, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.type)
159
			) : [];
I
isidor 已提交
160
		}, (e: Error) => [new Variable(this.stackFrame, this, 0, null, e.message, '', 0, 0, null, false)]);
161 162
	}

163 164
	// 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 {
165
		return !!this.indexedVariables;
166 167
	}

168
	public set value(value: string) {
169
		this._value = value;
170 171 172 173
		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 已提交
174 175
}

176 177
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
178

179
	public available: boolean;
180
	public type: string;
E
Erich Gamma 已提交
181

182 183
	constructor(public name: string, id = generateUuid()) {
		super(null, 0, id, 0, 0);
184
		this.available = false;
185 186 187 188 189
		// name is not set if the expression is just being added
		// in that case do not set default value to prevent flashing #14499
		if (name) {
			this.value = Expression.DEFAULT_VALUE;
		}
E
Erich Gamma 已提交
190
	}
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

	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;
		});
	}
224
}
E
Erich Gamma 已提交
225

226 227
export class Variable extends ExpressionContainer implements debug.IExpression {

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

I
isidor 已提交
233
	constructor(
234
		stackFrame: debug.IStackFrame,
I
isidor 已提交
235 236 237
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
I
isidor 已提交
238
		private _evaluateName: string,
I
isidor 已提交
239
		value: string,
240 241
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
242 243
		public type: string = null,
		public available = true,
244
		startOfVariables = 0
I
isidor 已提交
245
	) {
246
		super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, namedVariables, indexedVariables, startOfVariables);
247
		this.value = value;
E
Erich Gamma 已提交
248
	}
249

I
isidor 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	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;
	}

278 279 280 281
	public setVariable(value: string): TPromise<any> {
		return this.stackFrame.thread.process.session.setVariable({
			name: this.name,
			value,
282
			variablesReference: (<ExpressionContainer>this.parent).reference
283 284 285
		}).then(response => {
			if (response && response.body) {
				this.value = response.body.value;
286
				this.type = response.body.type || this.type;
287 288 289
				this.reference = response.body.variablesReference;
				this.namedVariables = response.body.namedVariables;
				this.indexedVariables = response.body.indexedVariables;
290 291 292 293 294 295
			}
			// TODO@Isidor notify stackFrame that a change has happened so watch expressions get revelauted
		}, err => {
			this.errorMessage = err.message;
		});
	}
E
Erich Gamma 已提交
296 297
}

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

I
isidor 已提交
300
	constructor(
301
		stackFrame: debug.IStackFrame,
I
isidor 已提交
302 303 304
		public name: string,
		reference: number,
		public expensive: boolean,
305
		namedVariables: number,
I
isidor 已提交
306 307
		indexedVariables: number,
		public range?: IRange
I
isidor 已提交
308
	) {
309
		super(stackFrame, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, namedVariables, indexedVariables);
E
Erich Gamma 已提交
310 311 312 313 314 315 316
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
317
	constructor(
318
		public thread: debug.IThread,
I
isidor 已提交
319 320 321 322 323 324
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
325 326 327 328
		this.scopes = null;
	}

	public getId(): string {
329
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
330 331
	}

332 333
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
334
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
335
				return response && response.body && response.body.scopes ?
I
isidor 已提交
336 337
					response.body.scopes.map(rs => new Scope(this, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
						rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : null)) : [];
338
			}, err => []);
E
Erich Gamma 已提交
339 340 341 342
		}

		return this.scopes;
	}
I
isidor 已提交
343 344

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

348
	public completions(text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
I
isidor 已提交
349 350 351 352 353 354 355 356 357 358
		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 => {
359
			return response && response.body && response.body.targets ? response.body.targets.map(item => (<ISuggestion>{
I
isidor 已提交
360 361
				label: item.label,
				insertText: item.text || item.label,
362 363
				type: item.type,
				overwriteBefore: item.length || overwriteBefore
I
isidor 已提交
364 365 366
			})) : [];
		}, err => []);
	}
E
Erich Gamma 已提交
367 368
}

I
isidor 已提交
369 370 371 372 373
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 已提交
374

375
	constructor(public process: debug.IProcess, public name: string, public threadId: number) {
376 377 378
		this.promisedCallStack = null;
		this.stoppedDetails = null;
		this.cachedCallStack = null;
I
isidor 已提交
379
		this.stopped = false;
E
Erich Gamma 已提交
380 381 382
	}

	public getId(): string {
383
		return `thread:${this.process.getId()}:${this.name}:${this.threadId}`;
I
isidor 已提交
384
	}
I
isidor 已提交
385

I
isidor 已提交
386
	public clearCallStack(): void {
387 388
		this.promisedCallStack = null;
		this.cachedCallStack = null;
I
isidor 已提交
389 390
	}

391
	public getCallStack(): debug.IStackFrame[] {
I
isidor 已提交
392
		return this.cachedCallStack;
I
isidor 已提交
393 394
	}

395 396 397 398 399 400 401 402
	/**
	 * Queries the debug adapter for the callstack and returns a promise with
	 * the stack frames of the callstack.
	 * If the thread is not stopped, it returns a promise to an empty array.
	 * Only gets the first 20 stack frames. Calling this method consecutive times
	 * with getAdditionalStackFrames = true gets the remainder of the call stack.
	 */
	public fetchCallStack(getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
I
isidor 已提交
403 404 405
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
406

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

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

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

I
isidor 已提交
428 429 430 431
			if (this.stoppedDetails) {
				this.stoppedDetails.totalFrames = response.body.totalFrames;
			}

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

437
				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 已提交
438 439
			});
		}, (err: Error) => {
I
isidor 已提交
440 441 442 443
			if (this.stoppedDetails) {
				this.stoppedDetails.framesErrorMessage = err.message;
			}

I
isidor 已提交
444 445
			return [];
		});
E
Erich Gamma 已提交
446
	}
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470

	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 });
	}
I
isidor 已提交
471 472 473 474

	public reverseContinue(): TPromise<any> {
		return this.process.session.reverseContinue({ threadId: this.threadId });
	}
E
Erich Gamma 已提交
475 476
}

477
export class Process implements debug.IProcess {
478

479
	private threads: { [reference: number]: Thread; };
480

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

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

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

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

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

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

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

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

I
isidor 已提交
567 568 569 570 571 572 573 574
export class Breakpoint implements debug.IBreakpoint {

	public verified: boolean;
	public idFromAdapter: number;
	public message: string;
	private id: string;

	constructor(
575
		public uri: uri,
576
		public lineNumber: number,
I
isidor 已提交
577 578 579 580 581 582 583 584
		public enabled: boolean,
		public condition: string,
		public hitCondition: string
	) {
		if (enabled === undefined) {
			this.enabled = true;
		}
		this.verified = false;
I
isidor 已提交
585
		this.id = generateUuid();
I
isidor 已提交
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
	}

	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;
I
isidor 已提交
601
		this.id = generateUuid();
I
isidor 已提交
602 603 604 605 606 607 608 609 610 611 612 613
	}

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

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

622
export class Model implements debug.IModel {
E
Erich Gamma 已提交
623

624
	private processes: Process[];
E
Erich Gamma 已提交
625 626
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
627 628 629 630
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
631

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

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

652 653
	public getProcesses(): Process[] {
		return this.processes;
654 655
	}

656 657 658 659 660
	public addProcess(name: string, session: debug.ISession & debug.ITreeElement): Process {
		const process = new Process(name, session);
		this.processes.push(process);

		return process;
661 662
	}

663
	public removeProcess(id: string): void {
664
		this.processes = this.processes.filter(p => p.getId() !== id);
665 666 667
		this._onDidChangeCallStack.fire();
	}

668 669 670 671 672 673 674 675 676 677 678 679
	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;
	}

680
	public get onDidChangeReplElements(): Event<void> {
681 682 683
		return this._onDidChangeREPLElements.event;
	}

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

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

700
	public getBreakpoints(): Breakpoint[] {
E
Erich Gamma 已提交
701 702 703
		return this.breakpoints;
	}

I
isidor 已提交
704 705 706 707
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
708 709 710 711
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

712
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
713
		if (data) {
714 715 716 717
			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);
			});
718 719 720
		}
	}

E
Erich Gamma 已提交
721 722 723 724
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

725 726
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
727
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
728 729
	}

730
	public addBreakpoints(uri: uri, rawData: debug.IRawBreakpoint[]): void {
731
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
732
			new Breakpoint(uri, rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
733
		this.breakpointsActivated = true;
734
		this._onDidChangeBreakpoints.fire();
735
	}
E
Erich Gamma 已提交
736

737 738
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
739
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
740 741
	}

I
isidor 已提交
742
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
743 744 745
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
746
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
747
				bp.verified = bpData.verified;
748
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
749
				bp.message = bpData.message;
750 751
			}
		});
752 753 754

		// Remove duplicate breakpoints. This can happen when an adapter updates a line number of a breakpoint
		this.breakpoints = distinct(this.breakpoints, bp => bp.uri.toString() + bp.lineNumber);
755
		this._onDidChangeBreakpoints.fire();
756 757
	}

758 759
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
760
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
761
			var breakpoint = <Breakpoint>element;
762
			breakpoint.verified = false;
E
Erich Gamma 已提交
763 764
		}

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

768
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
769
		this.breakpoints.forEach(bp => {
770 771
			bp.enabled = enable;
			if (!enable) {
772
				bp.verified = false;
E
Erich Gamma 已提交
773 774
			}
		});
775 776
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
777

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

I
isidor 已提交
781
	public addFunctionBreakpoint(functionName: string): void {
782
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
783
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
784 785
	}

786
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
787 788 789 790 791
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
792
				fbp.idFromAdapter = fbpData.id;
793
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
794 795 796
			}
		});

797
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
798 799
	}

800
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
801
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
802
		this._onDidChangeBreakpoints.fire();
803 804
	}

E
Erich Gamma 已提交
805 806 807 808
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

809
	public addReplExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
810
		const expression = new Expression(name);
811
		this.addReplElements([expression]);
812
		return expression.evaluate(process, stackFrame, 'repl')
813
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
814 815
	}

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

I
isidor 已提交
820
		// string message
E
Erich Gamma 已提交
821 822 823 824
		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 已提交
825
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
826
				lines.forEach((line, index) => {
I
isidor 已提交
827
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
828 829 830 831
				});
			}
		}

I
isidor 已提交
832
		// key-value output
E
Erich Gamma 已提交
833
		else {
834
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
835 836 837
		}

		if (elements.length) {
838
			this.addReplElements(elements);
E
Erich Gamma 已提交
839
		}
I
isidor 已提交
840
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
841 842 843
	}

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

		if (groupTogether) {
850 851 852 853 854
			// 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 已提交
855 856 857 858
		}

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

862
		this.addReplElements(elements);
863
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
864 865
	}

866 867 868 869 870 871 872
	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);
		}
	}

873
	public removeReplExpressions(): void {
874 875
		if (this.replElements.length > 0) {
			this.replElements = [];
876
			this._onDidChangeREPLElements.fire();
877
		}
E
Erich Gamma 已提交
878 879 880 881 882 883
	}

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

884
	public addWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
885
		const we = new Expression(name);
E
Erich Gamma 已提交
886 887
		this.watchExpressions.push(we);
		if (!name) {
888
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
889
			return TPromise.as(null);
E
Erich Gamma 已提交
890 891
		}

892
		return this.evaluateWatchExpressions(process, stackFrame, we.getId());
E
Erich Gamma 已提交
893 894
	}

895
	public renameWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
896
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
897 898
		if (filtered.length === 1) {
			filtered[0].name = newName;
899
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
900
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
901 902 903
			});
		}

A
Alex Dima 已提交
904
		return TPromise.as(null);
E
Erich Gamma 已提交
905 906
	}

907
	public evaluateWatchExpressions(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
908
		if (id) {
I
isidor 已提交
909
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
910
			if (filtered.length !== 1) {
A
Alex Dima 已提交
911
				return TPromise.as(null);
E
Erich Gamma 已提交
912 913
			}

914
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
915
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
916 917 918
			});
		}

919
		return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => {
920
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
921 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 930 931 932 933 934 935 936
	public moveWatchExpression(id: string, position: number): void {
		const we = this.watchExpressions.filter(we => we.getId() === id).pop();
		this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id);
		this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position));

		this._onDidChangeWatchExpressions.fire();
	}

I
isidor 已提交
937
	public sourceIsUnavailable(source: Source): void {
938
		this.processes.forEach(p => p.sourceIsUnavailable(source));
939
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
940 941 942
	}

	public dispose(): void {
J
Joao Moreno 已提交
943
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
944 945
	}
}