提交 33bed406 编写于 作者: I isidor

debug: introduce sessions in the debug model

上级 b42cf8c2
......@@ -169,10 +169,11 @@ export class StepOverAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.next(threadId);
return this.debugService.next(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -189,10 +190,11 @@ export class StepIntoAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.stepIn(threadId);
return this.debugService.stepIn(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -209,10 +211,11 @@ export class StepOutAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.stepOut(threadId);
return this.debugService.stepOut(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -229,10 +232,11 @@ export class StepBackAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.stepBack(threadId);
return this.debugService.stepBack(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -287,10 +291,11 @@ export class ContinueAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.continue(threadId);
return this.debugService.continue(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -307,10 +312,11 @@ export class PauseAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!thread) {
thread = this.debugService.getViewModel().getFocusedThread();
}
return this.debugService.pause(threadId);
return this.debugService.pause(thread.threadId);
}
protected isEnabled(state: debug.State): boolean {
......@@ -645,7 +651,7 @@ class RunToCursorAction extends EditorAction {
const bpExists = !!(debugService.getModel().getBreakpoints().filter(bp => bp.lineNumber === lineNumber && bp.source.uri.toString() === uri.toString()).pop());
return (bpExists ? TPromise.as(null) : debugService.addBreakpoints([{ uri, lineNumber }])).then(() => {
debugService.continue(debugService.getViewModel().getFocusedThreadId());
debugService.continue(debugService.getViewModel().getFocusedThread().threadId);
});
}
}
......
......@@ -133,26 +133,24 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
private createCallStackDecorations(modelUrlStr: string): editorcommon.IModelDeltaDecoration[] {
const result: editorcommon.IModelDeltaDecoration[] = [];
const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
const focusedThreadId = this.debugService.getViewModel().getFocusedThreadId();
const allThreads = this.debugService.getModel().getThreads();
if (!focusedStackFrame || !allThreads[focusedThreadId] || !allThreads[focusedThreadId].getCachedCallStack()) {
const focusedThread = this.debugService.getViewModel().getFocusedThread();
if (!focusedStackFrame || !focusedThread || !focusedThread.getCachedCallStack()) {
return result;
}
// only show decorations for the currently focussed thread.
const thread = allThreads[focusedThreadId];
thread.getCachedCallStack().filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
focusedThread.getCachedCallStack().filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
const wholeLineRange = createRange(sf.lineNumber, sf.column, sf.lineNumber, Number.MAX_VALUE);
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focussed stack frame,
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
if (sf === thread.getCachedCallStack()[0]) {
if (sf === focusedThread.getCachedCallStack()[0]) {
result.push({
options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1)
});
if (thread.stoppedDetails.reason === 'exception') {
if (focusedThread.stoppedDetails.reason === 'exception') {
result.push({
options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION,
range: wholeLineRange
......
......@@ -31,7 +31,7 @@ export const DEBUG_SCHEME = 'debug';
export interface IRawModelUpdate {
threadId: number;
sessionId: string;
rawSession: IRawDebugSession;
thread?: DebugProtocol.Thread;
callStack?: DebugProtocol.StackFrame[];
stoppedDetails?: IRawStoppedDetails;
......@@ -93,7 +93,7 @@ export interface IThread extends ITreeElement {
* Only gets the first 20 stack frames. Calling this method consecutive times
* with getAdditionalStackFrames = true gets the remainder of the call stack.
*/
getCallStack(debugService: IDebugService, getAdditionalStackFrames?: boolean): TPromise<IStackFrame[]>;
getCallStack(getAdditionalStackFrames?: boolean): TPromise<IStackFrame[]>;
/**
* Gets the callstack if it has already been received from the debug
......@@ -120,6 +120,7 @@ export interface IScope extends IExpressionContainer {
export interface IStackFrame extends ITreeElement {
threadId: number;
sessionId: string;
name: string;
lineNumber: number;
column: number;
......@@ -166,10 +167,13 @@ export interface IExceptionBreakpoint extends IEnablement {
// model interfaces
export interface IViewModel extends ITreeElement {
/**
* Returns the active debug session or null if debug is inactive.
*/
activeSession: IRawDebugSession;
getFocusedStackFrame(): IStackFrame;
getSelectedExpression(): IExpression;
getFocusedThreadId(): number;
getFocusedThread(): IThread;
setSelectedExpression(expression: IExpression);
getSelectedFunctionBreakpoint(): IFunctionBreakpoint;
setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void;
......@@ -180,7 +184,8 @@ export interface IViewModel extends ITreeElement {
}
export interface IModel extends ITreeElement {
getThreads(): { [threadId: number]: IThread; };
getSessions(): IRawDebugSession[];
getThreads(sessionId): { [threadId: number]: IThread; };
getBreakpoints(): IBreakpoint[];
areBreakpointsActivated(): boolean;
getFunctionBreakpoints(): IFunctionBreakpoint[];
......@@ -271,6 +276,7 @@ export interface IRawBreakpointContribution {
export interface IRawDebugSession {
configuration: { type: string, capabilities: DebugProtocol.Capabilities };
getId(): string;
disconnect(restart?: boolean, force?: boolean): TPromise<DebugProtocol.DisconnectResponse>;
......
......@@ -89,7 +89,7 @@ export class Thread implements debug.IThread {
public stoppedDetails: debug.IRawStoppedDetails;
public stopped: boolean;
constructor(public sessionId: string, public name: string, public threadId: number) {
constructor(private session: debug.IRawDebugSession, public name: string, public threadId: number) {
this.promisedCallStack = undefined;
this.stoppedDetails = undefined;
this.cachedCallStack = undefined;
......@@ -100,6 +100,10 @@ export class Thread implements debug.IThread {
return `thread:${this.sessionId}:${this.name}:${this.threadId}`;
}
public get sessionId(): string {
return this.session.getId();
}
public clearCallStack(): void {
this.promisedCallStack = undefined;
this.cachedCallStack = undefined;
......@@ -109,18 +113,18 @@ export class Thread implements debug.IThread {
return this.cachedCallStack;
}
public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
public getCallStack(getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
if (!this.stopped) {
return TPromise.as([]);
}
if (!this.promisedCallStack) {
this.promisedCallStack = this.getCallStackImpl(debugService, 0).then(callStack => {
this.promisedCallStack = this.getCallStackImpl(0).then(callStack => {
this.cachedCallStack = callStack;
return callStack;
});
} else if (getAdditionalStackFrames) {
this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(debugService, callStackFirstPart.length).then(callStackSecondPart => {
this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(callStackFirstPart.length).then(callStackSecondPart => {
this.cachedCallStack = callStackFirstPart.concat(callStackSecondPart);
return this.cachedCallStack;
}));
......@@ -129,9 +133,8 @@ export class Thread implements debug.IThread {
return this.promisedCallStack;
}
private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise<debug.IStackFrame[]> {
let session = debugService.activeSession;
return session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
private getCallStackImpl(startFrame: number): TPromise<debug.IStackFrame[]> {
return this.session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
if (!response || !response.body) {
return [];
}
......@@ -139,10 +142,10 @@ export class Thread implements debug.IThread {
this.stoppedDetails.totalFrames = response.body.totalFrames;
return response.body.stackFrames.map((rsf, level) => {
if (!rsf) {
return new StackFrame(this.threadId, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
return new StackFrame(this.sessionId, this.threadId, 0, new Source({ name: UNKNOWN_SOURCE_LABEL }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
}
return new StackFrame(this.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: UNKNOWN_SOURCE_LABEL }, false), rsf.name, rsf.line, rsf.column);
return new StackFrame(this.sessionId, this.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: UNKNOWN_SOURCE_LABEL }, false), rsf.name, rsf.line, rsf.column);
});
}, (err: Error) => {
this.stoppedDetails.framesErrorMessage = err.message;
......@@ -373,6 +376,7 @@ export class StackFrame implements debug.IStackFrame {
private scopes: TPromise<Scope[]>;
constructor(
public sessionId: string,
public threadId: number,
public frameId: number,
public source: Source,
......@@ -384,7 +388,7 @@ export class StackFrame implements debug.IStackFrame {
}
public getId(): string {
return `stackframe:${this.threadId}:${this.frameId}`;
return `stackframe:${this.sessionId}:${this.threadId}:${this.frameId}`;
}
public getScopes(debugService: debug.IDebugService): TPromise<debug.IScope[]> {
......@@ -457,13 +461,86 @@ export class ExceptionBreakpoint implements debug.IExceptionBreakpoint {
}
class DebugSession {
constructor(public id: string, public raw: debug.IRawDebugSession, public threads: { [reference: number]: debug.IThread; }) { }
public threads: { [reference: number]: debug.IThread; };
constructor(public raw: debug.IRawDebugSession) {
this.threads = {};
}
public getId(): string {
return this.raw.getId();
}
public rawUpdate(data: debug.IRawModelUpdate): void {
if (data.thread && !this.threads[data.threadId]) {
// A new thread came in, initialize it.
this.threads[data.threadId] = new Thread(this.raw, data.thread.name, data.thread.id);
}
if (data.stoppedDetails) {
// Set the availability of the threads' callstacks depending on
// whether the thread is stopped or not
if (data.allThreadsStopped) {
Object.keys(this.threads).forEach(ref => {
// Only update the details if all the threads are stopped
// because we don't want to overwrite the details of other
// threads that have stopped for a different reason
this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
this.threads[ref].stopped = true;
this.threads[ref].clearCallStack();
});
} else {
// One thread is stopped, only update that thread.
this.threads[data.threadId].stoppedDetails = data.stoppedDetails;
this.threads[data.threadId].clearCallStack();
this.threads[data.threadId].stopped = true;
}
}
}
public clearThreads(removeThreads: boolean, reference: number = undefined): void {
if (reference) {
if (this.threads[reference]) {
this.threads[reference].clearCallStack();
this.threads[reference].stoppedDetails = undefined;
this.threads[reference].stopped = false;
if (removeThreads) {
delete this.threads[reference];
}
}
} else {
Object.keys(this.threads).forEach(ref => {
this.threads[ref].clearCallStack();
this.threads[ref].stoppedDetails = undefined;
this.threads[ref].stopped = false;
});
if (removeThreads) {
this.threads = {};
ExpressionContainer.allValues = {};
}
}
}
public sourceIsUnavailable(source: Source): void {
Object.keys(this.threads).forEach(key => {
if (this.threads[key].getCachedCallStack()) {
this.threads[key].getCachedCallStack().forEach(stackFrame => {
if (stackFrame.source.uri.toString() === source.uri.toString()) {
stackFrame.source.available = false;
}
});
}
});
}
}
export class Model implements debug.IModel {
private sessions: DebugSession[];
private threads: { [reference: number]: debug.IThread; };
private toDispose: lifecycle.IDisposable[];
private replElements: debug.ITreeElement[];
private _onDidChangeBreakpoints: Emitter<void>;
......@@ -481,7 +558,6 @@ export class Model implements debug.IModel {
this.sessions = [];
this.replElements = [];
this.toDispose = [];
this.threads = {};
this._onDidChangeBreakpoints = new Emitter<void>();
this._onDidChangeCallStack = new Emitter<void>();
this._onDidChangeWatchExpressions = new Emitter<debug.IExpression>();
......@@ -492,12 +568,12 @@ export class Model implements debug.IModel {
return 'root';
}
public getSession(id: string): debug.IRawDebugSession {
return this.sessions.filter(s => s.id === id).map(s => s.raw).pop();
public getSessions(): debug.IRawDebugSession[] {
return this.sessions.map(s => s.raw);
}
public removeSession(id: string): void {
this.sessions = this.sessions.filter(s => s.id !== id);
this.sessions = this.sessions.filter(s => s.getId() !== id);
this._onDidChangeCallStack.fire();
}
......@@ -517,37 +593,34 @@ export class Model implements debug.IModel {
return this._onDidChangeREPLElements.event;
}
public getThreads(): { [reference: number]: debug.IThread; } {
return this.threads;
}
public clearThreads(removeThreads: boolean, reference: number = undefined): void {
if (reference) {
if (this.threads[reference]) {
this.threads[reference].clearCallStack();
this.threads[reference].stoppedDetails = undefined;
this.threads[reference].stopped = false;
public getThreads(sessionId: string): { [reference: number]: debug.IThread; } {
const session = this.sessions.filter(s => s.getId() === sessionId).pop();
if (!session) {
return {};
}
if (removeThreads) {
delete this.threads[reference];
}
}
} else {
Object.keys(this.threads).forEach(ref => {
this.threads[ref].clearCallStack();
this.threads[ref].stoppedDetails = undefined;
this.threads[ref].stopped = false;
});
return session.threads;
}
if (removeThreads) {
this.threads = {};
ExpressionContainer.allValues = {};
}
public rawUpdate(data: debug.IRawModelUpdate): void {
let session = this.sessions.filter(s => s.getId() === data.rawSession.getId()).pop();
if (!session) {
session = new DebugSession(data.rawSession);
this.sessions.push(session);
}
session.rawUpdate(data);
this._onDidChangeCallStack.fire();
}
public clearThreads(sessionId: string, removeThreads: boolean, reference: number = undefined): void {
const session = this.sessions.filter(s => s.getId() === sessionId).pop();
if (session) {
session.clearThreads(removeThreads, reference);
this._onDidChangeCallStack.fire();
}
}
public getBreakpoints(): debug.IBreakpoint[] {
return this.breakpoints;
}
......@@ -787,45 +860,7 @@ export class Model implements debug.IModel {
}
public sourceIsUnavailable(source: Source): void {
Object.keys(this.threads).forEach(key => {
if (this.threads[key].getCachedCallStack()) {
this.threads[key].getCachedCallStack().forEach(stackFrame => {
if (stackFrame.source.uri.toString() === source.uri.toString()) {
stackFrame.source.available = false;
}
});
}
});
this._onDidChangeCallStack.fire();
}
public rawUpdate(data: debug.IRawModelUpdate): void {
if (data.thread && !this.threads[data.threadId]) {
// A new thread came in, initialize it.
this.threads[data.threadId] = new Thread(data.sessionId, data.thread.name, data.thread.id);
}
if (data.stoppedDetails) {
// Set the availability of the threads' callstacks depending on
// whether the thread is stopped or not
if (data.allThreadsStopped) {
Object.keys(this.threads).forEach(ref => {
// Only update the details if all the threads are stopped
// because we don't want to overwrite the details of other
// threads that have stopped for a different reason
this.threads[ref].stoppedDetails = objects.clone(data.stoppedDetails);
this.threads[ref].stopped = true;
this.threads[ref].clearCallStack();
});
} else {
// One thread is stopped, only update that thread.
this.threads[data.threadId].stoppedDetails = data.stoppedDetails;
this.threads[data.threadId].clearCallStack();
this.threads[data.threadId].stopped = true;
}
}
this.sessions.forEach(s => s.sourceIsUnavailable(source));
this._onDidChangeCallStack.fire();
}
......
......@@ -38,12 +38,15 @@ export class Source {
public static toRawSource(uri: uri, model: IModel): DebugProtocol.Source {
if (model) {
// first try to find the raw source amongst the stack frames - since that represenation has more data (source reference),
const threads = model.getThreads();
for (let threadId in threads) {
if (threads.hasOwnProperty(threadId) && threads[threadId].getCachedCallStack()) {
const found = threads[threadId].getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop();
if (found) {
return found.source.raw;
const sessions = model.getSessions();
for (let i = 0; i < sessions.length; i++) {
const threads = model.getThreads(sessions[i].getId());
for (let threadId in threads) {
if (threads.hasOwnProperty(threadId) && threads[threadId].getCachedCallStack()) {
const found = threads[threadId].getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop();
if (found) {
return found.source.raw;
}
}
}
}
......
......@@ -48,8 +48,8 @@ export class ViewModel implements debug.IViewModel {
return this._onDidFocusStackFrame.event;
}
public getFocusedThreadId(): number {
return this.focusedThread ? this.focusedThread.threadId : 0;
public getFocusedThread(): debug.IThread {
return this.focusedThread;
}
public getSelectedExpression(): debug.IExpression {
......
......@@ -68,7 +68,6 @@ export class DebugService implements debug.IDebugService {
private _state: debug.State;
private _onDidChangeState: Emitter<debug.State>;
private session: RawDebugSession;
private model: model.Model;
private viewModel: viewmodel.ViewModel;
private configurationManager: ConfigurationManager;
......@@ -103,7 +102,6 @@ export class DebugService implements debug.IDebugService {
) {
this.toDispose = [];
this.toDisposeOnSessionEnd = [];
this.session = null;
this.breakpointsToSendOnResourceSaved = {};
this._state = debug.State.Inactive;
this._onDidChangeState = new Emitter<debug.State>();
......@@ -145,6 +143,10 @@ export class DebugService implements debug.IDebugService {
this.toDispose.push(this.windowService.onBroadcast(this.onBroadcast, this));
}
private get session(): RawDebugSession {
return <RawDebugSession>this.viewModel.activeSession;
}
private onBroadcast(broadcast: IBroadcast): void {
// attach: PH is ready to be attached to
......@@ -261,24 +263,24 @@ export class DebugService implements debug.IDebugService {
this.getThreadData(session).done(() => {
this.model.rawUpdate({
sessionId: session.getId(),
rawSession: session,
threadId,
stoppedDetails: event.body,
allThreadsStopped: event.body.allThreadsStopped
});
const thread = this.model.getThreads()[threadId];
thread.getCallStack(this).then(callStack => {
const thread = this.model.getThreads(session.getId())[threadId];
thread.getCallStack().then(callStack => {
if (callStack.length > 0) {
// focus first stack frame from top that has source location
const stackFrameToFocus = arrays.first(callStack, sf => sf.source && sf.source.available, callStack[0]);
this.setFocusedStackFrameAndEvaluate(stackFrameToFocus, thread).done(null, errors.onUnexpectedError);
this.setFocusedStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError);
this.windowService.getWindow().focus();
aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", event.body.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.lineNumber));
return this.openOrRevealSource(stackFrameToFocus.source, stackFrameToFocus.lineNumber, false, false);
} else {
this.setFocusedStackFrameAndEvaluate(null, thread).done(null, errors.onUnexpectedError);
this.setFocusedStackFrameAndEvaluate(null).done(null, errors.onUnexpectedError);
}
});
}, errors.onUnexpectedError);
......@@ -288,7 +290,7 @@ export class DebugService implements debug.IDebugService {
if (event.body.reason === 'started') {
this.getThreadData(session).done(null, errors.onUnexpectedError);
} else if (event.body.reason === 'exited') {
this.model.clearThreads(true, event.body.threadId);
this.model.clearThreads(session.getId(), true, event.body.threadId);
}
}));
......@@ -304,7 +306,7 @@ export class DebugService implements debug.IDebugService {
}));
this.toDisposeOnSessionEnd.push(session.onDidContinued(event => {
this.lazyTransitionToRunningState(event.body.allThreadsContinued ? undefined : event.body.threadId);
this.lazyTransitionToRunningState(session, event.body.allThreadsContinued ? undefined : event.body.threadId);
}));
this.toDisposeOnSessionEnd.push(session.onDidOutput(event => {
......@@ -351,7 +353,7 @@ export class DebugService implements debug.IDebugService {
private getThreadData(session: RawDebugSession): TPromise<void> {
return session.threads().then(response => {
if (response && response.body && response.body.threads) {
response.body.threads.forEach(thread => this.model.rawUpdate({ sessionId: session.getId(), threadId: thread.id, thread }));
response.body.threads.forEach(thread => this.model.rawUpdate({ rawSession: session, threadId: thread.id, thread }));
}
});
}
......@@ -418,11 +420,13 @@ export class DebugService implements debug.IDebugService {
return !!this.contextService.getWorkspace();
}
public setFocusedStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame, thread?: debug.IThread): TPromise<void> {
if (!thread && focusedStackFrame) {
thread = this.model.getThreads()[focusedStackFrame.threadId];
public setFocusedStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame): TPromise<void> {
let thread: debug.IThread = null;
let session: debug.IRawDebugSession = null;
if (focusedStackFrame) {
session = this.model.getSessions().filter(s => s.getId() === focusedStackFrame.sessionId).pop();
thread = this.model.getThreads(focusedStackFrame.sessionId)[focusedStackFrame.threadId];
}
const session = this.model.getSession(thread.sessionId);
this.viewModel.setFocusedStackFrame(focusedStackFrame, thread, session);
if (focusedStackFrame) {
......@@ -631,7 +635,6 @@ export class DebugService implements debug.IDebugService {
}
const session = this.instantiationService.createInstance(RawDebugSession, configuration.debugServer, this.configurationManager.adapter, this.customTelemetryService);
this.session = session;
this.registerSessionListeners(session);
return session.initialize({
......@@ -643,14 +646,14 @@ export class DebugService implements debug.IDebugService {
supportsVariablePaging: true, // #9537
supportsRunInTerminalRequest: true // #10574
}).then((result: DebugProtocol.InitializeResponse) => {
if (!this.session) {
if (session.disconnected) {
return TPromise.wrapError(new Error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly")));
}
this.model.setExceptionBreakpoints(session.configuration.capabilities.exceptionBreakpointFilters);
return configuration.request === 'attach' ? session.attach(configuration) : session.launch(configuration);
}).then((result: DebugProtocol.Response) => {
if (!this.session) {
if (session.disconnected) {
return TPromise.as(null);
}
......@@ -670,7 +673,7 @@ export class DebugService implements debug.IDebugService {
}
this.extensionService.activateByEvent(`onDebug:${configuration.type}`).done(null, errors.onUnexpectedError);
this.inDebugMode.set(true);
this.lazyTransitionToRunningState();
this.lazyTransitionToRunningState(session);
this.telemetryService.publicLog('debugSessionStart', {
type: configuration.type,
......@@ -688,8 +691,8 @@ export class DebugService implements debug.IDebugService {
this.telemetryService.publicLog('debugMisconfiguration', { type: configuration ? configuration.type : undefined });
this.setStateAndEmit(debug.State.Inactive);
if (this.session) {
this.session.disconnect().done(null, errors.onUnexpectedError);
if (!session.disconnected) {
session.disconnect().done(null, errors.onUnexpectedError);
}
// Show the repl if some error got logged there #5870
if (this.model.getReplElements().length > 0) {
......@@ -790,7 +793,6 @@ export class DebugService implements debug.IDebugService {
});
}
this.session = null;
try {
this.toDisposeOnSessionEnd = lifecycle.dispose(this.toDisposeOnSessionEnd);
} catch (e) {
......@@ -799,7 +801,7 @@ export class DebugService implements debug.IDebugService {
this.partService.removeClass('debugging');
this.model.clearThreads(true);
this.model.removeSession(session.getId());
this.setFocusedStackFrameAndEvaluate(null).done(null, errors.onUnexpectedError);
this.setStateAndEmit(debug.State.Inactive);
......@@ -899,7 +901,7 @@ export class DebugService implements debug.IDebugService {
}
return this.session.next({ threadId }).then(() => {
this.lazyTransitionToRunningState(threadId);
this.lazyTransitionToRunningState(this.session, threadId);
});
}
......@@ -909,7 +911,7 @@ export class DebugService implements debug.IDebugService {
}
return this.session.stepIn({ threadId }).then(() => {
this.lazyTransitionToRunningState(threadId);
this.lazyTransitionToRunningState(this.session, threadId);
});
}
......@@ -919,7 +921,7 @@ export class DebugService implements debug.IDebugService {
}
return this.session.stepOut({ threadId }).then(() => {
this.lazyTransitionToRunningState(threadId);
this.lazyTransitionToRunningState(this.session, threadId);
});
}
......@@ -929,7 +931,7 @@ export class DebugService implements debug.IDebugService {
}
return this.session.stepBack({ threadId }).then(() => {
this.lazyTransitionToRunningState(threadId);
this.lazyTransitionToRunningState(this.session, threadId);
});
}
......@@ -940,7 +942,7 @@ export class DebugService implements debug.IDebugService {
return this.session.continue({ threadId }).then(response => {
const allThreadsContinued = response && response.body ? response.body.allThreadsContinued !== false : true;
this.lazyTransitionToRunningState(allThreadsContinued ? undefined : threadId);
this.lazyTransitionToRunningState(this.session, allThreadsContinued ? undefined : threadId);
});
}
......@@ -980,19 +982,19 @@ export class DebugService implements debug.IDebugService {
}, err => []);
}
private lazyTransitionToRunningState(threadId?: number): void {
private lazyTransitionToRunningState(session: RawDebugSession, threadId?: number): void {
let setNewFocusedStackFrameScheduler: RunOnceScheduler;
const toDispose = this.session.onDidStop(e => {
const toDispose = session.onDidStop(e => {
if (e.body.threadId === threadId || e.body.allThreadsStopped || !threadId) {
setNewFocusedStackFrameScheduler.cancel();
}
});
this.model.clearThreads(false, threadId);
this.model.clearThreads(session.getId(), false, threadId);
// Get a top stack frame of a stopped thread if there is any.
const threads = this.model.getThreads();
const threads = this.model.getThreads(session.getId());
const stoppedReference = Object.keys(threads).filter(ref => threads[ref].stopped).pop();
const stoppedThread = stoppedReference ? threads[parseInt(stoppedReference)] : null;
const callStack = stoppedThread ? stoppedThread.getCachedCallStack() : null;
......
......@@ -212,10 +212,14 @@ export class BaseDebugController extends treedefaults.DefaultController {
// call stack
class ThreadAndSessionId {
constructor(public sessionId: string, public threadId: number) { }
}
export class CallStackController extends BaseDebugController {
protected onLeftClick(tree: tree.ITree, element: any, event: IMouseEvent): boolean {
if (typeof element === 'number') {
if (element instanceof ThreadAndSessionId) {
return this.showMoreStackFrames(tree, element);
}
if (element instanceof model.StackFrame) {
......@@ -227,7 +231,7 @@ export class CallStackController extends BaseDebugController {
protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean {
const element = tree.getFocus();
if (typeof element === 'number') {
if (element instanceof ThreadAndSessionId) {
return this.showMoreStackFrames(tree, element);
}
if (element instanceof model.StackFrame) {
......@@ -266,10 +270,10 @@ export class CallStackController extends BaseDebugController {
}
// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
private showMoreStackFrames(tree: tree.ITree, threadId: number): boolean {
const thread = this.debugService.getModel().getThreads()[threadId];
private showMoreStackFrames(tree: tree.ITree, threadAndSessionId: ThreadAndSessionId): boolean {
const thread = this.debugService.getModel().getThreads(threadAndSessionId.sessionId)[threadAndSessionId.threadId];
if (thread) {
thread.getCallStack(this.debugService, true)
thread.getCallStack(true)
.done(() => tree.refresh(), errors.onUnexpectedError);
}
......@@ -358,17 +362,23 @@ export class CallStackDataSource implements tree.IDataSource {
return this.getThreadChildren(element);
}
const threads = (<model.Model>element).getThreads();
// TODO@Isidor FIX THIS
const session = this.debugService.getViewModel().activeSession;
if (!session) {
return TPromise.as([]);
}
const threads = (<model.Model>element).getThreads(session.getId());
return TPromise.as(Object.keys(threads).map(ref => threads[ref]));
}
private getThreadChildren(thread: debug.IThread): TPromise<any> {
return thread.getCallStack(this.debugService).then((callStack: any[]) => {
return thread.getCallStack().then((callStack: any[]) => {
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
return callStack.concat([thread.stoppedDetails.framesErrorMessage]);
}
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
return callStack.concat([thread.threadId]);
return callStack.concat([new ThreadAndSessionId(thread.sessionId, thread.threadId)]);
}
return callStack;
......
......@@ -265,7 +265,7 @@ export class CallStackView extends viewlet.CollapsibleViewletView {
const model = this.debugService.getModel();
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
const focussedThread = model.getThreads()[this.debugService.getViewModel().getFocusedThreadId()];
const focussedThread = this.debugService.getViewModel().getFocusedThread();
if (!focussedThread) {
this.pauseMessage.hide();
return;
......@@ -290,7 +290,8 @@ export class CallStackView extends viewlet.CollapsibleViewletView {
}));
this.toDispose.push(model.onDidChangeCallStack(() => {
const threads = model.getThreads();
const session = this.debugService.getViewModel().activeSession;
const threads = session ? model.getThreads(session.getId()) : Object.create(null);
const threadsArray = Object.keys(threads).map(ref => threads[ref]);
// Only show the threads in the call stack if there is more than 1 thread.
const newTreeInput = threadsArray.length === 1 ? threadsArray[0] : model;
......
......@@ -54,7 +54,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
private socket: net.Socket = null;
private cachedInitServer: TPromise<void>;
private startTime: number;
private stopServerPending: boolean;
public disconnected: boolean;
private sentPromises: TPromise<DebugProtocol.Response>[];
private capabilities: DebugProtocol.Capabilities;
......@@ -284,7 +284,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
}
public disconnect(restart = false, force = false): TPromise<DebugProtocol.DisconnectResponse> {
if (this.stopServerPending && force) {
if (this.disconnected && force) {
return this.stopServer();
}
......@@ -295,9 +295,9 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
this.sentPromises = [];
}, 1000);
if ((this.serverProcess || this.socket) && !this.stopServerPending) {
if ((this.serverProcess || this.socket) && !this.disconnected) {
// point of no return: from now on don't report any errors
this.stopServerPending = true;
this.disconnected = true;
this.restarted = restart;
return this.send('disconnect', { restart: restart }, false).then(() => this.stopServer(), () => this.stopServer());
}
......@@ -436,7 +436,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
return TPromise.as(null);
}
this.stopServerPending = true;
this.disconnected = true;
let ret: TPromise<void>;
// when killing a process in windows its child
......@@ -492,7 +492,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
private onServerExit(): void {
this.serverProcess = null;
this.cachedInitServer = null;
if (!this.stopServerPending) {
if (!this.disconnected) {
this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
......
......@@ -6,6 +6,7 @@
import assert = require('assert');
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { StackFrame, Expression, Thread } from 'vs/workbench/parts/debug/common/debugModel';
import { MockRawSession } from 'vs/workbench/parts/debug/test/common/mockDebugService';
suite('Debug - View Model', () => {
var model: ViewModel;
......@@ -20,12 +21,12 @@ suite('Debug - View Model', () => {
test('focused stack frame', () => {
assert.equal(model.getFocusedStackFrame(), null);
assert.equal(model.getFocusedThreadId(), 0);
const frame = new StackFrame(1, 1, null, 'app.js', 1, 1);
model.setFocusedStackFrame(frame, new Thread('sessionid', 'myThread', 1), null);
assert.equal(model.getFocusedThread(), null);
const frame = new StackFrame('mockrawsession', 1, 1, null, 'app.js', 1, 1);
model.setFocusedStackFrame(frame, new Thread(new MockRawSession(), 'myThread', 1), null);
assert.equal(model.getFocusedStackFrame(), frame);
assert.equal(model.getFocusedThreadId(), 1);
assert.equal(model.getFocusedThread().threadId, 1);
});
test('selected expression', () => {
......
......@@ -143,7 +143,7 @@ export class MockDebugService implements debug.IDebugService {
}
class MockRawSession implements debug.IRawDebugSession {
export class MockRawSession implements debug.IRawDebugSession {
public get configuration(): { type: string, capabilities: DebugProtocol.Capabilities } {
return {
......@@ -152,6 +152,10 @@ class MockRawSession implements debug.IRawDebugSession {
};
}
public getId() {
return 'mockrawsession';
}
public get onDidEvent(): Event<DebugProtocol.Event> {
return null;
}
......
......@@ -8,13 +8,15 @@ import uri from 'vs/base/common/uri';
import severity from 'vs/base/common/severity';
import debugmodel = require('vs/workbench/parts/debug/common/debugModel');
import * as sinon from 'sinon';
import { MockDebugService } from 'vs/workbench/parts/debug/test/common/mockDebugService';
import { MockRawSession } from 'vs/workbench/parts/debug/test/common/mockDebugService';
suite('Debug - Model', () => {
var model: debugmodel.Model;
let model: debugmodel.Model;
let rawSession: MockRawSession;
setup(() => {
model = new debugmodel.Model([], true, [], [], []);
rawSession = new MockRawSession();
});
teardown(() => {
......@@ -78,8 +80,9 @@ suite('Debug - Model', () => {
test('threads simple', () => {
var threadId = 1;
var threadName = 'firstThread';
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: threadId,
thread: {
id: threadId,
......@@ -87,16 +90,15 @@ suite('Debug - Model', () => {
}
});
var threads = model.getThreads();
var threads = model.getThreads(rawSession.getId());
assert.equal(threads[threadId].name, threadName);
model.clearThreads(true);
model.clearThreads(rawSession.getId(),true);
assert.equal(model.getThreads[threadId], null);
});
test('threads multiple wtih allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.activeSession, 'stackTrace');
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const threadId1 = 1;
const threadName1 = 'firstThread';
......@@ -106,7 +108,7 @@ suite('Debug - Model', () => {
// Add the threads
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: threadId1,
thread: {
id: threadId1,
......@@ -115,7 +117,7 @@ suite('Debug - Model', () => {
});
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: threadId2,
thread: {
id: threadId2,
......@@ -125,7 +127,7 @@ suite('Debug - Model', () => {
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: threadId1,
stoppedDetails: {
reason: stoppedReason,
......@@ -134,8 +136,8 @@ suite('Debug - Model', () => {
allThreadsStopped: true
});
const thread1 = model.getThreads()[threadId1];
const thread2 = model.getThreads()[threadId2];
const thread1 = model.getThreads(rawSession.getId())[threadId1];
const thread2 = model.getThreads(rawSession.getId())[threadId2];
// at the beginning, callstacks are obtainable but not available
assert.equal(thread1.name, threadName1);
......@@ -149,13 +151,13 @@ suite('Debug - Model', () => {
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
thread1.getCallStack().then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.equal(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
});
thread2.getCallStack(mockDebugService).then(() => {
thread2.getCallStack().then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.notEqual(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 2);
......@@ -163,8 +165,8 @@ suite('Debug - Model', () => {
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
return thread2.getCallStack(mockDebugService);
thread1.getCallStack().then(() => {
return thread2.getCallStack();
}).then(() => {
assert.equal(sessionStub.callCount, 2);
});
......@@ -178,14 +180,13 @@ suite('Debug - Model', () => {
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCachedCallStack(), undefined);
model.clearThreads(true);
model.clearThreads(rawSession.getId(), true);
assert.equal(model.getThreads[threadId1], null);
assert.equal(model.getThreads[threadId2], null);
});
test('threads mutltiple without allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.activeSession, 'stackTrace');
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
......@@ -195,7 +196,7 @@ suite('Debug - Model', () => {
// Add the threads
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: stoppedThreadId,
thread: {
id: stoppedThreadId,
......@@ -204,7 +205,7 @@ suite('Debug - Model', () => {
});
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: runningThreadId,
thread: {
id: runningThreadId,
......@@ -214,7 +215,7 @@ suite('Debug - Model', () => {
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: 'sessionid',
rawSession,
threadId: stoppedThreadId,
stoppedDetails: {
reason: stoppedReason,
......@@ -223,8 +224,8 @@ suite('Debug - Model', () => {
allThreadsStopped: false
});
const stoppedThread = model.getThreads()[stoppedThreadId];
const runningThread = model.getThreads()[runningThreadId];
const stoppedThread = model.getThreads(rawSession.getId())[stoppedThreadId];
const runningThread = model.getThreads(rawSession.getId())[runningThreadId];
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
......@@ -239,7 +240,7 @@ suite('Debug - Model', () => {
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
stoppedThread.getCallStack().then(() => {
assert.notEqual(stoppedThread.getCachedCallStack(), undefined);
assert.equal(runningThread.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
......@@ -248,14 +249,14 @@ suite('Debug - Model', () => {
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.getCallStack(mockDebugService).then(callStack => {
runningThread.getCallStack().then(callStack => {
assert.deepEqual(callStack, []);
assert.equal(sessionStub.callCount, 1);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
stoppedThread.getCallStack().then(() => {
assert.equal(sessionStub.callCount, 1);
});
......@@ -264,7 +265,7 @@ suite('Debug - Model', () => {
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCachedCallStack(), undefined);
model.clearThreads(true);
model.clearThreads(rawSession.getId(), true);
assert.equal(model.getThreads[stoppedThreadId], null);
assert.equal(model.getThreads[runningThreadId], null);
});
......@@ -282,7 +283,7 @@ suite('Debug - Model', () => {
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
const stackFrame = new debugmodel.StackFrame(1, 1, null, 'app.js', 1, 1);
const stackFrame = new debugmodel.StackFrame(rawSession.getId(), 1, 1, null, 'app.js', 1, 1);
model.addWatchExpression(null, stackFrame, 'console').done();
model.addWatchExpression(null, stackFrame, 'console').done();
const watchExpressions = model.getWatchExpressions();
......@@ -301,7 +302,7 @@ suite('Debug - Model', () => {
test('repl expressions', () => {
assert.equal(model.getReplElements().length, 0);
const stackFrame = new debugmodel.StackFrame(1, 1, null, 'app.js', 1, 1);
const stackFrame = new debugmodel.StackFrame(rawSession.getId(), 1, 1, null, 'app.js', 1, 1);
model.addReplExpression(null, stackFrame, 'myVariable').done();
model.addReplExpression(null, stackFrame, 'myVariable').done();
model.addReplExpression(null, stackFrame, 'myVariable').done();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册