debugModel.ts 29.9 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';
I
isidor 已提交
22
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
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)) {
I
isidor 已提交
68
			return `"${this.valueObj}"`;
E
Erich Gamma 已提交
69 70
		}

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

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

	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

I
isidor 已提交
94
	public static allValues: Map<string, string> = new Map<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;
I
isidor 已提交
177 178 179
		this.valueChanged = ExpressionContainer.allValues.get(this.getId()) &&
			ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value;
		ExpressionContainer.allValues.set(this.getId(), value);
180
	}
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

I
isidor 已提交
348 349 350 351 352 353 354 355 356 357 358 359 360
	public getMostSpecificScopes(range: IRange): TPromise<debug.IScope[]> {
		return this.getScopes().then(scopes => {
			scopes = scopes.filter(s => !s.expensive);
			const haveRangeInfo = scopes.some(s => !!s.range);
			if (!haveRangeInfo) {
				return scopes;
			}

			return [scopes.filter(scope => scope.range && Range.containsRange(scope.range, range))
				.sort((first, second) => (first.range.endLineNumber - first.range.startLineNumber) - (second.range.endLineNumber - second.range.startLineNumber)).shift()];
		});
	}

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

	public toString(): string {
I
isidor 已提交
366
		return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.lineNumber})`;
I
isidor 已提交
367
	}
I
isidor 已提交
368 369 370 371 372 373 374 375 376 377 378 379 380

	public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
		return editorService.openEditor({
			resource: this.source.uri,
			description: this.source.origin,
			options: {
				preserveFocus,
				selection: { startLineNumber: this.lineNumber, startColumn: 1 },
				revealIfVisible: true,
				revealInCenterIfOutsideViewport: true
			}
		}, sideBySide);
	}
E
Erich Gamma 已提交
381 382
}

I
isidor 已提交
383 384 385 386 387
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 已提交
388

389
	constructor(public process: debug.IProcess, public name: string, public threadId: number) {
390 391 392
		this.promisedCallStack = null;
		this.stoppedDetails = null;
		this.cachedCallStack = null;
I
isidor 已提交
393
		this.stopped = false;
E
Erich Gamma 已提交
394 395 396
	}

	public getId(): string {
I
isidor 已提交
397
		return `thread:${this.process.getId()}:${this.threadId}`;
I
isidor 已提交
398
	}
I
isidor 已提交
399

I
isidor 已提交
400
	public clearCallStack(): void {
401 402
		this.promisedCallStack = null;
		this.cachedCallStack = null;
I
isidor 已提交
403 404
	}

405
	public getCallStack(): debug.IStackFrame[] {
I
isidor 已提交
406
		return this.cachedCallStack;
I
isidor 已提交
407 408
	}

409 410 411 412 413 414 415 416
	/**
	 * 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 已提交
417 418 419
		if (!this.stopped) {
			return TPromise.as([]);
		}
E
Erich Gamma 已提交
420

I
isidor 已提交
421 422 423 424 425 426 427 428 429 430 431
		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 已提交
432

I
isidor 已提交
433
		return this.promisedCallStack;
E
Erich Gamma 已提交
434 435
	}

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

I
isidor 已提交
442 443 444 445
			if (this.stoppedDetails) {
				this.stoppedDetails.totalFrames = response.body.totalFrames;
			}

I
isidor 已提交
446 447
			return response.body.stackFrames.map((rsf, level) => {
				if (!rsf) {
448
					return new StackFrame(this, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, true), nls.localize('unknownStack', "Unknown stack location"), null, null);
I
isidor 已提交
449 450
				}

451
				return new StackFrame(this, rsf.id, rsf.source ? new Source(rsf.source, rsf.source.presentationHint === 'deemphasize') : new Source({ name: UNKNOWN_SOURCE_LABEL }, true), rsf.name, rsf.line, rsf.column);
I
isidor 已提交
452 453
			});
		}, (err: Error) => {
I
isidor 已提交
454 455 456 457
			if (this.stoppedDetails) {
				this.stoppedDetails.framesErrorMessage = err.message;
			}

I
isidor 已提交
458 459
			return [];
		});
E
Erich Gamma 已提交
460
	}
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484

	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 已提交
485 486 487 488

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

491
export class Process implements debug.IProcess {
492

I
isidor 已提交
493
	private threads: Map<number, Thread>;
494

495
	constructor(public configuration: debug.IConfig, private _session: debug.ISession & debug.ITreeElement) {
I
isidor 已提交
496
		this.threads = new Map<number, Thread>();
497 498
	}

I
isidor 已提交
499 500 501 502
	public get session(): debug.ISession {
		return this._session;
	}

503 504 505 506
	public get name(): string {
		return this.configuration.name;
	}

I
isidor 已提交
507 508 509 510
	public isAttach(): boolean {
		return this.configuration.type === 'attach';
	}

511
	public getThread(threadId: number): Thread {
I
isidor 已提交
512
		return this.threads.get(threadId);
I
isidor 已提交
513 514 515
	}

	public getAllThreads(): debug.IThread[] {
I
isidor 已提交
516 517 518
		const result = [];
		this.threads.forEach(t => result.push(t));
		return result;
I
isidor 已提交
519 520
	}

521
	public getId(): string {
522
		return this._session.getId();
523 524 525 526
	}

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

I
isidor 已提交
527
		if (data.thread && !this.threads.has(data.threadId)) {
528
			// A new thread came in, initialize it.
I
isidor 已提交
529
			this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id));
I
isidor 已提交
530 531 532
		} else if (data.thread && data.thread.name) {
			// Just the thread name got updated #18244
			this.threads.get(data.threadId).name = data.thread.name;
533 534 535 536 537 538
		}

		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
			if (data.allThreadsStopped) {
I
isidor 已提交
539
				this.threads.forEach(thread => {
540 541 542
					// 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 已提交
543 544 545
					thread.stoppedDetails = clone(data.stoppedDetails);
					thread.stopped = true;
					thread.clearCallStack();
546
				});
I
isidor 已提交
547
			} else if (this.threads.has(data.threadId)) {
548
				// One thread is stopped, only update that thread.
I
isidor 已提交
549 550 551 552
				const thread = this.threads.get(data.threadId);
				thread.stoppedDetails = data.stoppedDetails;
				thread.clearCallStack();
				thread.stopped = true;
553 554 555 556 557 558
			}
		}
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
I
isidor 已提交
559 560 561 562 563
			if (this.threads.has(reference)) {
				const thread = this.threads.get(reference);
				thread.clearCallStack();
				thread.stoppedDetails = undefined;
				thread.stopped = false;
564 565

				if (removeThreads) {
I
isidor 已提交
566
					this.threads.delete(reference);
567 568 569
				}
			}
		} else {
I
isidor 已提交
570 571 572 573
			this.threads.forEach(thread => {
				thread.clearCallStack();
				thread.stoppedDetails = undefined;
				thread.stopped = false;
574 575 576
			});

			if (removeThreads) {
I
isidor 已提交
577
				this.threads.clear();
I
isidor 已提交
578
				ExpressionContainer.allValues.clear();
579 580 581 582
			}
		}
	}

I
isidor 已提交
583
	public deemphasizeSource(uri: uri): void {
I
isidor 已提交
584 585 586
		this.threads.forEach(thread => {
			thread.getCallStack().forEach(stackFrame => {
				if (stackFrame.source.uri.toString() === uri.toString()) {
587
					stackFrame.source.deemphasize = true;
I
isidor 已提交
588 589
				}
			});
590 591
		});
	}
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611

	public completions(frameId: number, text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
		if (!this.session.configuration.capabilities.supportsCompletionsRequest) {
			return TPromise.as([]);
		}

		return this.session.completions({
			frameId,
			text,
			column: position.column,
			line: position.lineNumber
		}).then(response => {
			return response && response.body && response.body.targets ? response.body.targets.map(item => (<ISuggestion>{
				label: item.label,
				insertText: item.text || item.label,
				type: item.type,
				overwriteBefore: item.length || overwriteBefore
			})) : [];
		}, err => []);
	}
I
isidor 已提交
612
}
613

I
isidor 已提交
614 615 616 617 618 619 620 621
export class Breakpoint implements debug.IBreakpoint {

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

	constructor(
622
		public uri: uri,
623
		public lineNumber: number,
I
isidor 已提交
624
		public column: number,
I
isidor 已提交
625 626 627 628 629 630 631 632
		public enabled: boolean,
		public condition: string,
		public hitCondition: string
	) {
		if (enabled === undefined) {
			this.enabled = true;
		}
		this.verified = false;
I
isidor 已提交
633
		this.id = generateUuid();
I
isidor 已提交
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
	}

	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 已提交
649
		this.id = generateUuid();
I
isidor 已提交
650 651 652 653 654 655 656 657 658 659 660 661
	}

	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 已提交
662
		this.id = generateUuid();
I
isidor 已提交
663 664 665 666 667
	}

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

670
export class Model implements debug.IModel {
E
Erich Gamma 已提交
671

672
	private processes: Process[];
E
Erich Gamma 已提交
673 674
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
675 676 677 678
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
679

I
isidor 已提交
680
	constructor(
681
		private breakpoints: Breakpoint[],
I
isidor 已提交
682
		private breakpointsActivated: boolean,
683 684
		private functionBreakpoints: FunctionBreakpoint[],
		private exceptionBreakpoints: ExceptionBreakpoint[],
I
isidor 已提交
685 686
		private watchExpressions: Expression[]
	) {
687
		this.processes = [];
E
Erich Gamma 已提交
688 689
		this.replElements = [];
		this.toDispose = [];
690 691 692 693
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
694 695 696 697 698 699
	}

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

700 701
	public getProcesses(): Process[] {
		return this.processes;
702 703
	}

704 705
	public addProcess(configuration: debug.IConfig, session: debug.ISession & debug.ITreeElement): Process {
		const process = new Process(configuration, session);
706 707 708
		this.processes.push(process);

		return process;
709 710
	}

711
	public removeProcess(id: string): void {
712
		this.processes = this.processes.filter(p => p.getId() !== id);
713 714 715
		this._onDidChangeCallStack.fire();
	}

716 717 718 719 720 721 722 723 724 725 726 727
	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;
	}

728
	public get onDidChangeReplElements(): Event<void> {
729 730 731
		return this._onDidChangeREPLElements.event;
	}

732
	public rawUpdate(data: debug.IRawModelUpdate): void {
733 734 735 736
		let process = this.processes.filter(p => p.getId() === data.sessionId).pop();
		if (process) {
			process.rawUpdate(data);
			this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
737 738 739
		}
	}

740 741
	public clearThreads(id: string, removeThreads: boolean, reference: number = undefined): void {
		const process = this.processes.filter(p => p.getId() === id).pop();
742 743
		if (process) {
			process.clearThreads(removeThreads, reference);
744 745 746 747
			this._onDidChangeCallStack.fire();
		}
	}

748
	public getBreakpoints(): Breakpoint[] {
E
Erich Gamma 已提交
749 750 751
		return this.breakpoints;
	}

I
isidor 已提交
752 753 754 755
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
756 757 758 759
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

760
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
761
		if (data) {
762 763 764 765
			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);
			});
766 767 768
		}
	}

E
Erich Gamma 已提交
769 770 771 772
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

773 774
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
775
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
776 777
	}

778
	public addBreakpoints(uri: uri, rawData: debug.IRawBreakpoint[]): void {
779
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
I
isidor 已提交
780
			new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
781
		this.breakpointsActivated = true;
782
		this._onDidChangeBreakpoints.fire();
783
	}
E
Erich Gamma 已提交
784

785 786
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
787
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
788 789
	}

I
isidor 已提交
790
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
791 792 793
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
794
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
795
				bp.verified = bpData.verified;
796
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
797
				bp.message = bpData.message;
798 799
			}
		});
800 801 802

		// 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);
803
		this._onDidChangeBreakpoints.fire();
804 805
	}

806 807
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
808
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
809
			var breakpoint = <Breakpoint>element;
810
			breakpoint.verified = false;
E
Erich Gamma 已提交
811 812
		}

813
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
814 815
	}

816
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
817
		this.breakpoints.forEach(bp => {
818 819
			bp.enabled = enable;
			if (!enable) {
820
				bp.verified = false;
E
Erich Gamma 已提交
821 822
			}
		});
823 824
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
825

826
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
827 828
	}

I
isidor 已提交
829
	public addFunctionBreakpoint(functionName: string): void {
830
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
831
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
832 833
	}

834
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
835 836 837 838 839
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
840
				fbp.idFromAdapter = fbpData.id;
841
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
842 843 844
			}
		});

845
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
846 847
	}

848
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
849
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
850
		this._onDidChangeBreakpoints.fire();
851 852
	}

E
Erich Gamma 已提交
853 854 855 856
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

857
	public addReplExpression(process: debug.IProcess, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
858
		const expression = new Expression(name);
859
		this.addReplElements([expression]);
860
		return expression.evaluate(process, stackFrame, 'repl')
861
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
862 863
	}

I
isidor 已提交
864
	public appendToRepl(output: string | debug.IExpression, severity: severity): void {
865
		const previousOutput = this.replElements.length && (this.replElements[this.replElements.length - 1] as OutputElement);
866
		if (previousOutput instanceof OutputElement && severity === previousOutput.severity && previousOutput.value === output && output.trim() && output.length > 1) {
I
isidor 已提交
867 868
			// we got the same output (but not an empty string when trimmed) so we just increment the counter
			previousOutput.counter++;
869
		} else {
870 871 872 873 874 875 876
			if (previousOutput && previousOutput.value === '') {
				// remove potential empty lines between different output types
				this.replElements.pop();
			}

			const newReplElements = typeof output === 'string' ? output.split('\n').map(line => new OutputElement(line, severity)) : [output];
			this.addReplElements(newReplElements);
E
Erich Gamma 已提交
877 878
		}

879
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
880 881
	}

882 883
	private addReplElements(newElements: debug.ITreeElement[]): void {
		this.replElements.push(...newElements);
884 885 886 887 888
		if (this.replElements.length > MAX_REPL_LENGTH) {
			this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
		}
	}

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

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

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

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

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

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

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

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

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

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

I
isidor 已提交
945 946 947 948 949 950 951 952
	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 已提交
953 954
	public deemphasizeSource(uri: uri): void {
		this.processes.forEach(p => p.deemphasizeSource(uri));
955
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
956 957 958
	}

	public dispose(): void {
J
Joao Moreno 已提交
959
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
960 961
	}
}