debugModel.ts 31.2 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';
A
Alex Dima 已提交
16
import { Range, IRange } from 'vs/editor/common/core/range';
I
isidor 已提交
17 18
import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
I
isidor 已提交
19 20
import {
	ITreeElement, IExpression, IExpressionContainer, IProcess, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IModel,
21
	IConfig, ISession, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IRawBreakpoint, IExceptionInfo
I
isidor 已提交
22
} from 'vs/workbench/parts/debug/common/debug';
J
Johannes Rieken 已提交
23
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
I
isidor 已提交
24
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
E
Erich Gamma 已提交
25

26 27
const MAX_REPL_LENGTH = 10000;

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

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

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

40 41 42
export class OutputElement extends AbstractOutputElement {

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

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

I
isidor 已提交
53
export class OutputNameValueElement extends AbstractOutputElement implements IExpression {
E
Erich Gamma 已提交
54 55 56

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

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

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

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

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

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

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

I
isidor 已提交
93
export class ExpressionContainer implements IExpressionContainer {
E
Erich Gamma 已提交
94

I
isidor 已提交
95
	public static allValues: Map<string, string> = new Map<string, string>();
I
isidor 已提交
96
	// Use chunks to support variable paging #9537
97
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
98 99

	public valueChanged: boolean;
100
	private _value: string;
101
	protected children: TPromise<IExpression[]>;
E
Erich Gamma 已提交
102

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

112 113 114 115 116 117 118 119 120
	public get reference(): number {
		return this._reference;
	}

	public set reference(value: number) {
		this._reference = value;
		this.children = undefined; // invalidate children cache
	}

I
isidor 已提交
121
	public getChildren(): TPromise<IExpression[]> {
122 123 124 125 126 127 128 129
		if (!this.children) {
			this.children = this.doGetChildren();
		}

		return this.children;
	}

	private doGetChildren(): TPromise<IExpression[]> {
I
isidor 已提交
130
		if (!this.hasChildren) {
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
			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 已提交
152
					childrenArray.push(new Variable(this.process, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, null, true, start));
153 154
				}

155
				return childrenArray;
I
isidor 已提交
156
			}
E
Erich Gamma 已提交
157

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

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

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

171
	public get hasChildren(): boolean {
I
isidor 已提交
172
		// only variables with reference > 0 have children.
173 174 175
		return this.reference > 0;
	}

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

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

194
	public set value(value: string) {
195
		this._value = value;
I
isidor 已提交
196 197 198
		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);
199
	}
E
Erich Gamma 已提交
200 201
}

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

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

208
	constructor(public name: string, id = generateUuid()) {
I
isidor 已提交
209
		super(null, 0, id);
210
		this.available = false;
211 212 213 214 215
		// 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 已提交
216
	}
217

I
isidor 已提交
218
	public evaluate(process: IProcess, stackFrame: IStackFrame, context: string): TPromise<void> {
219
		if (!process || (!stackFrame && context !== 'repl')) {
220 221 222 223 224 225 226
			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 已提交
227
		this.process = process;
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
		return process.session.evaluate({
			expression: this.name,
			frameId: stackFrame ? stackFrame.frameId : undefined,
			context
		}).then(response => {
			this.available = !!(response && response.body);
			if (response && response.body) {
				this.value = response.body.result;
				this.reference = response.body.variablesReference;
				this.namedVariables = response.body.namedVariables;
				this.indexedVariables = response.body.indexedVariables;
				this.type = response.body.type;
			}
		}, err => {
			this.value = err.message;
			this.available = false;
			this.reference = 0;
		});
	}
247
}
E
Erich Gamma 已提交
248

I
isidor 已提交
249
export class Variable extends ExpressionContainer implements IExpression {
250

251 252 253
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;

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

	public setVariable(value: string): TPromise<any> {
I
isidor 已提交
272
		return this.process.session.setVariable({
273 274
			name: this.name,
			value,
275
			variablesReference: (<ExpressionContainer>this.parent).reference
276 277 278
		}).then(response => {
			if (response && response.body) {
				this.value = response.body.value;
279
				this.type = response.body.type || this.type;
280 281 282
				this.reference = response.body.variablesReference;
				this.namedVariables = response.body.namedVariables;
				this.indexedVariables = response.body.indexedVariables;
283 284 285 286 287
			}
		}, err => {
			this.errorMessage = err.message;
		});
	}
E
Erich Gamma 已提交
288 289
}

I
isidor 已提交
290
export class Scope extends ExpressionContainer implements IScope {
E
Erich Gamma 已提交
291

I
isidor 已提交
292
	constructor(
I
isidor 已提交
293
		stackFrame: IStackFrame,
I
isidor 已提交
294 295 296
		public name: string,
		reference: number,
		public expensive: boolean,
297
		namedVariables: number,
I
isidor 已提交
298 299
		indexedVariables: number,
		public range?: IRange
I
isidor 已提交
300
	) {
I
isidor 已提交
301
		super(stackFrame.thread.process, reference, `scope:${stackFrame.getId()}:${name}:${reference}`, namedVariables, indexedVariables);
E
Erich Gamma 已提交
302 303 304
	}
}

I
isidor 已提交
305
export class StackFrame implements IStackFrame {
E
Erich Gamma 已提交
306 307 308

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
309
	constructor(
I
isidor 已提交
310
		public thread: IThread,
I
isidor 已提交
311 312 313
		public frameId: number,
		public source: Source,
		public name: string,
I
isidor 已提交
314
		public range: IRange
I
isidor 已提交
315
	) {
E
Erich Gamma 已提交
316 317 318 319
		this.scopes = null;
	}

	public getId(): string {
320
		return `stackframe:${this.thread.getId()}:${this.frameId}`;
E
Erich Gamma 已提交
321 322
	}

I
isidor 已提交
323
	public getScopes(): TPromise<IScope[]> {
324
		if (!this.scopes) {
I
isidor 已提交
325
			this.scopes = this.thread.process.session.scopes({ frameId: this.frameId }).then(response => {
326
				return response && response.body && response.body.scopes ?
I
isidor 已提交
327 328
					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)) : [];
329
			}, err => []);
E
Erich Gamma 已提交
330 331 332 333
		}

		return this.scopes;
	}
I
isidor 已提交
334

I
isidor 已提交
335
	public getMostSpecificScopes(range: IRange): TPromise<IScope[]> {
I
isidor 已提交
336 337 338 339 340 341 342
		return this.getScopes().then(scopes => {
			scopes = scopes.filter(s => !s.expensive);
			const haveRangeInfo = scopes.some(s => !!s.range);
			if (!haveRangeInfo) {
				return scopes;
			}

343 344 345
			const scopesContainingRange = 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));
			return scopesContainingRange.length > 0 ? scopesContainingRange.slice(0, 1) : scopes;
I
isidor 已提交
346 347 348
		});
	}

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

	public toString(): string {
I
isidor 已提交
354
		return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`;
I
isidor 已提交
355
	}
I
isidor 已提交
356 357

	public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
358

I
isidor 已提交
359
		return !this.source.available ? TPromise.as(null) : editorService.openEditor({
I
isidor 已提交
360 361 362 363
			resource: this.source.uri,
			description: this.source.origin,
			options: {
				preserveFocus,
I
isidor 已提交
364
				selection: { startLineNumber: this.range.startLineNumber, startColumn: 1 },
I
isidor 已提交
365
				revealIfVisible: true,
366 367
				revealInCenterIfOutsideViewport: true,
				pinned: !preserveFocus
I
isidor 已提交
368 369 370
			}
		}, sideBySide);
	}
E
Erich Gamma 已提交
371 372
}

I
isidor 已提交
373
export class Thread implements IThread {
374 375
	private fetchPromise: TPromise<void>;
	private callStack: IStackFrame[];
I
isidor 已提交
376
	public stoppedDetails: IRawStoppedDetails;
I
isidor 已提交
377
	public stopped: boolean;
E
Erich Gamma 已提交
378

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

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

I
isidor 已提交
390
	public clearCallStack(): void {
391
		this.fetchPromise = null;
392
		this.callStack = [];
I
isidor 已提交
393 394
	}

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

399
	/**
400 401
	 * Queries the debug adapter for the callstack and returns a promise
	 * which completes once the call stack has been retrieved.
402
	 * If the thread is not stopped, it returns a promise to an empty array.
403 404
	 * Only fetches the first stack frame for performance reasons. Calling this method consecutive times
	 * gets the remainder of the call stack.
405
	 */
406
	public fetchCallStack(): TPromise<void> {
I
isidor 已提交
407
		if (!this.stopped) {
408
			return TPromise.as(null);
I
isidor 已提交
409
		}
E
Erich Gamma 已提交
410

411 412 413
		if (!this.fetchPromise) {
			this.fetchPromise = this.getCallStackImpl(0, 1).then(callStack => {
				this.callStack = callStack || [];
I
isidor 已提交
414
			});
415 416 417
		} else {
			this.fetchPromise = this.fetchPromise.then(() => this.getCallStackImpl(this.callStack.length, 20).then(callStackSecondPart => {
				this.callStack = this.callStack.concat(callStackSecondPart);
I
isidor 已提交
418 419
			}));
		}
E
Erich Gamma 已提交
420

421
		return this.fetchPromise;
E
Erich Gamma 已提交
422 423
	}

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

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

I
isidor 已提交
434
			return response.body.stackFrames.map((rsf, level) => {
I
isidor 已提交
435
				let source = new Source(rsf.source, rsf.source ? rsf.source.presentationHint : rsf.presentationHint);
436
				if (this.process.sources.has(source.uri.toString())) {
437 438 439
					const alreadyCreatedSource = this.process.sources.get(source.uri.toString());
					alreadyCreatedSource.presenationHint = source.presenationHint;
					source = alreadyCreatedSource;
440 441 442
				} else {
					this.process.sources.set(source.uri.toString(), source);
				}
I
isidor 已提交
443

I
isidor 已提交
444 445 446
				return new StackFrame(this, rsf.id, source, rsf.name, new Range(
					rsf.line,
					rsf.column,
447 448
					rsf.endLine,
					rsf.endColumn
I
isidor 已提交
449
				));
I
isidor 已提交
450 451
			});
		}, (err: Error) => {
I
isidor 已提交
452 453 454 455
			if (this.stoppedDetails) {
				this.stoppedDetails.framesErrorMessage = err.message;
			}

I
isidor 已提交
456 457
			return [];
		});
E
Erich Gamma 已提交
458
	}
459

460
	/**
461
	 * Returns exception info promise if the exception was thrown, otherwise null
462
	 */
463
	public get exceptionInfo(): TPromise<IExceptionInfo> {
464
		const session = this.process.session;
465 466 467 468 469 470 471 472 473 474 475 476 477 478
		if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') {
			if (!session.capabilities.supportsExceptionInfoRequest) {
				return TPromise.as({
					description: this.stoppedDetails.text,
					breakMode: null
				});
			}

			return session.exceptionInfo({ threadId: this.threadId }).then(exception => ({
				id: exception.body.exceptionId,
				description: exception.body.description,
				breakMode: exception.body.breakMode,
				details: exception.body.details
			}));
479 480
		}

481
		return TPromise.as(null);
482 483
	}

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
	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 已提交
507 508 509 510

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

I
isidor 已提交
513
export class Process implements IProcess {
514

I
isidor 已提交
515
	private threads: Map<number, Thread>;
516
	public sources: Map<string, Source>;
517

I
isidor 已提交
518
	constructor(public configuration: IConfig, private _session: ISession & ITreeElement) {
I
isidor 已提交
519
		this.threads = new Map<number, Thread>();
520
		this.sources = new Map<string, Source>();
521 522
	}

I
isidor 已提交
523
	public get session(): ISession {
I
isidor 已提交
524 525 526
		return this._session;
	}

527 528 529 530
	public get name(): string {
		return this.configuration.name;
	}

I
isidor 已提交
531 532 533 534
	public isAttach(): boolean {
		return this.configuration.type === 'attach';
	}

535
	public getThread(threadId: number): Thread {
I
isidor 已提交
536
		return this.threads.get(threadId);
I
isidor 已提交
537 538
	}

I
isidor 已提交
539
	public getAllThreads(): IThread[] {
I
isidor 已提交
540 541 542
		const result = [];
		this.threads.forEach(t => result.push(t));
		return result;
I
isidor 已提交
543 544
	}

545
	public getId(): string {
546
		return this._session.getId();
547 548
	}

I
isidor 已提交
549
	public rawUpdate(data: IRawModelUpdate): void {
550

I
isidor 已提交
551
		if (data.thread && !this.threads.has(data.threadId)) {
552
			// A new thread came in, initialize it.
I
isidor 已提交
553
			this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id));
I
isidor 已提交
554 555 556
		} else if (data.thread && data.thread.name) {
			// Just the thread name got updated #18244
			this.threads.get(data.threadId).name = data.thread.name;
557 558 559 560 561 562
		}

		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
			if (data.allThreadsStopped) {
I
isidor 已提交
563
				this.threads.forEach(thread => {
564 565 566
					// 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 已提交
567 568 569
					thread.stoppedDetails = clone(data.stoppedDetails);
					thread.stopped = true;
					thread.clearCallStack();
570
				});
I
isidor 已提交
571
			} else if (this.threads.has(data.threadId)) {
572
				// One thread is stopped, only update that thread.
I
isidor 已提交
573 574 575 576
				const thread = this.threads.get(data.threadId);
				thread.stoppedDetails = data.stoppedDetails;
				thread.clearCallStack();
				thread.stopped = true;
577 578 579 580 581 582
			}
		}
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
I
isidor 已提交
583 584 585 586 587
			if (this.threads.has(reference)) {
				const thread = this.threads.get(reference);
				thread.clearCallStack();
				thread.stoppedDetails = undefined;
				thread.stopped = false;
588 589

				if (removeThreads) {
I
isidor 已提交
590
					this.threads.delete(reference);
591 592 593
				}
			}
		} else {
I
isidor 已提交
594 595 596 597
			this.threads.forEach(thread => {
				thread.clearCallStack();
				thread.stoppedDetails = undefined;
				thread.stopped = false;
598 599 600
			});

			if (removeThreads) {
I
isidor 已提交
601
				this.threads.clear();
I
isidor 已提交
602
				ExpressionContainer.allValues.clear();
603 604 605 606
			}
		}
	}

607
	public completions(frameId: number, text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
608
		if (!this.session.capabilities.supportsCompletionsRequest) {
609 610 611 612 613 614 615 616 617
			return TPromise.as([]);
		}

		return this.session.completions({
			frameId,
			text,
			column: position.column,
			line: position.lineNumber
		}).then(response => {
I
isidor 已提交
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
			const result: ISuggestion[] = [];
			if (response && response.body && response.body.targets) {
				response.body.targets.forEach(item => {
					if (item && item.label) {
						result.push({
							label: item.label,
							insertText: item.text || item.label,
							type: item.type,
							filterText: item.start && item.length && text.substr(item.start, item.length).concat(item.label),
							overwriteBefore: item.length || overwriteBefore
						});
					}
				});
			}

			return result;
634 635
		}, err => []);
	}
I
isidor 已提交
636
}
637

I
isidor 已提交
638
export class Breakpoint implements IBreakpoint {
I
isidor 已提交
639 640 641 642

	public verified: boolean;
	public idFromAdapter: number;
	public message: string;
643 644
	public endLineNumber: number;
	public endColumn: number;
I
isidor 已提交
645 646 647
	private id: string;

	constructor(
648
		public uri: uri,
649
		public lineNumber: number,
I
isidor 已提交
650
		public column: number,
I
isidor 已提交
651 652
		public enabled: boolean,
		public condition: string,
653
		public hitCondition: string,
I
isidor 已提交
654 655 656 657 658
	) {
		if (enabled === undefined) {
			this.enabled = true;
		}
		this.verified = false;
I
isidor 已提交
659
		this.id = generateUuid();
I
isidor 已提交
660 661 662 663 664 665 666
	}

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

I
isidor 已提交
667
export class FunctionBreakpoint implements IFunctionBreakpoint {
I
isidor 已提交
668 669 670 671 672 673 674

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

	constructor(public name: string, public enabled: boolean, public hitCondition: string) {
		this.verified = false;
I
isidor 已提交
675
		this.id = generateUuid();
I
isidor 已提交
676 677 678 679 680 681 682
	}

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

I
isidor 已提交
683
export class ExceptionBreakpoint implements IExceptionBreakpoint {
I
isidor 已提交
684 685 686 687

	private id: string;

	constructor(public filter: string, public label: string, public enabled: boolean) {
I
isidor 已提交
688
		this.id = generateUuid();
I
isidor 已提交
689 690 691 692 693
	}

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

I
isidor 已提交
696
export class ThreadAndProcessIds implements ITreeElement {
697 698 699 700 701 702 703
	constructor(public processId: string, public threadId: number) { }

	public getId(): string {
		return `${this.processId}:${this.threadId}`;
	}
}

I
isidor 已提交
704
export class Model implements IModel {
E
Erich Gamma 已提交
705

706
	private processes: Process[];
E
Erich Gamma 已提交
707
	private toDispose: lifecycle.IDisposable[];
I
isidor 已提交
708
	private replElements: ITreeElement[];
709 710
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
I
isidor 已提交
711
	private _onDidChangeWatchExpressions: Emitter<IExpression>;
712
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
713

I
isidor 已提交
714
	constructor(
715
		private breakpoints: Breakpoint[],
I
isidor 已提交
716
		private breakpointsActivated: boolean,
717 718
		private functionBreakpoints: FunctionBreakpoint[],
		private exceptionBreakpoints: ExceptionBreakpoint[],
I
isidor 已提交
719 720
		private watchExpressions: Expression[]
	) {
721
		this.processes = [];
E
Erich Gamma 已提交
722 723
		this.replElements = [];
		this.toDispose = [];
724 725
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
I
isidor 已提交
726
		this._onDidChangeWatchExpressions = new Emitter<IExpression>();
727
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
728 729 730 731 732 733
	}

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

734 735
	public getProcesses(): Process[] {
		return this.processes;
736 737
	}

I
isidor 已提交
738
	public addProcess(configuration: IConfig, session: ISession & ITreeElement): Process {
739
		const process = new Process(configuration, session);
740 741 742
		this.processes.push(process);

		return process;
743 744
	}

745
	public removeProcess(id: string): void {
746
		this.processes = this.processes.filter(p => p.getId() !== id);
747 748 749
		this._onDidChangeCallStack.fire();
	}

750 751 752 753 754 755 756 757
	public get onDidChangeBreakpoints(): Event<void> {
		return this._onDidChangeBreakpoints.event;
	}

	public get onDidChangeCallStack(): Event<void> {
		return this._onDidChangeCallStack.event;
	}

I
isidor 已提交
758
	public get onDidChangeWatchExpressions(): Event<IExpression> {
759 760 761
		return this._onDidChangeWatchExpressions.event;
	}

762
	public get onDidChangeReplElements(): Event<void> {
763 764 765
		return this._onDidChangeREPLElements.event;
	}

I
isidor 已提交
766
	public rawUpdate(data: IRawModelUpdate): void {
767 768 769 770
		let process = this.processes.filter(p => p.getId() === data.sessionId).pop();
		if (process) {
			process.rawUpdate(data);
			this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
771 772 773
		}
	}

774 775
	public clearThreads(id: string, removeThreads: boolean, reference: number = undefined): void {
		const process = this.processes.filter(p => p.getId() === id).pop();
776 777
		if (process) {
			process.clearThreads(removeThreads, reference);
778 779 780 781
			this._onDidChangeCallStack.fire();
		}
	}

782 783
	public fetchCallStack(thread: IThread): TPromise<void> {
		return (<Thread>thread).fetchCallStack().then(() => {
784 785 786 787
			this._onDidChangeCallStack.fire();
		});
	}

788
	public getBreakpoints(): Breakpoint[] {
E
Erich Gamma 已提交
789 790 791
		return this.breakpoints;
	}

I
isidor 已提交
792
	public getFunctionBreakpoints(): IFunctionBreakpoint[] {
I
isidor 已提交
793 794 795
		return this.functionBreakpoints;
	}

I
isidor 已提交
796
	public getExceptionBreakpoints(): IExceptionBreakpoint[] {
E
Erich Gamma 已提交
797 798 799
		return this.exceptionBreakpoints;
	}

800
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
801
		if (data) {
802 803 804 805
			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);
			});
806 807 808
		}
	}

E
Erich Gamma 已提交
809 810 811 812
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

813 814
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
815
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
816 817
	}

I
isidor 已提交
818
	public addBreakpoints(uri: uri, rawData: IRawBreakpoint[]): void {
819
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
820
			new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition)));
821
		this.breakpointsActivated = true;
822
		this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);
823
		this._onDidChangeBreakpoints.fire();
824
	}
E
Erich Gamma 已提交
825

I
isidor 已提交
826
	public removeBreakpoints(toRemove: IBreakpoint[]): void {
827
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
828
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
829 830
	}

I
isidor 已提交
831
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
832 833 834
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
835
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
836
				bp.endLineNumber = bpData.endLine;
837
				bp.column = bpData.column;
838
				bp.endColumn = bpData.endColumn;
839
				bp.verified = bpData.verified;
840
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
841
				bp.message = bpData.message;
842 843
			}
		});
844
		this.breakpoints = distinct(this.breakpoints, bp => `${bp.uri.toString()}:${bp.lineNumber}:${bp.column}`);
845

846
		this._onDidChangeBreakpoints.fire();
847 848
	}

I
isidor 已提交
849
	public setEnablement(element: IEnablement, enable: boolean): void {
850
		element.enabled = enable;
E
Erich Gamma 已提交
851
		if (element instanceof Breakpoint && !element.enabled) {
J
Johannes Rieken 已提交
852
			var breakpoint = <Breakpoint>element;
853
			breakpoint.verified = false;
E
Erich Gamma 已提交
854 855
		}

856
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
857 858
	}

859
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
860
		this.breakpoints.forEach(bp => {
861 862
			bp.enabled = enable;
			if (!enable) {
863
				bp.verified = false;
E
Erich Gamma 已提交
864 865
			}
		});
866 867
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
868

869
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
870 871
	}

I
isidor 已提交
872
	public addFunctionBreakpoint(functionName: string): void {
873
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true, null));
874
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
875 876
	}

877
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number; hitCondition?: string } }): void {
I
isidor 已提交
878 879 880 881 882
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
883
				fbp.idFromAdapter = fbpData.id;
884
				fbp.hitCondition = fbpData.hitCondition;
I
isidor 已提交
885 886 887
			}
		});

888
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
889 890
	}

891
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
892
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
893
		this._onDidChangeBreakpoints.fire();
894 895
	}

I
isidor 已提交
896
	public getReplElements(): ITreeElement[] {
E
Erich Gamma 已提交
897 898 899
		return this.replElements;
	}

I
isidor 已提交
900
	public addReplExpression(process: IProcess, stackFrame: IStackFrame, name: string): TPromise<void> {
901
		const expression = new Expression(name);
902
		this.addReplElements([expression]);
903
		return expression.evaluate(process, stackFrame, 'repl')
904
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
905 906
	}

I
isidor 已提交
907
	public appendToRepl(output: string | IExpression, severity: severity): void {
I
isidor 已提交
908 909 910 911 912
		if (typeof output === 'string') {
			const previousOutput = this.replElements.length && (this.replElements[this.replElements.length - 1] as OutputElement);
			if (previousOutput instanceof OutputElement && severity === previousOutput.severity && previousOutput.value === output && output.trim() && output.length > 1) {
				// we got the same output (but not an empty string when trimmed) so we just increment the counter
				previousOutput.counter++;
913
			} else {
I
isidor 已提交
914 915 916 917
				const toAdd = output.split('\n').map(line => new OutputElement(line, severity));
				if (previousOutput instanceof OutputElement && severity === previousOutput.severity && toAdd.length) {
					previousOutput.value += toAdd.shift().value;
				}
I
isidor 已提交
918
				if (previousOutput && previousOutput.value === '' && previousOutput.severity !== severity) {
I
isidor 已提交
919 920 921 922
					// remove potential empty lines between different output types
					this.replElements.pop();
				}
				this.addReplElements(toAdd);
923
			}
I
isidor 已提交
924 925 926 927
		} else {
			// TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression
			(<any>output).severity = severity;
			this.addReplElements([output]);
E
Erich Gamma 已提交
928 929
		}

930
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
931 932
	}

I
isidor 已提交
933
	private addReplElements(newElements: ITreeElement[]): void {
934
		this.replElements.push(...newElements);
935 936 937 938 939
		if (this.replElements.length > MAX_REPL_LENGTH) {
			this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
		}
	}

940
	public removeReplExpressions(): void {
941 942
		if (this.replElements.length > 0) {
			this.replElements = [];
943
			this._onDidChangeREPLElements.fire();
944
		}
E
Erich Gamma 已提交
945 946 947 948 949 950
	}

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

I
isidor 已提交
951
	public addWatchExpression(process: IProcess, stackFrame: IStackFrame, name: string): TPromise<void> {
952
		const we = new Expression(name);
E
Erich Gamma 已提交
953 954
		this.watchExpressions.push(we);
		if (!name) {
955
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
956
			return TPromise.as(null);
E
Erich Gamma 已提交
957 958
		}

959
		return this.evaluateWatchExpressions(process, stackFrame, we.getId());
E
Erich Gamma 已提交
960 961
	}

I
isidor 已提交
962
	public renameWatchExpression(process: IProcess, stackFrame: IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
963
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
964 965
		if (filtered.length === 1) {
			filtered[0].name = newName;
966 967
			// Evaluate all watch expressions again since the new watch expression might have changed some.
			return this.evaluateWatchExpressions(process, stackFrame).then(() => {
968
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
969 970 971
			});
		}

A
Alex Dima 已提交
972
		return TPromise.as(null);
E
Erich Gamma 已提交
973 974
	}

I
isidor 已提交
975
	public evaluateWatchExpressions(process: IProcess, stackFrame: IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
976
		if (id) {
I
isidor 已提交
977
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
978
			if (filtered.length !== 1) {
A
Alex Dima 已提交
979
				return TPromise.as(null);
E
Erich Gamma 已提交
980 981
			}

982
			return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
983
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
984 985 986
			});
		}

987
		return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => {
988
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
989 990 991
		});
	}

992
	public removeWatchExpressions(id: string = null): void {
993
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
994
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
995 996
	}

I
isidor 已提交
997 998 999 1000 1001 1002 1003 1004
	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 已提交
1005
	public deemphasizeSource(uri: uri): void {
1006 1007
		this.processes.forEach(p => {
			if (p.sources.has(uri.toString())) {
1008
				p.sources.get(uri.toString()).presenationHint = 'deemphasize';
1009 1010
			}
		});
1011
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
1012 1013 1014
	}

	public dispose(): void {
J
Joao Moreno 已提交
1015
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
1016 1017
	}
}