debugModel.ts 23.5 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');
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
function resolveChildren(debugService: debug.IDebugService, parent: debug.IExpressionContainer): TPromise<Variable[]> {
I
isidor 已提交
22 23
	const session = debugService.getActiveSession();
	// only variables with reference > 0 have children.
E
Erich Gamma 已提交
24 25 26 27
	if (!session || parent.reference <= 0) {
		return TPromise.as([]);
	}

28
	return session.variables({ variablesReference: parent.reference }).then(response => {
29
		return arrays.distinct(response.body.variables.filter(v => !!v), v => v.name).map(
30
			v => new Variable(parent, v.variablesReference, v.name, v.value, v.type)
E
Erich Gamma 已提交
31
		);
32
	}, (e: Error) => [new Variable(parent, 0, null, e.message, null, false)]);
E
Erich Gamma 已提交
33 34 35 36 37 38
}

function massageValue(value: string): string {
	return value ? value.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') : value;
}

I
isidor 已提交
39 40 41 42 43
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 已提交
44
		return TPromise.as(expression);
I
isidor 已提交
45 46 47 48 49 50 51
	}

	return session.evaluate({
		expression: expression.name,
		frameId: stackFrame ? stackFrame.frameId : undefined,
		context
	}).then(response => {
I
isidor 已提交
52 53 54 55 56
		expression.available = !!response.body;
		if (response.body) {
			expression.value = response.body.result;
			expression.reference = response.body.variablesReference;
		}
I
isidor 已提交
57 58 59 60 61 62 63 64 65 66 67

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

		return expression;
	});
}

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

71 72 73
export function getFullExpressionName(expression: debug.IExpression, sessionType: string): string {
	let names = [expression.name];
	if (expression instanceof Variable) {
I
isidor 已提交
74
		let v = (<Variable> expression).parent;
75 76 77 78 79 80 81 82 83 84 85
		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 已提交
86
		} else if (arrayElementSyntax.test(name) || (sessionType === 'node' && !notPropertySyntax.test(name))) {
I
isidor 已提交
87
			// use safe way to access node properties a['property_name']. Also handles array elements.
88 89 90 91 92 93 94 95 96
			result = name && name.indexOf('[') === 0 ? `${ result }${ name }` : `${ result }['${ name }']`;
		} else {
			result = `${ result }.${ name }`;
		}
	});

	return result;
}

E
Erich Gamma 已提交
97
export class Thread implements debug.IThread {
98 99
	private promisedCallStack: TPromise<debug.IStackFrame[]>;
	private cachedCallStack: debug.IStackFrame[];
I
isidor 已提交
100
	public stoppedDetails: debug.IRawStoppedDetails;
101
	public stopped: boolean;
E
Erich Gamma 已提交
102

I
isidor 已提交
103
	constructor(public name: string, public threadId: number) {
104
		this.promisedCallStack = undefined;
I
isidor 已提交
105
		this.stoppedDetails = undefined;
106 107
		this.cachedCallStack = undefined;
		this.stopped = false;
E
Erich Gamma 已提交
108 109 110 111 112
	}

	public getId(): string {
		return `thread:${ this.name }:${ this.threadId }`;
	}
113 114 115 116 117 118 119 120 121 122

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

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

I
isidor 已提交
123
	public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
124 125 126
		if (!this.stopped) {
			return TPromise.as([]);
		}
I
isidor 已提交
127

128
		if (!this.promisedCallStack) {
I
isidor 已提交
129 130 131 132 133 134 135 136 137
			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;
			}));
138 139 140 141 142
		}

		return this.promisedCallStack;
	}

I
isidor 已提交
143
	private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise<debug.IStackFrame[]> {
144
		let session = debugService.getActiveSession();
I
isidor 已提交
145
		return session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
146
			this.stoppedDetails.totalFrames = response.body.totalFrames;
147 148
			return response.body.stackFrames.map((rsf, level) => {
				if (!rsf) {
I
isidor 已提交
149
					return new StackFrame(this.threadId, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
150 151
				}

I
isidor 已提交
152
				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);
153
			});
154 155 156
		}, (err: Error) => {
			this.stoppedDetails.framesErrorMessage = err.message;
			return [];
157 158
		});
	}
E
Erich Gamma 已提交
159 160 161
}

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

164
	constructor(private id = OutputElement.ID_COUNTER++) {
I
isidor 已提交
165
		// noop
E
Erich Gamma 已提交
166 167 168
	}

	public getId(): string {
I
isidor 已提交
169
		return `outputelement:${ this.id }`;
E
Erich Gamma 已提交
170 171 172 173 174
	}
}

export class ValueOutputElement extends OutputElement {

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

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 已提交
187 188
	constructor(public key: string, public valueObj: any, public annotation?: string) {
		super();
E
Erich Gamma 已提交
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 216 217

		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 已提交
218
				this.children = (<any[]>this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map((v, index) => new KeyValueOutputElement(String(index), v, null));
E
Erich Gamma 已提交
219
			} else if (types.isObject(this.valueObj)) {
I
isidor 已提交
220
				this.children = Object.getOwnPropertyNames(this.valueObj).slice(0, KeyValueOutputElement.MAX_CHILDREN).map(key => new KeyValueOutputElement(key, this.valueObj[key], null));
E
Erich Gamma 已提交
221 222 223 224 225 226 227 228 229
			} else {
				this.children = [];
			}
		}

		return this.children;
	}
}

230
export abstract class ExpressionContainer implements debug.IExpressionContainer {
E
Erich Gamma 已提交
231 232

	private children: TPromise<debug.IExpression[]>;
233 234
	public valueChanged: boolean;
	public static allValues: { [id: string]: string } = {};
235
	private _value: string;
E
Erich Gamma 已提交
236

237
	constructor(public reference: number, private id: string, private cacheChildren: boolean) {
E
Erich Gamma 已提交
238 239 240 241 242 243 244 245 246 247 248 249 250
		this.children = null;
	}

	public getChildren(debugService: debug.IDebugService): TPromise<debug.IExpression[]> {
		if (!this.cacheChildren) {
			return resolveChildren(debugService, this);
		}
		if (!this.children) {
			this.children = resolveChildren(debugService, this);
		}

		return this.children;
	}
251 252 253 254 255

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

256 257 258 259 260 261 262 263 264 265
	public get value(): string {
		return this._value;
	}

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

268 269
export class Expression extends ExpressionContainer implements debug.IExpression {
	static DEFAULT_VALUE = 'not available';
E
Erich Gamma 已提交
270

271
	public available: boolean;
E
Erich Gamma 已提交
272

273 274 275 276
	constructor(public name: string, cacheChildren: boolean, id = uuid.generateUuid()) {
		super(0, id, cacheChildren);
		this.value = Expression.DEFAULT_VALUE;
		this.available = false;
E
Erich Gamma 已提交
277
	}
278
}
E
Erich Gamma 已提交
279

280 281
export class Variable extends ExpressionContainer implements debug.IExpression {

282 283 284
	// Used to show the error message coming from the adapter when setting the value #7807
	public errorMessage: string;

285
	constructor(public parent: debug.IExpressionContainer, reference: number, public name: string, value: string, public type: string = null, public available = true) {
286 287
		super(reference, `variable:${ parent.getId() }:${ name }`, true);
		this.value = massageValue(value);
E
Erich Gamma 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
	}
}

export class Scope implements debug.IScope {

	private children: TPromise<Variable[]>;

	constructor(private threadId: number, public name: string, public reference: number, public expensive: boolean) {
		this.children = null;
	}

	public getId(): string {
		return `scope:${ this.threadId }:${ this.name }:${ this.reference }`;
	}

	public getChildren(debugService: debug.IDebugService): TPromise<Variable[]> {
		if (!this.children) {
			this.children = resolveChildren(debugService, this);
		}

		return this.children;
	}
}

export class StackFrame implements debug.IStackFrame {

	private scopes: TPromise<Scope[]>;

I
isidor 已提交
316
	constructor(public threadId: number, public frameId: number, public source: Source, public name: string, public lineNumber: number, public column: number) {
E
Erich Gamma 已提交
317 318 319 320
		this.scopes = null;
	}

	public getId(): string {
321
		return `stackframe:${ this.threadId }:${ this.frameId }`;
E
Erich Gamma 已提交
322 323 324 325 326 327
	}

	public getScopes(debugService: debug.IDebugService): TPromise<debug.IScope[]> {
		if (!this.scopes) {
			this.scopes = debugService.getActiveSession().scopes({ frameId: this.frameId }).then(response => {
				return response.body.scopes.map(rs => new Scope(this.threadId, rs.name, rs.variablesReference, rs.expensive));
328
			}, err => []);
E
Erich Gamma 已提交
329 330 331 332 333 334 335 336 337
		}

		return this.scopes;
	}
}

export class Breakpoint implements debug.IBreakpoint {

	public lineNumber: number;
338
	public verified: boolean;
339
	public idFromAdapter: number;
I
isidor 已提交
340
	public message: string;
E
Erich Gamma 已提交
341 342
	private id: string;

I
isidor 已提交
343
	constructor(public source: Source, public desiredLineNumber: number, public enabled: boolean, public condition: string) {
344 345 346
		if (enabled === undefined) {
			this.enabled = true;
		}
E
Erich Gamma 已提交
347
		this.lineNumber = this.desiredLineNumber;
348
		this.verified = false;
E
Erich Gamma 已提交
349 350 351 352 353 354 355 356
		this.id = uuid.generateUuid();
	}

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

I
isidor 已提交
357 358 359
export class FunctionBreakpoint implements debug.IFunctionBreakpoint {

	private id: string;
I
isidor 已提交
360
	public verified: boolean;
361
	public idFromAdapter: number;
I
isidor 已提交
362

363
	constructor(public name: string, public enabled: boolean) {
I
isidor 已提交
364
		this.verified = false;
I
isidor 已提交
365 366 367 368 369 370 371 372
		this.id = uuid.generateUuid();
	}

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

E
Erich Gamma 已提交
373 374 375 376
export class ExceptionBreakpoint implements debug.IExceptionBreakpoint {

	private id: string;

377
	constructor(public filter: string, public label: string, public enabled: boolean) {
E
Erich Gamma 已提交
378 379 380 381 382 383 384 385
		this.id = uuid.generateUuid();
	}

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

386
export class Model implements debug.IModel {
E
Erich Gamma 已提交
387 388 389 390

	private threads: { [reference: number]: debug.IThread; };
	private toDispose: lifecycle.IDisposable[];
	private replElements: debug.ITreeElement[];
391 392 393 394
	private _onDidChangeBreakpoints: Emitter<void>;
	private _onDidChangeCallStack: Emitter<void>;
	private _onDidChangeWatchExpressions: Emitter<debug.IExpression>;
	private _onDidChangeREPLElements: Emitter<void>;
E
Erich Gamma 已提交
395

I
isidor 已提交
396
	constructor(private breakpoints: debug.IBreakpoint[], private breakpointsActivated: boolean, private functionBreakpoints: debug.IFunctionBreakpoint[],
E
Erich Gamma 已提交
397 398 399 400 401
		private exceptionBreakpoints: debug.IExceptionBreakpoint[], private watchExpressions: Expression[]) {

		this.threads = {};
		this.replElements = [];
		this.toDispose = [];
402 403 404 405
		this._onDidChangeBreakpoints = new Emitter<void>();
		this._onDidChangeCallStack = new Emitter<void>();
		this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
		this._onDidChangeREPLElements = new Emitter<void>();
E
Erich Gamma 已提交
406 407 408 409 410 411
	}

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

412 413 414 415 416 417 418 419 420 421 422 423
	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;
	}

424
	public get onDidChangeReplElements(): Event<void> {
425 426 427
		return this._onDidChangeREPLElements.event;
	}

E
Erich Gamma 已提交
428 429 430 431 432 433
	public getThreads(): { [reference: number]: debug.IThread; } {
		return this.threads;
	}

	public clearThreads(removeThreads: boolean, reference: number = undefined): void {
		if (reference) {
434 435 436 437
			if (this.threads[reference]) {
				this.threads[reference].clearCallStack();
				this.threads[reference].stoppedDetails = undefined;
				this.threads[reference].stopped = false;
I
isidor 已提交
438

439 440 441
				if (removeThreads) {
					delete this.threads[reference];
				}
E
Erich Gamma 已提交
442 443
			}
		} else {
I
isidor 已提交
444 445 446
			Object.keys(this.threads).forEach(ref => {
				this.threads[ref].clearCallStack();
				this.threads[ref].stoppedDetails = undefined;
447
				this.threads[ref].stopped = false;
I
isidor 已提交
448 449
			});

E
Erich Gamma 已提交
450 451
			if (removeThreads) {
				this.threads = {};
452
				ExpressionContainer.allValues = {};
E
Erich Gamma 已提交
453 454 455
			}
		}

456
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
457 458 459 460 461 462
	}

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

I
isidor 已提交
463 464 465 466
	public getFunctionBreakpoints(): debug.IFunctionBreakpoint[] {
		return this.functionBreakpoints;
	}

E
Erich Gamma 已提交
467 468 469 470
	public getExceptionBreakpoints(): debug.IExceptionBreakpoint[] {
		return this.exceptionBreakpoints;
	}

471
	public setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void {
472
		if (data) {
473 474 475 476
			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);
			});
477 478 479
		}
	}

E
Erich Gamma 已提交
480 481 482 483
	public areBreakpointsActivated(): boolean {
		return this.breakpointsActivated;
	}

484 485
	public setBreakpointsActivated(activated: boolean): void {
		this.breakpointsActivated = activated;
486
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
487 488
	}

489
	public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
490 491
		this.breakpoints = this.breakpoints.concat(rawData.map(rawBp =>
			new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition)));
492
		this.breakpointsActivated = true;
493
		this._onDidChangeBreakpoints.fire();
494
	}
E
Erich Gamma 已提交
495

496 497
	public removeBreakpoints(toRemove: debug.IBreakpoint[]): void {
		this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
498
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
499 500
	}

I
isidor 已提交
501
	public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
502 503 504
		this.breakpoints.forEach(bp => {
			const bpData = data[bp.getId()];
			if (bpData) {
505
				bp.lineNumber = bpData.line ? bpData.line : bp.lineNumber;
506
				bp.verified = bpData.verified;
507
				bp.idFromAdapter = bpData.id;
I
isidor 已提交
508
				bp.message = bpData.message;
509 510
			}
		});
511
		this._onDidChangeBreakpoints.fire();
512 513
	}

514 515
	public setEnablement(element: debug.IEnablement, enable: boolean): void {
		element.enabled = enable;
E
Erich Gamma 已提交
516 517 518
		if (element instanceof Breakpoint && !element.enabled) {
			var breakpoint = <Breakpoint> element;
			breakpoint.lineNumber = breakpoint.desiredLineNumber;
519
			breakpoint.verified = false;
E
Erich Gamma 已提交
520 521
		}

522
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
523 524
	}

525
	public enableOrDisableAllBreakpoints(enable: boolean): void {
E
Erich Gamma 已提交
526
		this.breakpoints.forEach(bp => {
527 528
			bp.enabled = enable;
			if (!enable) {
E
Erich Gamma 已提交
529
				bp.lineNumber = bp.desiredLineNumber;
530
				bp.verified = false;
E
Erich Gamma 已提交
531 532
			}
		});
533 534
		this.exceptionBreakpoints.forEach(ebp => ebp.enabled = enable);
		this.functionBreakpoints.forEach(fbp => fbp.enabled = enable);
E
Erich Gamma 已提交
535

536
		this._onDidChangeBreakpoints.fire();
E
Erich Gamma 已提交
537 538
	}

I
isidor 已提交
539 540
	public addFunctionBreakpoint(functionName: string): void {
		this.functionBreakpoints.push(new FunctionBreakpoint(functionName, true));
541
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
542 543
	}

544
	public updateFunctionBreakpoints(data: { [id: string]: { name?: string, verified?: boolean; id?: number } }): void {
I
isidor 已提交
545 546 547 548 549
		this.functionBreakpoints.forEach(fbp => {
			const fbpData = data[fbp.getId()];
			if (fbpData) {
				fbp.name = fbpData.name || fbp.name;
				fbp.verified = fbpData.verified;
550
				fbp.idFromAdapter = fbpData.id;
I
isidor 已提交
551 552 553
			}
		});

554
		this._onDidChangeBreakpoints.fire();
I
isidor 已提交
555 556
	}

557
	public removeFunctionBreakpoints(id?: string): void {
I
isidor 已提交
558
		this.functionBreakpoints = id ? this.functionBreakpoints.filter(fbp => fbp.getId() !== id) : [];
559
		this._onDidChangeBreakpoints.fire();
560 561
	}

E
Erich Gamma 已提交
562 563 564 565
	public getReplElements(): debug.ITreeElement[] {
		return this.replElements;
	}

I
isidor 已提交
566
	public addReplExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
567
		const expression = new Expression(name, true);
568
		this.addReplElements([expression]);
569 570
		return evaluateExpression(session, stackFrame, expression, 'repl')
			.then(() => this._onDidChangeREPLElements.fire());
E
Erich Gamma 已提交
571 572
	}

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

I
isidor 已提交
577
		// string message
E
Erich Gamma 已提交
578 579 580 581
		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 已提交
582
				let lines = value.trim().split('\n');
E
Erich Gamma 已提交
583
				lines.forEach((line, index) => {
I
isidor 已提交
584
					elements.push(new ValueOutputElement(line, severity));
E
Erich Gamma 已提交
585 586 587 588
				});
			}
		}

I
isidor 已提交
589
		// key-value output
E
Erich Gamma 已提交
590
		else {
591
			elements.push(new KeyValueOutputElement((<any>value).prototype, value, nls.localize('snapshotObj', "Only primitive values are shown for this object.")));
E
Erich Gamma 已提交
592 593 594
		}

		if (elements.length) {
595
			this.addReplElements(elements);
E
Erich Gamma 已提交
596
		}
I
isidor 已提交
597
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
598 599 600
	}

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

		if (groupTogether) {
607 608 609 610 611
			// 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 已提交
612 613 614 615
		}

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

619
		this.addReplElements(elements);
620
		this._onDidChangeREPLElements.fire();
E
Erich Gamma 已提交
621 622
	}

623 624 625 626 627 628 629
	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);
		}
	}

630
	public removeReplExpressions(): void {
631 632
		if (this.replElements.length > 0) {
			this.replElements = [];
633
			this._onDidChangeREPLElements.fire();
634
		}
E
Erich Gamma 已提交
635 636 637 638 639 640
	}

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

I
isidor 已提交
641
	public addWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, name: string): TPromise<void> {
I
isidor 已提交
642
		const we = new Expression(name, false);
E
Erich Gamma 已提交
643 644
		this.watchExpressions.push(we);
		if (!name) {
645
			this._onDidChangeWatchExpressions.fire(we);
A
Alex Dima 已提交
646
			return TPromise.as(null);
E
Erich Gamma 已提交
647 648 649 650 651
		}

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

I
isidor 已提交
652
	public renameWatchExpression(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string, newName: string): TPromise<void> {
I
isidor 已提交
653
		const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
654 655
		if (filtered.length === 1) {
			filtered[0].name = newName;
I
isidor 已提交
656
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
657
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
658 659 660
			});
		}

A
Alex Dima 已提交
661
		return TPromise.as(null);
E
Erich Gamma 已提交
662 663
	}

I
isidor 已提交
664
	public evaluateWatchExpressions(session: debug.IRawDebugSession, stackFrame: debug.IStackFrame, id: string = null): TPromise<void> {
E
Erich Gamma 已提交
665
		if (id) {
I
isidor 已提交
666
			const filtered = this.watchExpressions.filter(we => we.getId() === id);
E
Erich Gamma 已提交
667
			if (filtered.length !== 1) {
A
Alex Dima 已提交
668
				return TPromise.as(null);
E
Erich Gamma 已提交
669 670
			}

I
isidor 已提交
671
			return evaluateExpression(session, stackFrame, filtered[0], 'watch').then(() => {
672
				this._onDidChangeWatchExpressions.fire(filtered[0]);
E
Erich Gamma 已提交
673 674 675
			});
		}

I
isidor 已提交
676
		return TPromise.join(this.watchExpressions.map(we => evaluateExpression(session, stackFrame, we, 'watch'))).then(() => {
677
			this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
678 679 680 681 682 683 684 685 686 687
		});
	}

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

688
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
689 690
	}

691
	public removeWatchExpressions(id: string = null): void {
692
		this.watchExpressions = id ? this.watchExpressions.filter(we => we.getId() !== id) : [];
693
		this._onDidChangeWatchExpressions.fire();
E
Erich Gamma 已提交
694 695
	}

I
isidor 已提交
696
	public sourceIsUnavailable(source: Source): void {
697
		Object.keys(this.threads).forEach(key => {
698 699 700 701 702 703 704
			if (this.threads[key].getCachedCallStack()) {
				this.threads[key].getCachedCallStack().forEach(stackFrame => {
					if (stackFrame.source.uri.toString() === source.uri.toString()) {
						stackFrame.source.available = false;
					}
				});
			}
705 706
		});

707
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
708 709 710
	}

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

716 717 718
		if (data.stoppedDetails) {
			// Set the availability of the threads' callstacks depending on
			// whether the thread is stopped or not
I
isidor 已提交
719 720 721 722 723
			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 已提交
724
					this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
I
isidor 已提交
725
					this.threads[ref].stopped = true;
726
					this.threads[ref].clearCallStack();
I
isidor 已提交
727 728 729 730 731 732
				});
			} 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;
733
			}
E
Erich Gamma 已提交
734 735
		}

736
		this._onDidChangeCallStack.fire();
E
Erich Gamma 已提交
737 738 739 740 741 742
	}

	public dispose(): void {
		this.threads = null;
		this.breakpoints = null;
		this.exceptionBreakpoints = null;
I
isidor 已提交
743
		this.functionBreakpoints = null;
E
Erich Gamma 已提交
744 745
		this.watchExpressions = null;
		this.replElements = null;
J
Joao Moreno 已提交
746
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
747 748
	}
}