提交 51cfd808 编写于 作者: I isidor

debug: introduce replModel and move repl related methods inside it

上级 aa351d82
......@@ -161,6 +161,7 @@ export interface IDebugSession extends ITreeElement {
removeReplExpressions(): void;
addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void>;
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<AdapterEndEvent>;
......
/*---------------------------------------------------------------------------------------------
* 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<IReplElement> {
return this.replElements;
}
public addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void> {
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
(<any>data).severity = sev;
(<any>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((<any>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 = [];
}
}
}
......@@ -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, 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 = <IRemoteConsoleLog>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;
}
}
......@@ -782,87 +787,6 @@ export class DebugService implements IDebugService {
this.viewModel.setFocus(stackFrame, thread, session, explicit);
}
//---- REPL
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) {
session.appendToRepl(simpleVals.join(' '), sev, source);
simpleVals = [];
}
// show object
session.appendToRepl(new RawObjectReplElement((<any>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) {
session.appendToRepl(simpleVals.join(' ') + '\n', sev, source);
}
}
//---- watches
addWatchExpression(name: string): void {
......
......@@ -16,7 +16,7 @@ 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, 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, Expression, SimpleReplElement } from 'vs/workbench/parts/debug/common/debugModel';
import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/parts/debug/common/debugModel';
import { RawDebugSession } from 'vs/workbench/parts/debug/electron-browser/rawDebugSession';
import product from 'vs/platform/node/product';
import { INotificationService } from 'vs/platform/notification/common/notification';
......@@ -31,11 +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';
const MAX_REPL_LENGTH = 10000;
import { ReplModel } from 'vs/workbench/parts/debug/common/replModel';
export class DebugSession implements IDebugSession {
private id: string;
private raw: RawDebugSession;
......@@ -43,7 +41,7 @@ export class DebugSession implements IDebugSession {
private threads = new Map<number, Thread>();
private rawListeners: IDisposable[] = [];
private fetchThreadsScheduler: RunOnceScheduler;
private replElements: IReplElement[] = [];
private repl: ReplModel;
private readonly _onDidChangeState = new Emitter<void>();
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent>();
......@@ -66,6 +64,7 @@ export class DebugSession implements IDebugSession {
@IViewletService private viewletService: IViewletService
) {
this.id = generateUuid();
this.repl = new ReplModel(this);
}
getId(): string {
......@@ -785,61 +784,30 @@ export class DebugSession implements IDebugSession {
// REPL
public getReplElements(): ReadonlyArray<IReplElement> {
return this.replElements;
getReplElements(): ReadonlyArray<IReplElement> {
return this.repl.getReplElements();
}
removeReplExpressions(): void {
this.repl.removeReplExpressions();
this._onDidChangeREPLElements.fire();
}
public addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void> {
const expression = new Expression(name);
this.addReplElements([expression]);
addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void> {
const viewModel = this.debugService.getViewModel();
return expression.evaluate(this, stackFrame, 'repl')
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));
}
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
(<any>data).severity = sev;
(<any>data).sourceData = source;
this.addReplElements([data]);
}
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void {
this.repl.appendToRepl(data, severity, source);
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);
}
}
removeReplExpressions(): void {
if (this.replElements.length > 0) {
this.replElements = [];
this._onDidChangeREPLElements.fire();
}
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
this.repl.logToRepl(sev, args, frame);
this._onDidChangeREPLElements.fire();
}
}
......@@ -139,6 +139,7 @@ export class MockSession implements IDebugSession {
}
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' };
......
......@@ -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
......@@ -354,19 +347,20 @@ suite('Debug - Model', () => {
session['raw'] = <any>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);
session.addReplExpression(stackFrame, 'myVariable').then();
session.addReplExpression(stackFrame, 'myVariable').then();
session.addReplExpression(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((<Expression>re).available, false);
assert.equal((<Expression>re).name, 'myVariable');
assert.equal((<Expression>re).reference, 0);
});
session.removeReplExpressions();
assert.equal(model.getReplElements().length, 0);
replModel.removeReplExpressions();
assert.equal(replModel.getReplElements().length, 0);
});
test('stack frame get specific source name', () => {
......@@ -402,12 +396,13 @@ suite('Debug - Model', () => {
test('repl output', () => {
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
session.appendToRepl('first line\n', severity.Error);
session.appendToRepl('second line', severity.Error);
session.appendToRepl('third line', severity.Warning);
session.appendToRepl('fourth line', severity.Error);
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 = <SimpleReplElement[]>model.getReplElements();
let elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 4);
assert.equal(elements[0].value, 'first line');
assert.equal(elements[0].severity, severity.Error);
......@@ -418,19 +413,19 @@ suite('Debug - Model', () => {
assert.equal(elements[3].value, 'fourth line');
assert.equal(elements[3].severity, severity.Error);
session.appendToRepl('1', severity.Warning);
elements = <SimpleReplElement[]>model.getReplElements();
repl.appendToRepl('1', severity.Warning);
elements = <SimpleReplElement[]>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' };
session.appendToRepl(new RawObjectReplElement('fake', keyValueObject), null);
const element = <RawObjectReplElement>model.getReplElements()[5];
repl.appendToRepl(new RawObjectReplElement('fake', keyValueObject), null);
const element = <RawObjectReplElement>repl.getReplElements()[5];
assert.equal(element.value, 'Object');
assert.deepEqual(element.valueObj, keyValueObject);
session.removeReplExpressions();
assert.equal(model.getReplElements().length, 0);
repl.removeReplExpressions();
assert.equal(repl.getReplElements().length, 0);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册