未验证 提交 2f0fdd34 编写于 作者: I Isidor Nikolic 提交者: GitHub

Merge pull request #60016 from Microsoft/isidorn/multiRepl

Multiple repls per debug session
......@@ -205,7 +205,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
public $appendDebugConsole(value: string): Thenable<void> {
// 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<void>(undefined);
}
......
......@@ -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<any> {
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);
......
......@@ -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);
}
......
......@@ -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<IThread>;
clearThreads(removeThreads: boolean, reference?: number): void;
getReplElements(): ReadonlyArray<IReplElement>;
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>;
readonly onDidChangeState: Event<void>;
readonly onDidChangeReplElements: Event<void>;
// DA capabilities
readonly capabilities: DebugProtocol.Capabilities;
......@@ -200,10 +212,6 @@ export interface IDebugSession extends ITreeElement {
setVariable(variablesReference: number, name: string, value: string): TPromise<DebugProtocol.SetVariableResponse>;
loadSource(resource: uri): TPromise<DebugProtocol.SourceResponse>;
getLoadedSources(): TPromise<Source[]>;
getThread(threadId: number): IThread;
getAllThreads(): ReadonlyArray<IThread>;
clearThreads(removeThreads: boolean, reference?: number): void;
}
export interface IThread extends ITreeElement {
......@@ -374,12 +382,10 @@ export interface IDebugModel extends ITreeElement {
getFunctionBreakpoints(): ReadonlyArray<IFunctionBreakpoint>;
getExceptionBreakpoints(): ReadonlyArray<IExceptionBreakpoint>;
getWatchExpressions(): ReadonlyArray<IExpression>;
getReplElements(): ReadonlyArray<IReplElement>;
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent>;
onDidChangeCallStack: Event<void>;
onDidChangeWatchExpressions: Event<IExpression>;
onDidChangeReplElements: Event<void>;
}
/**
......@@ -721,21 +727,6 @@ export interface IDebugService {
*/
sendAllBreakpoints(session?: IDebugSession): TPromise<any>;
/**
* Adds a new expression to the repl.
*/
addReplExpression(name: string): TPromise<void>;
/**
* 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.
*/
......
......@@ -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<string, RunOnceScheduler>();
private breakpointsSessionId: string;
private readonly _onDidChangeBreakpoints: Emitter<IBreakpointsChangeEvent>;
private readonly _onDidChangeCallStack: Emitter<void>;
private readonly _onDidChangeWatchExpressions: Emitter<IExpression>;
private readonly _onDidChangeREPLElements: Emitter<void>;
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<IBreakpointsChangeEvent>();
this._onDidChangeCallStack = new Emitter<void>();
this._onDidChangeWatchExpressions = new Emitter<IExpression>();
this._onDidChangeREPLElements = new Emitter<void>();
}
public getId(): string {
......@@ -782,10 +776,6 @@ export class DebugModel implements IDebugModel {
return this._onDidChangeWatchExpressions.event;
}
public get onDidChangeReplElements(): Event<void> {
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<void> {
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
(<any>data).severity = severity;
(<any>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;
}
......
/*---------------------------------------------------------------------------------------------
* 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, 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 = <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;
}
}
......@@ -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<void> {
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((<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.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 {
......
......@@ -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<number, Thread>();
private rawListeners: IDisposable[] = [];
private fetchThreadsScheduler: RunOnceScheduler;
private repl: ReplModel;
private readonly _onDidChangeState = new Emitter<void>();
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent>();
......@@ -48,6 +49,7 @@ export class DebugSession implements IDebugSession {
private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();
private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();
private readonly _onDidChangeREPLElements = new Emitter<void>();
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<void> {
return this._onDidChangeREPLElements.event;
}
//---- DAP events
get onDidCustomEvent(): Event<DebugProtocol.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<IReplElement> {
return this.repl.getReplElements();
}
removeReplExpressions(): void {
this.repl.removeReplExpressions();
this._onDidChangeREPLElements.fire();
}
addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void> {
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();
}
}
......@@ -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<IPrivateReplService>('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<void> {
......@@ -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)
];
getActionItem(action: IAction): IActionItem {
if (action.id === FocusSessionAction.ID) {
return this.focusSessionActionItem;
}
return undefined;
}
this.actions.forEach(a => this._register(a));
public getActions(): IAction[] {
const result: IAction[] = [];
if (this.debugService.getModel().getSessions().length > 1) {
result.push(this.focusSessionAction);
}
result.push(this.clearReplAction);
return this.actions;
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();
......
......@@ -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 || (<IExpressionContainer>element).hasChildren;
return element instanceof DebugSession || (<IExpressionContainer>element).hasChildren;
}
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof DebugModel) {
if (element instanceof DebugSession) {
return Promise.resolve(element.getReplElements());
}
if (element instanceof RawObjectReplElement) {
......
......@@ -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<IReplElement> {
return [];
}
removeReplExpressions(): void { }
get onDidChangeReplElements(): Event<void> {
return null;
}
addReplExpression(stackFrame: IStackFrame, name: string): TPromise<void> {
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' };
......
......@@ -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'] = <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);
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((<Expression>re).available, false);
assert.equal((<Expression>re).name, 'myVariable');
assert.equal((<Expression>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 = <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);
......@@ -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 = <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' };
model.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);
model.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.
先完成此消息的编辑!
想要评论请 注册