debugModel.ts 28.4 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
	}
}

I
isidor 已提交
52
export class OutputNameValueElement extends AbstractOutputElement implements debug.IExpression {
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
	}

I
isidor 已提交
74 75 76 77 78 79
	public get hasChildren(): boolean {
		return Array.isArray(this.valueObj) || isObject(this.valueObj);
	}

	public getChildren(): TPromise<debug.IExpression[]> {
		let result: debug.IExpression[] = [];
80
		if (Array.isArray(this.valueObj)) {
I
isidor 已提交
81
			result = (<any[]>this.valueObj).slice(0, OutputNameValueElement.MAX_CHILDREN)
82
				.map((v, index) => new OutputNameValueElement(String(index), v));
83
		} else if (isObject(this.valueObj)) {
I
isidor 已提交
84
			result = Object.getOwnPropertyNames(this.valueObj).slice(0, OutputNameValueElement.MAX_CHILDREN)
85
				.map(key => new OutputNameValueElement(key, this.valueObj[key]));
E
Erich Gamma 已提交
86 87
		}

I
isidor 已提交
88
		return TPromise.as(result);
E
Erich Gamma 已提交
89 90 91
	}
}

I
isidor 已提交
92
export class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
93

94
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
95
	// Use chunks to support variable paging #9537
96
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
97 98

	public valueChanged: boolean;
99
	private _value: string;
E
Erich Gamma 已提交
100

I
isidor 已提交
101
	constructor(
I
isidor 已提交
102
		protected process: debug.IProcess,
I
isidor 已提交
103 104
		public reference: number,
		private id: string,
I
isidor 已提交
105 106
		public namedVariables = 0,
		public indexedVariables = 0,
107
		private startOfVariables = 0
108
	) { }
E
Erich Gamma 已提交
109

110
	public getChildren(): TPromise<debug.IExpression[]> {
I
isidor 已提交
111
		if (!this.hasChildren) {
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
			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);
I
isidor 已提交
133
					childrenArray.push(new Variable(this.process, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start));
134 135
				}

136
				return childrenArray;
I
isidor 已提交
137
			}
E
Erich Gamma 已提交
138

139 140 141
			return this.fetchVariables(this.startOfVariables, this.indexedVariables, 'indexed')
				.then(variables => childrenArray.concat(variables));
		});
E
Erich Gamma 已提交
142
	}
143 144 145 146 147

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

148 149 150 151
	public get value(): string {
		return this._value;
	}

152
	public get hasChildren(): boolean {
I
isidor 已提交
153
		// only variables with reference > 0 have children.
154 155 156
		return this.reference > 0;
	}

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

170 171
	// 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 {
172
		return !!this.indexedVariables;
173 174
	}

175
	public set value(value: string) {
176
		this._value = value;
177 178 179 180
		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 已提交
181 182
}

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

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

189
	constructor(public name: string, id = generateUuid()) {
I
isidor 已提交
190
		super(null, 0, id);
191
		this.available = false;
192 193 194 195 196
		// 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 已提交
197
	}
198 199 200 201 202 203 204 205 206 207

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

I
isidor 已提交
208
		this.process = process;
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
		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;
		});
	}
228
}
E
Erich Gamma 已提交
229

230 231
export class Variable extends ExpressionContainer implements debug.IExpression {

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

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

I
isidor 已提交
254 255 256 257 258
	public get evaluateName(): string {
		if (this._evaluateName) {
			return this._evaluateName;
		}

I
isidor 已提交
259
		// TODO@Isidor get rid of this ugly heuristic
I
isidor 已提交
260 261 262 263 264 265 266 267 268 269 270 271
		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;
I
isidor 已提交
272
			} else if (Variable.ARRAY_ELEMENT_SYNTAX.test(name) || (this.process.session.configuration.type === 'node' && !Variable.NOT_PROPERTY_SYNTAX.test(name))) {
I
isidor 已提交
273 274 275 276 277 278 279 280 281 282
				// 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;
	}

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

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

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

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

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

	public getId(): string {
333
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
334 335
	}

336 337
	public getScopes(): TPromise<debug.IScope[]> {
		if (!this.scopes) {
I
isidor 已提交
338
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
339
				return response && response.body && response.body.scopes ?
I
isidor 已提交
340 341
					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)) : [];
342
			}, err => []);
E
Erich Gamma 已提交
343 344 345 346
		}

		return this.scopes;
	}
I
isidor 已提交
347 348

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

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

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

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

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

I
isidor 已提交
390
	public clearCallStack(): void {
391 392
		this.promisedCallStack = null;
		this.cachedCallStack = null;
I
isidor 已提交
393 394
	}

395
	public getCallStack(): debug.IStackFrame[] {
I
isidor 已提交
396
		return this.cachedCallStack;
I
isidor 已提交
397 398
	}

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

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

I
isidor 已提交
423
		return this.promisedCallStack;
E
Erich Gamma 已提交
424 425
	}

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

I
isidor 已提交
432 433 434 435
			if (this.stoppedDetails) {
				this.stoppedDetails.totalFrames = response.body.totalFrames;
			}

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

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

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

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

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

481
export class Process implements debug.IProcess {
482

483
	private threads: { [reference: number]: Thread; };
484

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

I
isidor 已提交
489 490 491 492
	public get session(): debug.ISession {
		return this._session;
	}

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

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

501
	public getId(): string {
502
		return this._session.getId();
503 504 505 506 507 508
	}

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

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

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

I
isidor 已提交
571 572 573 574 575 576 577 578
export class Breakpoint implements debug.IBreakpoint {

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

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

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

	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 已提交
618
		this.id = generateUuid();
I
isidor 已提交
619 620 621 622 623
	}

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

626
export class Model implements debug.IModel {
E
Erich Gamma 已提交
627

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

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

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

656 657
	public getProcesses(): Process[] {
		return this.processes;
658 659
	}

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

		return process;
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
	public addBreakpoints(uri: uri, rawData: debug.IRawBreakpoint[]): void {
735
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
736
			new Breakpoint(uri, rawBp.lineNumber, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
737
		this.breakpointsActivated = true;
738
		this._onDidChangeBreakpoints.fire();
739
	}
E
Erich Gamma 已提交
740

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

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

		// 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);
759
		this._onDidChangeBreakpoints.fire();
760 761
	}

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

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

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

782
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
783 784
	}

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

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

801
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
802 803
	}

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

E
Erich Gamma 已提交
809 810 811 812
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

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

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

836
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
837 838
	}

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

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

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

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

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

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

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

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

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

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

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

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

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