debugModel.ts 28.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
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
import { isObject, isString } from 'vs/base/common/types';
15
import * as strings from 'vs/base/common/strings';
I
isidor 已提交
16
import { distinct } from 'vs/base/common/arrays';
I
isidor 已提交
17 18
import { IRange } from 'vs/editor/common/editorCommon';
import { Range } from 'vs/editor/common/core/range';
I
isidor 已提交
19 20
import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
I
isidor 已提交
21
import * as debug from 'vs/workbench/parts/debug/common/debug';
J
Johannes Rieken 已提交
22
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
23

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

I
isidor 已提交
27
export abstract class AbstractOutputElement implements debug.ITreeElement {
28
	private static ID_COUNTER = 0;
E
Erich Gamma 已提交
29

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

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

39 40 41
export class OutputElement extends AbstractOutputElement {

	public counter: number;
E
Erich Gamma 已提交
42

I
isidor 已提交
43 44 45 46
	constructor(
		public value: string,
		public severity: severity,
	) {
I
isidor 已提交
47
		super();
48
		this.counter = 1;
E
Erich Gamma 已提交
49 50 51
	}
}

52
export class OutputNameValueElement extends AbstractOutputElement {
E
Erich Gamma 已提交
53 54 55

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

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

	public get value(): string {
61 62 63 64 65 66 67
		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)) {
68
			return this.valueObj;
E
Erich Gamma 已提交
69 70
		}

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

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

83
		return [];
E
Erich Gamma 已提交
84 85 86
	}
}

87
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
88

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

	public valueChanged: boolean;
94
	private _value: string;
E
Erich Gamma 已提交
95

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

105
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
106
		if (!this.hasChildren) {
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
			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));
129 130
				}

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

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

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

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

147
	public get hasChildren(): boolean {
I
isidor 已提交
148
		// only variables with reference > 0 have children.
149 150 151
		return this.reference > 0;
	}

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

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

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

178
export class OutputExpressionContainer extends ExpressionContainer {
179
	constructor(public name: string, stackFrame: debug.IStackFrame, reference: number, public annotation = null) {
180 181 182 183
		super(stackFrame, reference, generateUuid());
	}
}

184
export class Expression extends ExpressionContainer implements debug.IExpression {
I
isidor 已提交
185
	static DEFAULT_VALUE = nls.localize('notAvailable', "not available");
E
Erich Gamma 已提交
186

187
	public available: boolean;
188
	public type: string;
E
Erich Gamma 已提交
189

190
	constructor(public name: string, id = generateUuid()) {
I
isidor 已提交
191
		super(null, 0, id);
192
		this.available = false;
193 194 195 196 197
		// 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 已提交
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 224 225 226 227 228 229 230 231

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

234 235
export class Variable extends ExpressionContainer implements debug.IExpression {

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

I
isidor 已提交
241
	constructor(
242
		stackFrame: debug.IStackFrame,
I
isidor 已提交
243 244 245
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
I
isidor 已提交
246
		private _evaluateName: string,
I
isidor 已提交
247
		value: string,
248 249
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
250 251
		public type: string = null,
		public available = true,
252
		startOfVariables = 0
I
isidor 已提交
253
	) {
254
		super(stackFrame, reference, `variable:${parent.getId()}:${name}:${reference}`, namedVariables, indexedVariables, startOfVariables);
255
		this.value = value;
E
Erich Gamma 已提交
256
	}
257

I
isidor 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	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;
	}

286 287 288 289
	public setVariable(value: string): TPromise<any> {
		return this.stackFrame.thread.process.session.setVariable({
			name: this.name,
			value,
290
			variablesReference: (<ExpressionContainer>this.parent).reference
291 292 293
		}).then(response => {
			if (response && response.body) {
				this.value = response.body.value;
294
				this.type = response.body.type || this.type;
295 296 297
				this.reference = response.body.variablesReference;
				this.namedVariables = response.body.namedVariables;
				this.indexedVariables = response.body.indexedVariables;
298 299 300 301 302
			}
		}, err => {
			this.errorMessage = err.message;
		});
	}
E
Erich Gamma 已提交
303 304
}

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

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

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

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

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

339 340
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
341
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
342
				return response && response.body && response.body.scopes ?
I
isidor 已提交
343 344
					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)) : [];
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
	public completions(text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
I
isidor 已提交
356 357 358 359 360 361 362 363 364 365
		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 => {
366
			return response && response.body && response.body.targets ? response.body.targets.map(item => (<ISuggestion>{
I
isidor 已提交
367 368
				label: item.label,
				insertText: item.text || item.label,
369 370
				type: item.type,
				overwriteBefore: item.length || overwriteBefore
I
isidor 已提交
371 372 373
			})) : [];
		}, err => []);
	}
E
Erich Gamma 已提交
374 375
}

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

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

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

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

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

402 403 404 405 406 407 408 409
	/**
	 * 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 已提交
410 411 412
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
413

I
isidor 已提交
414 415 416 417 418 419 420 421 422 423 424
		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 已提交
425

I
isidor 已提交
426
		return this.promisedCallStack;
E
Erich Gamma 已提交
427 428
	}

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

I
isidor 已提交
435 436 437 438
			if (this.stoppedDetails) {
				this.stoppedDetails.totalFrames = response.body.totalFrames;
			}

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

444
				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 已提交
445 446
			});
		}, (err: Error) => {
I
isidor 已提交
447 448 449 450
			if (this.stoppedDetails) {
				this.stoppedDetails.framesErrorMessage = err.message;
			}

I
isidor 已提交
451 452
			return [];
		});
E
Erich Gamma 已提交
453
	}
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477

	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 已提交
478 479 480 481

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

484
export class Process implements debug.IProcess {
485

486
	private threads: { [reference: number]: Thread; };
487

I
isidor 已提交
488
	constructor(public name: string, private _session: debug.ISession & debug.ITreeElement) {
489 490 491
		this.threads = {};
	}

I
isidor 已提交
492 493 494 495
	public get session(): debug.ISession {
		return this._session;
	}

496
	public getThread(threadId: number): Thread {
I
isidor 已提交
497 498 499 500 501 502 503
		return this.threads[threadId];
	}

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

504
	public getId(): string {
505
		return this._session.getId();
506 507 508 509 510 511
	}

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

		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
512
			this.threads[data.threadId] = new Thread(this, data.thread.name, data.thread.id);
513 514 515 516 517 518 519 520 521 522
		}

		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 已提交
523
					this.threads[ref].stoppedDetails = clone(data.stoppedDetails);
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 566 567 568 569 570 571
					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 已提交
572
}
573

I
isidor 已提交
574 575 576 577 578 579 580 581
export class Breakpoint implements debug.IBreakpoint {

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

	constructor(
582
		public uri: uri,
583
		public lineNumber: number,
I
isidor 已提交
584 585 586 587 588 589 590 591
		public enabled: boolean,
		public condition: string,
		public hitCondition: string
	) {
		if (enabled === undefined) {
			this.enabled = true;
		}
		this.verified = false;
I
isidor 已提交
592
		this.id = generateUuid();
I
isidor 已提交
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
	}

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

663 664 665 666 667
	public addProcess(name: string, session: debug.ISession & debug.ITreeElement): Process {
		const process = new Process(name, session);
		this.processes.push(process);

		return process;
668 669
	}

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

675 676 677 678 679 680 681 682 683 684 685 686
	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;
	}

687
	public get onDidChangeReplElements(): Event<void> {
688 689 690
		return this._onDidChangeREPLElements.event;
	}

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

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

707
	public getBreakpoints(): Breakpoint[] {
E
Erich Gamma 已提交
708 709 710
		return this.breakpoints;
	}

I
isidor 已提交
711 712 713 714
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
715 716 717 718
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

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

E
Erich Gamma 已提交
728 729 730 731
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

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

737
	public addBreakpoints(uri: uri, rawData: debug.IRawBreakpoint[]): void {
738
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
739
			new Breakpoint(uri, rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
740
		this.breakpointsActivated = true;
741
		this._onDidChangeBreakpoints.fire();
742
	}
E
Erich Gamma 已提交
743

744 745
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
746
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
747 748
	}

I
isidor 已提交
749
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
750 751 752
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
753
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
754
				bp.verified = bpData.verified;
755
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
756
				bp.message = bpData.message;
757 758
			}
		});
759 760 761

		// 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);
762
		this._onDidChangeBreakpoints.fire();
763 764
	}

765 766
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
767
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
768
			var breakpoint = <Breakpoint>element;
769
			breakpoint.verified = false;
E
Erich Gamma 已提交
770 771
		}

772
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
773 774
	}

775
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
776
		this.breakpoints.forEach(bp => {
777 778
			bp.enabled = enable;
			if (!enable) {
779
				bp.verified = false;
E
Erich Gamma 已提交
780 781
			}
		});
782 783
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
784

785
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
786 787
	}

I
isidor 已提交
788
	public addFunctionBreakpoint(functionName: string): void {
789
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
790
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
791 792
	}

793
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
794 795 796 797 798
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
799
				fbp.idFromAdapter = fbpData.id;
800
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
801 802 803
			}
		});

804
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
805 806
	}

807
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
808
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
809
		this._onDidChangeBreakpoints.fire();
810 811
	}

E
Erich Gamma 已提交
812 813 814 815
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

816
	public addReplExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
817
		const expression = new Expression(name);
818
		this.addReplElement(expression);
819
		return expression.evaluate(process, stackFrame, 'repl')
820
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
821 822
	}

823 824 825 826 827 828 829
	public appendReplOutput(output: OutputElement | OutputExpressionContainer | OutputNameValueElement): void {
		const previousOutput = this.replElements.length && (this.replElements[this.replElements.length - 1] as OutputElement);
		const groupTogether = output instanceof OutputElement && previousOutput instanceof OutputElement && output.severity === previousOutput.severity;
		if (groupTogether) {
			if (strings.endsWith(previousOutput.value, '\n') && previousOutput.value === output.value && output.value.trim()) {
				// we got the same output (but not an empty string when trimmed) so we just increment the counter
				previousOutput.counter++;
E
Erich Gamma 已提交
830
			} else {
831 832
				// append to previous line if same group
				previousOutput.value += output.value;
E
Erich Gamma 已提交
833
			}
834
		} else {
835
			this.addReplElement(output);
E
Erich Gamma 已提交
836 837
		}

838
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
839 840
	}

841 842
	private addReplElement(newElement: debug.ITreeElement): void {
		this.replElements.push(newElement);
843 844 845 846 847
		if (this.replElements.length > MAX_REPL_LENGTH) {
			this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
		}
	}

848
	public removeReplExpressions(): void {
849 850
		if (this.replElements.length > 0) {
			this.replElements = [];
851
			this._onDidChangeREPLElements.fire();
852
		}
E
Erich Gamma 已提交
853 854 855 856 857 858
	}

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

859
	public addWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
860
		const we = new Expression(name);
E
Erich Gamma 已提交
861 862
		this.watchExpressions.push(we);
		if (!name) {
863
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
864
			return TPromise.as(null);
E
Erich Gamma 已提交
865 866
		}

867
		return this.evaluateWatchExpressions(process, stackFrame, we.getId());
E
Erich Gamma 已提交
868 869
	}

870
	public renameWatchExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
871
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
872 873
		if (filtered.length === 1) {
			filtered[0].name = newName;
874
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
875
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
876 877 878
			});
		}

A
Alex Dima 已提交
879
		return TPromise.as(null);
E
Erich Gamma 已提交
880 881
	}

882
	public evaluateWatchExpressions(process: debug.IProcess, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
883
		if (id) {
I
isidor 已提交
884
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
885
			if (filtered.length !== 1) {
A
Alex Dima 已提交
886
				return TPromise.as(null);
E
Erich Gamma 已提交
887 888
			}

889
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
890
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
891 892 893
			});
		}

894
		return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => {
895
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
896 897 898
		});
	}

899
	public removeWatchExpressions(id: string = null): void {
900
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
901
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
902 903
	}

I
isidor 已提交
904 905 906 907 908 909 910 911
	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 已提交
912
	public sourceIsUnavailable(source: Source): void {
913
		this.processes.forEach(p => p.sourceIsUnavailable(source));
914
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
915 916 917
	}

	public dispose(): void {
J
Joao Moreno 已提交
918
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
919 920
	}
}