diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 63f671e4a84..f859ff040d4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -205,7 +205,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb public $appendDebugConsole(value: string): Thenable { // Use warning as severity to get the orange color for messages coming from the debug extension - this.debugService.logToRepl(value, severity.Warning); + const session = this.debugService.getViewModel().focusedSession; + if (session) { + session.appendToRepl(value, severity.Warning); + } return TPromise.wrap(undefined); } diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 2fed9479bfa..6e53111f8ee 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -239,9 +239,7 @@ export class RestartAction extends AbstractDebugAction { return this.startAction.run(); } - if (this.debugService.getModel().getSessions().length <= 1) { - this.debugService.removeReplExpressions(); - } + session.removeReplExpressions(); return this.debugService.restartSession(session); } @@ -692,8 +690,11 @@ export class ClearReplAction extends AbstractDebugAction { } public run(): TPromise { - this.debugService.removeReplExpressions(); - aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared")); + const session = this.debugService.getViewModel().focusedSession; + if (session) { + session.removeReplExpressions(); + aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared")); + } // focus back to repl return this.panelService.openPanel(REPL_ID, true); @@ -706,7 +707,6 @@ export class ToggleReplAction extends TogglePanelAction { private toDispose: lifecycle.IDisposable[]; constructor(id: string, label: string, - @IDebugService private debugService: IDebugService, @IPartService partService: IPartService, @IPanelService panelService: IPanelService ) { @@ -716,12 +716,6 @@ export class ToggleReplAction extends TogglePanelAction { } private registerListeners(): void { - this.toDispose.push(this.debugService.getModel().onDidChangeReplElements(() => { - if (!this.isReplVisible()) { - this.class = 'debug-action toggle-repl notification'; - this.tooltip = nls.localize('unreadOutput', "New Output in Debug Console"); - } - })); this.toDispose.push(this.panelService.onDidPanelOpen(panel => { if (panel.getId() === REPL_ID) { this.class = 'debug-action toggle-repl'; @@ -730,11 +724,6 @@ export class ToggleReplAction extends TogglePanelAction { })); } - private isReplVisible(): boolean { - const panel = this.panelService.getActivePanel(); - return panel && panel.getId() === REPL_ID; - } - public dispose(): void { super.dispose(); this.toDispose = lifecycle.dispose(this.toDispose); diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts index c34c2c81431..ca7e8ac1021 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorActions.ts @@ -159,7 +159,9 @@ class SelectionToReplAction extends EditorAction { const panelService = accessor.get(IPanelService); const text = editor.getModel().getValueInRange(editor.getSelection()); - return debugService.addReplExpression(text) + const viewModel = debugService.getViewModel(); + const session = viewModel.focusedSession; + return session.addReplExpression(viewModel.focusedStackFrame, text) .then(() => panelService.openPanel(REPL_ID, true)) .then(_ => void 0); } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 188ae7ac40b..44f82c5c4cc 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -152,9 +152,21 @@ export interface IDebugSession extends ITreeElement { setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }): void; rawUpdate(data: IRawModelUpdate): void; + getThread(threadId: number): IThread; + getAllThreads(): ReadonlyArray; + clearThreads(removeThreads: boolean, reference?: number): void; + + getReplElements(): ReadonlyArray; + + removeReplExpressions(): void; + addReplExpression(stackFrame: IStackFrame, name: string): TPromise; + appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void; + logToRepl(sev: severity, args: any[], frame?: { uri: uri, line: number, column: number }); + // session events readonly onDidEndAdapter: Event; readonly onDidChangeState: Event; + readonly onDidChangeReplElements: Event; // DA capabilities readonly capabilities: DebugProtocol.Capabilities; @@ -200,10 +212,6 @@ export interface IDebugSession extends ITreeElement { setVariable(variablesReference: number, name: string, value: string): TPromise; loadSource(resource: uri): TPromise; getLoadedSources(): TPromise; - - getThread(threadId: number): IThread; - getAllThreads(): ReadonlyArray; - clearThreads(removeThreads: boolean, reference?: number): void; } export interface IThread extends ITreeElement { @@ -374,12 +382,10 @@ export interface IDebugModel extends ITreeElement { getFunctionBreakpoints(): ReadonlyArray; getExceptionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; - getReplElements(): ReadonlyArray; onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; onDidChangeWatchExpressions: Event; - onDidChangeReplElements: Event; } /** @@ -721,21 +727,6 @@ export interface IDebugService { */ sendAllBreakpoints(session?: IDebugSession): TPromise; - /** - * Adds a new expression to the repl. - */ - addReplExpression(name: string): TPromise; - - /** - * Removes all repl expressions. - */ - removeReplExpressions(): void; - - /** - * Appends the passed string to the debug repl. - */ - logToRepl(value: string | IExpression, sev?: severity, source?: IReplElementSource): void; - /** * Adds a new watch expression and evaluates it against the debug adapter. */ diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 2653d0ea6af..a5f38180727 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -25,8 +25,6 @@ import { sep } from 'vs/base/common/paths'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -const MAX_REPL_LENGTH = 10000; - export abstract class AbstractReplElement implements IReplElement { private static ID_COUNTER = 0; @@ -728,13 +726,11 @@ export class DebugModel implements IDebugModel { private sessions: IDebugSession[]; private toDispose: lifecycle.IDisposable[]; - private replElements: IReplElement[]; private schedulers = new Map(); private breakpointsSessionId: string; private readonly _onDidChangeBreakpoints: Emitter; private readonly _onDidChangeCallStack: Emitter; private readonly _onDidChangeWatchExpressions: Emitter; - private readonly _onDidChangeREPLElements: Emitter; constructor( private breakpoints: Breakpoint[], @@ -745,12 +741,10 @@ export class DebugModel implements IDebugModel { private textFileService: ITextFileService ) { this.sessions = []; - this.replElements = []; this.toDispose = []; this._onDidChangeBreakpoints = new Emitter(); this._onDidChangeCallStack = new Emitter(); this._onDidChangeWatchExpressions = new Emitter(); - this._onDidChangeREPLElements = new Emitter(); } public getId(): string { @@ -782,10 +776,6 @@ export class DebugModel implements IDebugModel { return this._onDidChangeWatchExpressions.event; } - public get onDidChangeReplElements(): Event { - return this._onDidChangeREPLElements.event; - } - public rawUpdate(data: IRawModelUpdate): void { let session = this.sessions.filter(p => p.getId() === data.sessionId).pop(); if (session) { @@ -1015,53 +1005,6 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed: removed }); } - public getReplElements(): IReplElement[] { - return this.replElements; - } - - public addReplExpression(session: IDebugSession, stackFrame: IStackFrame, name: string): TPromise { - const expression = new Expression(name); - this.addReplElements([expression]); - return expression.evaluate(session, stackFrame, 'repl') - .then(() => this._onDidChangeREPLElements.fire()); - } - - public appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void { - if (typeof data === 'string') { - const previousElement = this.replElements.length && (this.replElements[this.replElements.length - 1] as SimpleReplElement); - - const toAdd = data.split('\n').map((line, index) => new SimpleReplElement(line, severity, index === 0 ? source : undefined)); - if (previousElement && previousElement.value === '') { - // remove potential empty lines between different repl types - this.replElements.pop(); - } else if (previousElement instanceof SimpleReplElement && severity === previousElement.severity && toAdd.length && toAdd[0].sourceData === previousElement.sourceData) { - previousElement.value += toAdd.shift().value; - } - this.addReplElements(toAdd); - } else { - // TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression - (data).severity = severity; - (data).sourceData = source; - this.addReplElements([data]); - } - - this._onDidChangeREPLElements.fire(); - } - - private addReplElements(newElements: IReplElement[]): void { - this.replElements.push(...newElements); - if (this.replElements.length > MAX_REPL_LENGTH) { - this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); - } - } - - public removeReplExpressions(): void { - if (this.replElements.length > 0) { - this.replElements = []; - this._onDidChangeREPLElements.fire(); - } - } - public getWatchExpressions(): Expression[] { return this.watchExpressions; } diff --git a/src/vs/workbench/parts/debug/common/replModel.ts b/src/vs/workbench/parts/debug/common/replModel.ts new file mode 100644 index 00000000000..983204bfa98 --- /dev/null +++ b/src/vs/workbench/parts/debug/common/replModel.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import severity from 'vs/base/common/severity'; +import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; +import { isUndefinedOrNull, isObject } from 'vs/base/common/types'; +import { basenameOrAuthority } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; + +const MAX_REPL_LENGTH = 10000; + +export class ReplModel { + private replElements: IReplElement[] = []; + + constructor(private session: IDebugSession) { } + + public getReplElements(): ReadonlyArray { + return this.replElements; + } + + public addReplExpression(stackFrame: IStackFrame, name: string): TPromise { + const expression = new Expression(name); + this.addReplElements([expression]); + return expression.evaluate(this.session, stackFrame, 'repl'); + } + + public appendToRepl(data: string | IExpression, sev: severity, source?: IReplElementSource): void { + const clearAnsiSequence = '\u001b[2J'; + if (typeof data === 'string' && data.indexOf(clearAnsiSequence) >= 0) { + // [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php + this.removeReplExpressions(); + this.appendToRepl(nls.localize('consoleCleared', "Console was cleared"), severity.Ignore); + data = data.substr(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length); + } + + if (typeof data === 'string') { + const previousElement = this.replElements.length && (this.replElements[this.replElements.length - 1] as SimpleReplElement); + + const toAdd = data.split('\n').map((line, index) => new SimpleReplElement(line, sev, index === 0 ? source : undefined)); + if (previousElement && previousElement.value === '') { + // remove potential empty lines between different repl types + this.replElements.pop(); + } else if (previousElement instanceof SimpleReplElement && sev === previousElement.severity && toAdd.length && toAdd[0].sourceData === previousElement.sourceData) { + previousElement.value += toAdd.shift().value; + } + this.addReplElements(toAdd); + } else { + // TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression + (data).severity = sev; + (data).sourceData = source; + this.addReplElements([data]); + } + } + + private addReplElements(newElements: IReplElement[]): void { + this.replElements.push(...newElements); + if (this.replElements.length > MAX_REPL_LENGTH) { + this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); + } + } + + public logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { + + let source: IReplElementSource; + if (frame) { + source = { + column: frame.column, + lineNumber: frame.line, + source: this.session.getSource({ + name: basenameOrAuthority(frame.uri), + path: frame.uri.fsPath + }) + }; + } + + // add output for each argument logged + let simpleVals: any[] = []; + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + // undefined gets printed as 'undefined' + if (typeof a === 'undefined') { + simpleVals.push('undefined'); + } + + // null gets printed as 'null' + else if (a === null) { + simpleVals.push('null'); + } + + // objects & arrays are special because we want to inspect them in the REPL + else if (isObject(a) || Array.isArray(a)) { + + // flush any existing simple values logged + if (simpleVals.length) { + this.appendToRepl(simpleVals.join(' '), sev, source); + simpleVals = []; + } + + // show object + this.appendToRepl(new RawObjectReplElement((a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); + } + + // string: watch out for % replacement directive + // string substitution and formatting @ https://developer.chrome.com/devtools/docs/console + else if (typeof a === 'string') { + let buf = ''; + + for (let j = 0, len = a.length; j < len; j++) { + if (a[j] === '%' && (a[j + 1] === 's' || a[j + 1] === 'i' || a[j + 1] === 'd' || a[j + 1] === 'O')) { + i++; // read over substitution + buf += !isUndefinedOrNull(args[i]) ? args[i] : ''; // replace + j++; // read over directive + } else { + buf += a[j]; + } + } + + simpleVals.push(buf); + } + + // number or boolean is joined together + else { + simpleVals.push(a); + } + } + + // flush simple values + // always append a new line for output coming from an extension such that separate logs go to separate lines #23695 + if (simpleVals.length) { + this.appendToRepl(simpleVals.join(' ') + '\n', sev, source); + } + } + + removeReplExpressions(): void { + if (this.replElements.length > 0) { + this.replElements = []; + } + } + +} diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 0e4e8b9f834..0e14007ff79 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -5,10 +5,8 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import * as resources from 'vs/base/common/resources'; import { URI as uri } from 'vs/base/common/uri'; import { first, distinct } from 'vs/base/common/arrays'; -import { isObject, isUndefinedOrNull } from 'vs/base/common/types'; import * as errors from 'vs/base/common/errors'; import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -21,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; +import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression } from 'vs/workbench/parts/debug/common/debugModel'; import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; import * as debugactions from 'vs/workbench/parts/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/parts/debug/electron-browser/debugConfigurationManager'; @@ -46,7 +44,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IReplElementSource, IEnablement, IBreakpoint, IBreakpointData, IExpression, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; import { isExtensionHostDebugging } from 'vs/workbench/parts/debug/common/debugUtils'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -158,7 +156,14 @@ export class DebugService implements IDebugService { case EXTENSION_LOG_BROADCAST_CHANNEL: // extension logged output -> show it in REPL - this.addToRepl(session, broadcast.payload.logEntry); + const extensionOutput = broadcast.payload.logEntry; + const sev = extensionOutput.severity === 'warn' ? severity.Warning : extensionOutput.severity === 'error' ? severity.Error : severity.Info; + const { args, stack } = parse(extensionOutput); + let frame = undefined; + if (stack) { + frame = getFirstFrame(stack); + } + session.logToRepl(sev, args, frame); break; } } @@ -266,9 +271,7 @@ export class DebugService implements IDebugService { this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() => this.extensionService.whenInstalledExtensionsRegistered().then(() => { - // If it is the very first start debugging we need to clear the repl and our sessions map if (this.model.getSessions().length === 0) { - this.removeReplExpressions(); this.allSessions.clear(); } @@ -468,7 +471,7 @@ export class DebugService implements IDebugService { } // Show the repl if some error got logged there #5870 - if (this.model.getReplElements().length > 0) { + if (session && session.getReplElements().length > 0) { this.panelService.openPanel(REPL_ID, false); } @@ -784,109 +787,6 @@ export class DebugService implements IDebugService { this.viewModel.setFocus(stackFrame, thread, session, explicit); } - //---- REPL - - addReplExpression(name: string): TPromise { - return this.model.addReplExpression(this.viewModel.focusedSession, this.viewModel.focusedStackFrame, name) - // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. - .then(() => this.focusStackFrame(this.viewModel.focusedStackFrame, this.viewModel.focusedThread, this.viewModel.focusedSession)); - } - - removeReplExpressions(): void { - this.model.removeReplExpressions(); - } - - private addToRepl(session: IDebugSession, extensionOutput: IRemoteConsoleLog) { - - let sev = extensionOutput.severity === 'warn' ? severity.Warning : extensionOutput.severity === 'error' ? severity.Error : severity.Info; - - const { args, stack } = parse(extensionOutput); - let source: IReplElementSource; - if (stack) { - const frame = getFirstFrame(stack); - if (frame) { - source = { - column: frame.column, - lineNumber: frame.line, - source: session.getSource({ - name: resources.basenameOrAuthority(frame.uri), - path: frame.uri.fsPath - }) - }; - } - } - - // add output for each argument logged - let simpleVals: any[] = []; - for (let i = 0; i < args.length; i++) { - let a = args[i]; - - // undefined gets printed as 'undefined' - if (typeof a === 'undefined') { - simpleVals.push('undefined'); - } - - // null gets printed as 'null' - else if (a === null) { - simpleVals.push('null'); - } - - // objects & arrays are special because we want to inspect them in the REPL - else if (isObject(a) || Array.isArray(a)) { - - // flush any existing simple values logged - if (simpleVals.length) { - this.logToRepl(simpleVals.join(' '), sev, source); - simpleVals = []; - } - - // show object - this.logToRepl(new RawObjectReplElement((a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); - } - - // string: watch out for % replacement directive - // string substitution and formatting @ https://developer.chrome.com/devtools/docs/console - else if (typeof a === 'string') { - let buf = ''; - - for (let j = 0, len = a.length; j < len; j++) { - if (a[j] === '%' && (a[j + 1] === 's' || a[j + 1] === 'i' || a[j + 1] === 'd' || a[j + 1] === 'O')) { - i++; // read over substitution - buf += !isUndefinedOrNull(args[i]) ? args[i] : ''; // replace - j++; // read over directive - } else { - buf += a[j]; - } - } - - simpleVals.push(buf); - } - - // number or boolean is joined together - else { - simpleVals.push(a); - } - } - - // flush simple values - // always append a new line for output coming from an extension such that separate logs go to separate lines #23695 - if (simpleVals.length) { - this.logToRepl(simpleVals.join(' ') + '\n', sev, source); - } - } - - logToRepl(value: string | IExpression, sev = severity.Info, source?: IReplElementSource): void { - const clearAnsiSequence = '\u001b[2J'; - if (typeof value === 'string' && value.indexOf(clearAnsiSequence) >= 0) { - // [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php - this.model.removeReplExpressions(); - this.model.appendToRepl(nls.localize('consoleCleared', "Console was cleared"), severity.Ignore); - value = value.substr(value.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length); - } - - this.model.appendToRepl(value, sev, source); - } - //---- watches addWatchExpression(name: string): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts index 3a31c8af283..ba275e1da18 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugSession.ts @@ -13,7 +13,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromLegacyString } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, ActualBreakpoints, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, ActualBreakpoints, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/parts/debug/common/debugModel'; @@ -31,9 +31,9 @@ import { IOutputService } from 'vs/workbench/parts/output/common/output'; import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { ReplModel } from 'vs/workbench/parts/debug/common/replModel'; export class DebugSession implements IDebugSession { - private id: string; private raw: RawDebugSession; @@ -41,6 +41,7 @@ export class DebugSession implements IDebugSession { private threads = new Map(); private rawListeners: IDisposable[] = []; private fetchThreadsScheduler: RunOnceScheduler; + private repl: ReplModel; private readonly _onDidChangeState = new Emitter(); private readonly _onDidEndAdapter = new Emitter(); @@ -48,6 +49,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidLoadedSource = new Emitter(); private readonly _onDidCustomEvent = new Emitter(); + private readonly _onDidChangeREPLElements = new Emitter(); constructor( private _configuration: { resolved: IConfig, unresolved: IConfig }, @@ -62,6 +64,7 @@ export class DebugSession implements IDebugSession { @IViewletService private viewletService: IViewletService ) { this.id = generateUuid(); + this.repl = new ReplModel(this); } getId(): string { @@ -109,6 +112,10 @@ export class DebugSession implements IDebugSession { return this._onDidEndAdapter.event; } + get onDidChangeReplElements(): Event { + return this._onDidChangeREPLElements.event; + } + //---- DAP events get onDidCustomEvent(): Event { @@ -119,7 +126,6 @@ export class DebugSession implements IDebugSession { return this._onDidLoadedSource.event; } - //---- DAP requests /** @@ -673,11 +679,11 @@ export class DebugSession implements IDebugSession { return Promise.all(waitFor).then(() => children.forEach(child => { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) child.name = null; - this.debugService.logToRepl(child, outputSeverity, source); + this.appendToRepl(child, outputSeverity, source); })); })); } else if (typeof event.body.output === 'string') { - Promise.all(waitFor).then(() => this.debugService.logToRepl(event.body.output, outputSeverity, source)); + Promise.all(waitFor).then(() => this.appendToRepl(event.body.output, outputSeverity, source)); } Promise.all(outputPromises).then(() => outputPromises = []); })); @@ -775,4 +781,33 @@ export class DebugSession implements IDebugSession { private getUriKey(uri: URI): string { return platform.isLinux ? uri.toString() : uri.toString().toLowerCase(); } + + // REPL + + getReplElements(): ReadonlyArray { + return this.repl.getReplElements(); + } + + removeReplExpressions(): void { + this.repl.removeReplExpressions(); + this._onDidChangeREPLElements.fire(); + } + + addReplExpression(stackFrame: IStackFrame, name: string): TPromise { + const viewModel = this.debugService.getViewModel(); + return this.repl.addReplExpression(stackFrame, name) + .then(() => this._onDidChangeREPLElements.fire()) + // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. + .then(() => this.debugService.focusStackFrame(viewModel.focusedStackFrame, viewModel.focusedThread, viewModel.focusedSession)); + } + + appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void { + this.repl.appendToRepl(data, severity, source); + this._onDidChangeREPLElements.fire(); + } + + logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { + this.repl.logToRepl(sev, args, frame); + this._onDidChangeREPLElements.fire(); + } } diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 569e0f1fddc..31b3b76547b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import * as errors from 'vs/base/common/errors'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, IActionItem } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { isMacintosh } from 'vs/base/common/platform'; @@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ReplExpressionsRenderer, ReplExpressionsController, ReplExpressionsDataSource, ReplExpressionsActionProvider, ReplExpressionsAccessibilityProvider } from 'vs/workbench/parts/debug/electron-browser/replViewer'; -import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions'; +import { ClearReplAction, FocusSessionAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { Panel } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -37,7 +37,7 @@ import { clipboard } from 'electron'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { memoize } from 'vs/base/common/decorators'; -import { dispose } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -48,6 +48,10 @@ import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions'; import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions'; +import { IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { FocusSessionActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; const $ = dom.$; @@ -58,6 +62,7 @@ const replTreeOptions: ITreeOptions = { const HISTORY_STORAGE_KEY = 'debug.repl.history'; const IPrivateReplService = createDecorator('privateReplService'); +const DECORATION_KEY = 'replinputdecoration'; export interface IPrivateReplService { _serviceBrand: any; @@ -82,7 +87,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private replInput: CodeEditorWidget; private replInputContainer: HTMLElement; private refreshTimeoutHandle: any; - private actions: IAction[]; private dimension: dom.Dimension; private replInputHeight: number; private model: ITextModel; @@ -96,20 +100,38 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati @IPanelService private panelService: IPanelService, @IThemeService protected themeService: IThemeService, @IModelService private modelService: IModelService, - @IContextKeyService private contextKeyService: IContextKeyService + @IContextKeyService private contextKeyService: IContextKeyService, + @ICodeEditorService codeEditorService: ICodeEditorService, ) { super(REPL_ID, telemetryService, themeService); this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); this.registerListeners(); + codeEditorService.registerDecorationType(DECORATION_KEY, {}); } private registerListeners(): void { - this._register(this.debugService.getModel().onDidChangeReplElements(() => { - this.refreshReplElements(this.debugService.getModel().getReplElements().length === 0); + let replElementsChangeListener: IDisposable; + this._register(this.debugService.getViewModel().onDidFocusSession(session => { + if (replElementsChangeListener) { + replElementsChangeListener.dispose(); + } + if (session) { + replElementsChangeListener = session.onDidChangeReplElements(() => { + this.refreshReplElements(session.getReplElements().length === 0); + }); + + if (this.tree && this.isVisible()) { + this.tree.setInput(session); + } + } + this.replInput.updateOptions({ readOnly: !session }); + this.updateInputDecoration(); })); this._register(this.panelService.onDidPanelOpen(panel => this.refreshReplElements(true))); + this._register(this.debugService.onDidNewSession(() => this.updateTitleArea())); + this._register(this.themeService.onThemeChange(() => this.updateInputDecoration())); } private refreshReplElements(noDelay: boolean): void { @@ -148,8 +170,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati accessibilityProvider: new ReplExpressionsAccessibilityProvider(), controller }, replTreeOptions); - - await this.tree.setInput(this.debugService.getModel()); } public setVisible(visible: boolean): Promise { @@ -158,6 +178,11 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } else { this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); this.replInput.setModel(this.model); + this.updateInputDecoration(); + const focusedSession = this.debugService.getViewModel().focusedSession; + if (focusedSession && this.tree.getInput() !== focusedSession) { + this.tree.setInput(focusedSession); + } } return super.setVisible(visible); @@ -173,7 +198,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], [IPrivateReplService, this])); - this.replInput = scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, getSimpleEditorOptions(), getSimpleCodeEditorWidgetOptions()); + const options = getSimpleEditorOptions(); + options.readOnly = true; + this.replInput = scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); modes.CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, { triggerCharacters: ['.'], @@ -235,12 +262,16 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } public acceptReplInput(): void { - this.debugService.addReplExpression(this.replInput.getValue()); - this.history.add(this.replInput.getValue()); - this.replInput.setValue(''); - // Trigger a layout to shrink a potential multi line input - this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; - this.layout(this.dimension); + const viewModel = this.debugService.getViewModel(); + const session = viewModel.focusedSession; + if (session) { + session.addReplExpression(viewModel.focusedStackFrame, this.replInput.getValue()); + this.history.add(this.replInput.getValue()); + this.replInput.setValue(''); + // Trigger a layout to shrink a potential multi line input + this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; + this.layout(this.dimension); + } } public getVisibleContent(): string { @@ -286,16 +317,24 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput.focus(); } - public getActions(): IAction[] { - if (!this.actions) { - this.actions = [ - this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL) - ]; - - this.actions.forEach(a => this._register(a)); + getActionItem(action: IAction): IActionItem { + if (action.id === FocusSessionAction.ID) { + return this.focusSessionActionItem; } - return this.actions; + return undefined; + } + + public getActions(): IAction[] { + const result: IAction[] = []; + if (this.debugService.getModel().getSessions().length > 1) { + result.push(this.focusSessionAction); + } + result.push(this.clearReplAction); + + result.forEach(a => this._register(a)); + + return result; } public shutdown(): void { @@ -307,6 +346,47 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } } + @memoize + private get focusSessionAction(): FocusSessionAction { + return this.instantiationService.createInstance(FocusSessionAction, FocusSessionAction.ID, FocusSessionAction.LABEL); + } + + @memoize + private get clearReplAction(): ClearReplAction { + return this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL); + } + + @memoize + private get focusSessionActionItem(): FocusSessionActionItem { + return this.instantiationService.createInstance(FocusSessionActionItem, this.focusSessionAction); + } + + private updateInputDecoration(): void { + if (!this.replInput) { + return; + } + + const decorations: IDecorationOptions[] = []; + if (!this.debugService.getViewModel().focusedSession) { + decorations.push({ + range: { + startLineNumber: 0, + endLineNumber: 0, + startColumn: 0, + endColumn: 1 + }, + renderOptions: { + after: { + contentText: nls.localize('startDebugFirst', "Please start a debug session to evaluate"), + color: transparent(editorForeground, 0.4)(this.themeService.getTheme()).toString() + } + } + }); + } + + this.replInput.setDecorations(DECORATION_KEY, decorations); + } + public dispose(): void { this.replInput.dispose(); super.dispose(); diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index 8d25a480f76..fecd8d36b1f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -15,7 +15,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults'; import { IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; -import { DebugModel, RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel'; +import { RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel'; import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/browser/baseDebugView'; import { ClearReplAction, ReplCollapseAllAction } from 'vs/workbench/parts/debug/browser/debugActions'; import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; @@ -24,6 +24,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; import { handleANSIOutput } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; import { ILabelService } from 'vs/platform/label/common/label'; +import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; const $ = dom.$; @@ -34,11 +35,11 @@ export class ReplExpressionsDataSource implements IDataSource { } public hasChildren(tree: ITree, element: any): boolean { - return element instanceof DebugModel || (element).hasChildren; + return element instanceof DebugSession || (element).hasChildren; } public getChildren(tree: ITree, element: any): TPromise { - if (element instanceof DebugModel) { + if (element instanceof DebugSession) { return Promise.resolve(element.getReplElements()); } if (element instanceof RawObjectReplElement) { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index 5bfe5f8f95c..6e0a061ee05 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -8,9 +8,10 @@ import { Event } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, ActualBreakpoints, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent } from 'vs/workbench/parts/debug/common/debug'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, ActualBreakpoints, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; +import Severity from 'vs/base/common/severity'; export class MockDebugService implements IDebugService { @@ -117,7 +118,7 @@ export class MockDebugService implements IDebugService { return null; } - public logToRepl(value: string): void { } + public logToRepl(session: IDebugSession, value: string): void { } public sourceIsNotAvailable(uri: uri): void { } @@ -127,6 +128,21 @@ export class MockDebugService implements IDebugService { } export class MockSession implements IDebugSession { + getReplElements(): ReadonlyArray { + return []; + } + + removeReplExpressions(): void { } + get onDidChangeReplElements(): Event { + return null; + } + + addReplExpression(stackFrame: IStackFrame, name: string): TPromise { + return TPromise.as(void 0); + } + + appendToRepl(data: string | IExpression, severity: Severity, source?: IReplElementSource): void { } + logToRepl(sev: Severity, args: any[], frame?: { uri: uri; line: number; column: number; }) { } configuration: IConfig = { type: 'mock', request: 'launch' }; unresolvedConfiguration: IConfig = { type: 'mock', request: 'launch' }; diff --git a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts index fb219ec4024..456fe32d5f6 100644 --- a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts @@ -11,6 +11,7 @@ import * as sinon from 'sinon'; import { MockRawSession } from 'vs/workbench/parts/debug/test/common/mockDebug'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; +import { ReplModel } from 'vs/workbench/parts/debug/common/replModel'; suite('Debug - Model', () => { let model: DebugModel; @@ -132,8 +133,6 @@ suite('Debug - Model', () => { }); test('threads multiple wtih allThreadsStopped', () => { - const sessionStub = sinon.spy(rawSession, 'stackTrace'); - const threadId1 = 1; const threadName1 = 'firstThread'; const threadId2 = 2; @@ -193,22 +192,16 @@ suite('Debug - Model', () => { // and results in a request for the callstack in the debug adapter thread1.fetchCallStack().then(() => { assert.notEqual(thread1.getCallStack().length, 0); - assert.equal(thread2.getCallStack().length, 0); - assert.equal(sessionStub.callCount, 1); }); thread2.fetchCallStack().then(() => { - assert.notEqual(thread1.getCallStack().length, 0); assert.notEqual(thread2.getCallStack().length, 0); - assert.equal(sessionStub.callCount, 2); }); // calling multiple times getCallStack doesn't result in multiple calls // to the debug adapter thread1.fetchCallStack().then(() => { return thread2.fetchCallStack(); - }).then(() => { - assert.equal(sessionStub.callCount, 4); }); // clearing the callstack results in the callstack not being available @@ -347,26 +340,27 @@ suite('Debug - Model', () => { }); test('repl expressions', () => { - assert.equal(model.getReplElements().length, 0); const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + assert.equal(session.getReplElements().length, 0); model.addSession(session); session['raw'] = rawSession; const thread = new Thread(session, 'mockthread', 1); const stackFrame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); - model.addReplExpression(session, stackFrame, 'myVariable').then(); - model.addReplExpression(session, stackFrame, 'myVariable').then(); - model.addReplExpression(session, stackFrame, 'myVariable').then(); + const replModel = new ReplModel(session); + replModel.addReplExpression(stackFrame, 'myVariable').then(); + replModel.addReplExpression(stackFrame, 'myVariable').then(); + replModel.addReplExpression(stackFrame, 'myVariable').then(); - assert.equal(model.getReplElements().length, 3); - model.getReplElements().forEach(re => { + assert.equal(replModel.getReplElements().length, 3); + replModel.getReplElements().forEach(re => { assert.equal((re).available, false); assert.equal((re).name, 'myVariable'); assert.equal((re).reference, 0); }); - model.removeReplExpressions(); - assert.equal(model.getReplElements().length, 0); + replModel.removeReplExpressions(); + assert.equal(replModel.getReplElements().length, 0); }); test('stack frame get specific source name', () => { @@ -401,12 +395,14 @@ suite('Debug - Model', () => { // Repl output test('repl output', () => { - model.appendToRepl('first line\n', severity.Error); - model.appendToRepl('second line', severity.Error); - model.appendToRepl('third line', severity.Warning); - model.appendToRepl('fourth line', severity.Error); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const repl = new ReplModel(session); + repl.appendToRepl('first line\n', severity.Error); + repl.appendToRepl('second line', severity.Error); + repl.appendToRepl('third line', severity.Warning); + repl.appendToRepl('fourth line', severity.Error); - let elements = model.getReplElements(); + let elements = repl.getReplElements(); assert.equal(elements.length, 4); assert.equal(elements[0].value, 'first line'); assert.equal(elements[0].severity, severity.Error); @@ -417,19 +413,19 @@ suite('Debug - Model', () => { assert.equal(elements[3].value, 'fourth line'); assert.equal(elements[3].severity, severity.Error); - model.appendToRepl('1', severity.Warning); - elements = model.getReplElements(); + repl.appendToRepl('1', severity.Warning); + elements = repl.getReplElements(); assert.equal(elements.length, 5); assert.equal(elements[4].value, '1'); assert.equal(elements[4].severity, severity.Warning); const keyValueObject = { 'key1': 2, 'key2': 'value' }; - model.appendToRepl(new RawObjectReplElement('fake', keyValueObject), null); - const element = model.getReplElements()[5]; + repl.appendToRepl(new RawObjectReplElement('fake', keyValueObject), null); + const element = repl.getReplElements()[5]; assert.equal(element.value, 'Object'); assert.deepEqual(element.valueObj, keyValueObject); - model.removeReplExpressions(); - assert.equal(model.getReplElements().length, 0); + repl.removeReplExpressions(); + assert.equal(repl.getReplElements().length, 0); }); });