debugModel.ts 25.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import {TPromise} from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
7 8
import nls = require('vs/nls');
import lifecycle = require('vs/base/common/lifecycle');
I
isidor 已提交
9
import Event, {Emitter} from 'vs/base/common/event';
E
Erich Gamma 已提交
10
import uuid = require('vs/base/common/uuid');
I
isidor 已提交
11
import objects = require('vs/base/common/objects');
E
Erich Gamma 已提交
12 13 14 15
import severity from 'vs/base/common/severity';
import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays');
import debug = require('vs/workbench/parts/debug/common/debug');
I
isidor 已提交
16
import {Source} from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
17

18
const MAX_REPL_LENGTH = 10000;
I
isidor 已提交
19
const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
20

E
Erich Gamma 已提交
21 22 23 24
function massageValue(value: string): string {
	return value ? value.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') : value;
}

I
isidor 已提交
25 26 27 28 29
export function evaluateExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, expression: Expression, context: string): TPromise<Expression> {
	if (!session) {
		expression.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate") : Expression.DEFAULT_VALUE;
		expression.available = false;
		expression.reference = 0;
A
Alex Dima 已提交
30
		return TPromise.as(expression);
I
isidor 已提交
31 32 33 34 35 36 37
	}

	return session.evaluate({
		expression: expression.name,
		frameId: stackFrame ? stackFrame.frameId : undefined,
		context
	}).then(response => {
38
		expression.available = !!(response && response.body);
I
isidor 已提交
39
		if (response && response.body) {
I
isidor 已提交
40 41
			expression.value = response.body.result;
			expression.reference = response.body.variablesReference;
42
			expression.namedVariables = response.body.namedVariables;
43
			expression.indexedVariables = response.body.indexedVariables;
44
			expression.type = response.body.type;
I
isidor 已提交
45
		}
I
isidor 已提交
46 47 48 49 50 51 52 53 54 55 56

		return expression;
	}, err => {
		expression.value = err.message;
		expression.available = false;
		expression.reference = 0;

		return expression;
	});
}

I
isidor 已提交
57 58
const notPropertySyntax = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const arrayElementSyntax = /\[.*\]$/;
I
isidor 已提交
59

60 61 62
export function getFullExpressionName(expression: debug.IExpression, sessionType: string): string {
	let names = [expression.name];
	if (expression instanceof Variable) {
I
isidor 已提交
63
		let v = (<Variable> expression).parent;
64 65 66 67 68 69 70 71 72 73 74
		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 已提交
75
		} else if (arrayElementSyntax.test(name) || (sessionType === 'node' && !notPropertySyntax.test(name))) {
I
isidor 已提交
76
			// use safe way to access node properties a['property_name']. Also handles array elements.
77 78 79 80 81 82 83 84 85
			result = name && name.indexOf('[') === 0 ? `${ result }${ name }` : `${ result }['${ name }']`;
		} else {
			result = `${ result }.${ name }`;
		}
	});

	return result;
}

E
Erich Gamma 已提交
86
export class Thread implements debug.IThread {
87 88
	private promisedCallStack: TPromise<debug.IStackFrame[]>;
	private cachedCallStack: debug.IStackFrame[];
I
isidor 已提交
89
	public stoppedDetails: debug.IRawStoppedDetails;
90
	public stopped: boolean;
E
Erich Gamma 已提交
91

I
isidor 已提交
92
	constructor(public name: string, public threadId: number) {
93
		this.promisedCallStack = undefined;
I
isidor 已提交
94
		this.stoppedDetails = undefined;
95 96
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
97 98 99 100 101
	}

	public getId(): string {
		return `thread:${ this.name }:${ this.threadId }`;
	}
102 103 104 105 106 107 108 109 110 111

	public clearCallStack(): void {
		this.promisedCallStack = undefined;
		this.cachedCallStack = undefined;
	}

	public getCachedCallStack(): debug.IStackFrame[] {
		return this.cachedCallStack;
	}

I
isidor 已提交
112
	public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
113 114 115
		if (!this.stopped) {
			return TPromise.as([]);
		}
I
isidor 已提交
116

117
		if (!this.promisedCallStack) {
I
isidor 已提交
118 119 120 121 122 123 124 125 126
			this.promisedCallStack = this.getCallStackImpl(debugService, 0).then(callStack => {
				this.cachedCallStack = callStack;
				return callStack;
			});
		} else if (getAdditionalStackFrames) {
			this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(debugService, callStackFirstPart.length).then(callStackSecondPart => {
				this.cachedCallStack = callStackFirstPart.concat(callStackSecondPart);
				return this.cachedCallStack;
			}));
127 128 129 130 131
		}

		return this.promisedCallStack;
	}

I
isidor 已提交
132
	private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise<debug.IStackFrame[]> {
133
		let session = debugService.getActiveSession();
I
isidor 已提交
134
		return session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
135 136 137 138
			if (!response || !response.body) {
				return [];
			}

139
			this.stoppedDetails.totalFrames = response.body.totalFrames;
140 141
			return response.body.stackFrames.map((rsf, level) => {
				if (!rsf) {
I
isidor 已提交
142
					return new StackFrame(this.threadId, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
143 144
				}

I
isidor 已提交
145
				return new StackFrame(this.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: UNKNOWN_SOURCE_LABEL }, false), rsf.name, rsf.line, rsf.column);
146
			});
147 148 149
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
150 151
		});
	}
E
Erich Gamma 已提交
152 153 154
}

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

157
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
158
		// noop
E
Erich Gamma 已提交
159 160 161
	}

	public getId(): string {
I
isidor 已提交
162
		return `outputelement:${ this.id }`;
E
Erich Gamma 已提交
163 164 165 166 167
	}
}

export class ValueOutputElement extends OutputElement {

I
isidor 已提交
168 169 170 171 172 173
	constructor(
		public value: string,
		public severity: severity,
		public category?: string,
		public counter: number = 1
	) {
I
isidor 已提交
174
		super();
E
Erich Gamma 已提交
175 176 177 178 179 180 181 182 183 184
	}
}

export class KeyValueOutputElement extends OutputElement {

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

	private children: debug.ITreeElement[];
	private _valueName: string;

I
isidor 已提交
185 186
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

		this._valueName = null;
	}

	public get value(): string {
		if (this._valueName === null) {
			if (this.valueObj === null) {
				this._valueName = 'null';
			} else if (Array.isArray(this.valueObj)) {
				this._valueName = `Array[${this.valueObj.length}]`;
			} else if (types.isObject(this.valueObj)) {
				this._valueName = 'Object';
			} else if (types.isString(this.valueObj)) {
				this._valueName = `"${massageValue(this.valueObj)}"`;
			} else {
				this._valueName = String(this.valueObj);
			}

			if (!this._valueName) {
				this._valueName = '';
			}
		}

		return this._valueName;
	}

	public getChildren(): debug.ITreeElement[] {
		if (!this.children) {
			if (Array.isArray(this.valueObj)) {
I
isidor 已提交
216
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
217
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
218
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
219 220 221 222 223 224 225 226 227
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

228
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
229

230
	public static allValues: { [id: string]: string } = {};
I
isidor 已提交
231
	// Use chunks to support variable paging #9537
232
	private static BASE_CHUNK_SIZE = 100;
I
isidor 已提交
233 234 235

	public valueChanged: boolean;
	private children: TPromise<debug.IExpression[]>;
236
	private _value: string;
E
Erich Gamma 已提交
237

I
isidor 已提交
238 239 240 241
	constructor(
		public reference: number,
		private id: string,
		private cacheChildren: boolean,
242 243
		public namedVariables: number,
		public indexedVariables: number,
244
		private startOfVariables = 0
I
isidor 已提交
245
	) {
I
isidor 已提交
246
		// noop
E
Erich Gamma 已提交
247 248 249
	}

	public getChildren(debugService: debug.IDebugService): TPromise<debug.IExpression[]> {
I
isidor 已提交
250 251 252 253 254 255
		if (!this.cacheChildren || !this.children) {
			const session = debugService.getActiveSession();
			// only variables with reference > 0 have children.
			if (!session || this.reference <= 0) {
				this.children = TPromise.as([]);
			} else {
256 257 258

				// Check if object has named variables, fetch them independent from indexed variables #9670
				this.children = (!!this.namedVariables ? this.fetchVariables(session, undefined, undefined, 'named') : TPromise.as([])).then(childrenArray => {
259 260 261 262 263 264 265
					// 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) {
266
						// There are a lot of children, create fake intermediate values that represent chunks #9537
267
						const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);
268
						for (let i = 0; i < numberOfChunks; i++) {
269 270 271
							const start = this.startOfVariables + i * chunkSize;
							const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);
							childrenArray.push(new Variable(this, this.reference, `[${start}..${start + count - 1}]`, '', null, count, null, true, start));
272 273 274
						}

						return childrenArray;
I
isidor 已提交
275
					}
276

277
					const start = this.getChildrenInChunks ? this.startOfVariables : undefined;
278
					const count = this.getChildrenInChunks ? this.indexedVariables : undefined;
279 280
					return this.fetchVariables(session, start, count, 'indexed')
						.then(variables => arrays.distinct(childrenArray.concat(variables), child => child.name));
281
				});
I
isidor 已提交
282
			}
E
Erich Gamma 已提交
283 284 285 286
		}

		return this.children;
	}
287 288 289 290 291

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

292 293 294 295
	public get value(): string {
		return this._value;
	}

296 297 298 299 300 301 302
	private fetchVariables(session: debug.IRawDebugSession, start: number, count: number, filter: 'indexed'|'named'): TPromise<Variable[]> {
		return session.variables({
			variablesReference: this.reference,
			start,
			count,
			filter
		}).then(response => {
303
			return response && response.body && response.body.variables ? arrays.distinct(response.body.variables.filter(v => !!v), v => v.name).map(
304
				v => new Variable(this, v.variablesReference, v.name, v.value, v.namedVariables, v.indexedVariables, v.type)
305
			) : [];
306 307 308
		}, (e: Error) => [new Variable(this, 0, null, e.message, 0, 0, null, false)]);
	}

309 310
	// 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 {
311
		return !!this.indexedVariables;
312 313
	}

314 315 316 317 318 319
	public set value(value: string) {
		this._value = massageValue(value);
		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 已提交
320 321
}

322 323
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
324

325
	public available: boolean;
326
	public type: string;
E
Erich Gamma 已提交
327

328
	constructor(public name: string, cacheChildren: boolean, id = uuid.generateUuid()) {
329
		super(0, id, cacheChildren, 0, 0);
330 331
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
332
	}
333
}
E
Erich Gamma 已提交
334

335 336
export class Variable extends ExpressionContainer implements debug.IExpression {

337 338 339
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;

I
isidor 已提交
340 341 342 343 344
	constructor(
		public parent: debug.IExpressionContainer,
		reference: number,
		public name: string,
		value: string,
345 346
		namedVariables: number,
		indexedVariables: number,
I
isidor 已提交
347 348
		public type: string = null,
		public available = true,
349
		startOfVariables = 0
I
isidor 已提交
350
	) {
351
		super(reference, `variable:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables);
352
		this.value = massageValue(value);
E
Erich Gamma 已提交
353 354 355
	}
}

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

I
isidor 已提交
358
	constructor(
I
isidor 已提交
359
		threadId: number,
I
isidor 已提交
360 361 362
		public name: string,
		reference: number,
		public expensive: boolean,
363 364
		namedVariables: number,
		indexedVariables: number
I
isidor 已提交
365
	) {
366
		super(reference, `scope:${threadId}:${name}:${reference}`, true, namedVariables, indexedVariables);
E
Erich Gamma 已提交
367 368 369 370 371 372 373
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
374 375 376 377 378 379 380 381
	constructor(
		public threadId: number,
		public frameId: number,
		public source: Source,
		public name: string,
		public lineNumber: number,
		public column: number
	) {
E
Erich Gamma 已提交
382 383 384 385
		this.scopes = null;
	}

	public getId(): string {
386
		return `stackframe:${ this.threadId }:${ this.frameId }`;
E
Erich Gamma 已提交
387 388 389 390 391
	}

	public getScopes(debugService: debug.IDebugService): TPromise<debug.IScope[]> {
		if (!this.scopes) {
			this.scopes = debugService.getActiveSession().scopes({ frameId: this.frameId }).then(response => {
392 393
				return response && response.body && response.body.scopes ?
					response.body.scopes.map(rs => new Scope(this.threadId, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables)) : [];
394
			}, err => []);
E
Erich Gamma 已提交
395 396 397 398 399 400 401 402 403
		}

		return this.scopes;
	}
}

export class Breakpoint implements debug.IBreakpoint {

	public lineNumber: number;
404
	public verified: boolean;
405
	public idFromAdapter: number;
I
isidor 已提交
406
	public message: string;
E
Erich Gamma 已提交
407 408
	private id: string;

I
isidor 已提交
409 410 411 412 413 414
	constructor(
		public source: Source,
		public desiredLineNumber: number,
		public enabled: boolean,
		public condition: string
	) {
415 416 417
		if (enabled === undefined) {
			this.enabled = true;
		}
E
Erich Gamma 已提交
418
		this.lineNumber = this.desiredLineNumber;
419
		this.verified = false;
E
Erich Gamma 已提交
420 421 422 423 424 425 426 427
		this.id = uuid.generateUuid();
	}

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

I
isidor 已提交
428 429 430
export class FunctionBreakpoint implements debug.IFunctionBreakpoint {

	private id: string;
I
isidor 已提交
431
	public verified: boolean;
432
	public idFromAdapter: number;
I
isidor 已提交
433

434
	constructor(public name: string, public enabled: boolean) {
I
isidor 已提交
435
		this.verified = false;
I
isidor 已提交
436 437 438 439 440 441 442 443
		this.id = uuid.generateUuid();
	}

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

E
Erich Gamma 已提交
444 445 446 447
export class ExceptionBreakpoint implements debug.IExceptionBreakpoint {

	private id: string;

448
	constructor(public filter: string, public label: string, public enabled: boolean) {
E
Erich Gamma 已提交
449 450 451 452 453 454 455 456
		this.id = uuid.generateUuid();
	}

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

457
export class Model implements debug.IModel {
E
Erich Gamma 已提交
458 459 460 461

	private threads: { [reference: number]: debug.IThread; };
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
462 463 464 465
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
466

I
isidor 已提交
467 468 469 470 471 472 473
	constructor(
		private breakpoints: debug.IBreakpoint[],
		private breakpointsActivated: boolean,
		private functionBreakpoints: debug.IFunctionBreakpoint[],
		private exceptionBreakpoints: debug.IExceptionBreakpoint[],
		private watchExpressions: Expression[]
	) {
E
Erich Gamma 已提交
474 475 476
		this.threads = {};
		this.replElements = [];
		this.toDispose = [];
477 478 479 480
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
481 482 483 484 485 486
	}

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

487 488 489 490 491 492 493 494 495 496 497 498
	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;
	}

499
	public get onDidChangeReplElements(): Event<void> {
500 501 502
		return this._onDidChangeREPLElements.event;
	}

E
Erich Gamma 已提交
503 504 505 506 507 508
	public getThreads(): { [reference: number]: debug.IThread; } {
		return this.threads;
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
509 510 511 512
			if (this.threads[reference]) {
				this.threads[reference].clearCallStack();
				this.threads[reference].stoppedDetails = undefined;
				this.threads[reference].stopped = false;
I
isidor 已提交
513

514 515 516
				if (removeThreads) {
					delete this.threads[reference];
				}
E
Erich Gamma 已提交
517 518
			}
		} else {
I
isidor 已提交
519 520 521
			Object.keys(this.threads).forEach(ref => {
				this.threads[ref].clearCallStack();
				this.threads[ref].stoppedDetails = undefined;
522
				this.threads[ref].stopped = false;
I
isidor 已提交
523 524
			});

E
Erich Gamma 已提交
525 526
			if (removeThreads) {
				this.threads = {};
527
				ExpressionContainer.allValues = {};
E
Erich Gamma 已提交
528 529 530
			}
		}

531
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
532 533 534 535 536 537
	}

	public getBreakpoints(): debug.IBreakpoint[] {
		return this.breakpoints;
	}

I
isidor 已提交
538 539 540 541
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
542 543 544 545
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

546
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
547
		if (data) {
548 549 550 551
			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);
			});
552 553 554
		}
	}

E
Erich Gamma 已提交
555 556 557 558
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

559 560
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
561
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
562 563
	}

564
	public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
565 566
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
			new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition)));
567
		this.breakpointsActivated = true;
568
		this._onDidChangeBreakpoints.fire();
569
	}
E
Erich Gamma 已提交
570

571 572
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
573
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
574 575
	}

I
isidor 已提交
576
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
577 578 579
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
580
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
581
				bp.verified = bpData.verified;
582
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
583
				bp.message = bpData.message;
584 585
			}
		});
586
		this._onDidChangeBreakpoints.fire();
587 588
	}

589 590
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
591 592 593
		if (element instanceof Breakpoint && !element.enabled) {
			var breakpoint = <Breakpoint> element;
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
594
			breakpoint.verified = false;
E
Erich Gamma 已提交
595 596
		}

597
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
598 599
	}

600
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
601
		this.breakpoints.forEach(bp => {
602 603
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
604
				bp.lineNumber = bp.desiredLineNumber;
605
				bp.verified = false;
E
Erich Gamma 已提交
606 607
			}
		});
608 609
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
610

611
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
612 613
	}

I
isidor 已提交
614 615
	public addFunctionBreakpoint(functionName: string): void {
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true));
616
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
617 618
	}

619
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number } }): void {
I
isidor 已提交
620 621 622 623 624
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
625
				fbp.idFromAdapter = fbpData.id;
I
isidor 已提交
626 627 628
			}
		});

629
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
630 631
	}

632
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
633
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
634
		this._onDidChangeBreakpoints.fire();
635 636
	}

E
Erich Gamma 已提交
637 638 639 640
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

I
isidor 已提交
641
	public addReplExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
642
		const expression = new Expression(name, true);
643
		this.addReplElements([expression]);
644 645
		return evaluateExpression(session, stackFrame, expression, 'repl')
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
646 647
	}

648
	public logToRepl(value: string | { [key: string]: any }, severity?: severity): void {
E
Erich Gamma 已提交
649 650 651
		let elements:OutputElement[] = [];
		let previousOutput = this.replElements.length && (<ValueOutputElement>this.replElements[this.replElements.length - 1]);

I
isidor 已提交
652
		// string message
E
Erich Gamma 已提交
653 654 655 656
		if (typeof value === 'string') {
			if (value && value.trim() && previousOutput && previousOutput.value === value && previousOutput.severity === severity) {
				previousOutput.counter++; // we got the same output (but not an empty string when trimmed) so we just increment the counter
			} else {
I
isidor 已提交
657
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
658
				lines.forEach((line, index) => {
I
isidor 已提交
659
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
660 661 662 663
				});
			}
		}

I
isidor 已提交
664
		// key-value output
E
Erich Gamma 已提交
665
		else {
666
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
667 668 669
		}

		if (elements.length) {
670
			this.addReplElements(elements);
E
Erich Gamma 已提交
671
		}
I
isidor 已提交
672
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
673 674 675
	}

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

		if (groupTogether) {
682 683 684 685 686
			// append to previous line if same group
			previousOutput.value += lines.shift();
		} else if (previousOutput && previousOutput.value === '') {
			// remove potential empty lines between different output types
			this.replElements.pop();
E
Erich Gamma 已提交
687 688 689 690
		}

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

694
		this.addReplElements(elements);
695
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
696 697
	}

698 699 700 701 702 703 704
	private addReplElements(newElements: debug.ITreeElement[]): void {
		this.replElements.push(...newElements);
		if (this.replElements.length > MAX_REPL_LENGTH) {
			this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
		}
	}

705
	public removeReplExpressions(): void {
706 707
		if (this.replElements.length > 0) {
			this.replElements = [];
708
			this._onDidChangeREPLElements.fire();
709
		}
E
Erich Gamma 已提交
710 711 712 713 714 715
	}

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

I
isidor 已提交
716
	public addWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
717
		const we = new Expression(name, false);
E
Erich Gamma 已提交
718 719
		this.watchExpressions.push(we);
		if (!name) {
720
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
721
			return TPromise.as(null);
E
Erich Gamma 已提交
722 723 724 725 726
		}

		return this.evaluateWatchExpressions(session, stackFrame, we.getId());
	}

I
isidor 已提交
727
	public renameWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
728
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
729 730
		if (filtered.length === 1) {
			filtered[0].name = newName;
I
isidor 已提交
731
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
732
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
733 734 735
			});
		}

A
Alex Dima 已提交
736
		return TPromise.as(null);
E
Erich Gamma 已提交
737 738
	}

I
isidor 已提交
739
	public evaluateWatchExpressions(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
740
		if (id) {
I
isidor 已提交
741
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
742
			if (filtered.length !== 1) {
A
Alex Dima 已提交
743
				return TPromise.as(null);
E
Erich Gamma 已提交
744 745
			}

I
isidor 已提交
746
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
747
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
748 749 750
			});
		}

I
isidor 已提交
751
		return TPromise.join(this.watchExpressions.map(we => evaluateExpression(session, stackFrame, we, 'watch'))).then(() => {
752
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
753 754 755 756 757 758 759 760 761 762
		});
	}

	public clearWatchExpressionValues(): void {
		this.watchExpressions.forEach(we => {
			we.value = Expression.DEFAULT_VALUE;
			we.available = false;
			we.reference = 0;
		});

763
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
764 765
	}

766
	public removeWatchExpressions(id: string = null): void {
767
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
768
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
769 770
	}

I
isidor 已提交
771
	public sourceIsUnavailable(source: Source): void {
772
		Object.keys(this.threads).forEach(key => {
773 774 775 776 777 778 779
			if (this.threads[key].getCachedCallStack()) {
				this.threads[key].getCachedCallStack().forEach(stackFrame => {
					if (stackFrame.source.uri.toString() === source.uri.toString()) {
						stackFrame.source.available = false;
					}
				});
			}
780 781
		});

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

	public rawUpdate(data: debug.IRawModelUpdate): void {
I
isidor 已提交
786 787
		if (data.thread && !this.threads[data.threadId]) {
			// A new thread came in, initialize it.
788
			this.threads[data.threadId] = new Thread(data.thread.name, data.thread.id);
E
Erich Gamma 已提交
789 790
		}

791 792 793
		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
I
isidor 已提交
794 795 796 797 798
			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 已提交
799
					this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
I
isidor 已提交
800
					this.threads[ref].stopped = true;
801
					this.threads[ref].clearCallStack();
I
isidor 已提交
802 803 804 805 806 807
				});
			} 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;
808
			}
E
Erich Gamma 已提交
809 810
		}

811
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
812 813 814
	}

	public dispose(): void {
J
Joao Moreno 已提交
815
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
816 817
	}
}