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

I
isidor 已提交
6
import { 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 11 12 13 14
import uuid = require('vs/base/common/uuid');
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 已提交
15
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
E
Erich Gamma 已提交
16

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

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

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

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

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

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

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

		return expression;
	});
}

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

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

	return result;
}

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

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

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

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

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

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

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

		return this.promisedCallStack;
	}

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

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

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

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

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

export class ValueOutputElement extends OutputElement {

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

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

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

		return this.children;
	}
}

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

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

236
	constructor(public reference: number, private id: string, private cacheChildren: boolean) {
E
Erich Gamma 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249
		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;
	}
250 251 252 253 254

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

255 256 257 258 259 260 261 262 263 264
	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 已提交
265 266
}

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

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

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

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

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

284 285 286
	constructor(public parent: debug.IExpressionContainer, reference: number, public name: string, value: string, public available = true) {
		super(reference, `variable:${ parent.getId() }:${ name }`, true);
		this.value = massageValue(value);
E
Erich Gamma 已提交
287 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
	}
}

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 已提交
315
	constructor(public threadId: number, public frameId: number, public source: Source, public name: string, public lineNumber: number, public column: number) {
E
Erich Gamma 已提交
316 317 318 319
		this.scopes = null;
	}

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

	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));
327
			}, err => []);
E
Erich Gamma 已提交
328 329 330 331 332 333 334 335 336
		}

		return this.scopes;
	}
}

export class Breakpoint implements debug.IBreakpoint {

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

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

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

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

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

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

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

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

	private id: string;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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