Skip to content

Commit

Permalink
debug: stack frames paging
Browse files Browse the repository at this point in the history
fixes #4792
  • Loading branch information
isidorn committed Mar 31, 2016
1 parent 385ea5c commit f4d4639
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 27 deletions.
39 changes: 35 additions & 4 deletions src/vs/workbench/parts/debug/browser/debugViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ export class CallStackDataSource implements tree.IDataSource {
}

public getId(tree: tree.ITree, element: any): string {
if (typeof element === 'number') {
return element.toString();
}

return element.getId();
}

Expand All @@ -203,7 +207,7 @@ export class CallStackDataSource implements tree.IDataSource {

public getChildren(tree: tree.ITree, element: any): TPromise<any> {
if (element instanceof model.Thread) {
return (<model.Thread> element).getCallStack(this.debugService);
return this.getThreadChildren(element);
}

const threads = (<model.Model> element).getThreads();
Expand All @@ -213,12 +217,22 @@ export class CallStackDataSource implements tree.IDataSource {
});

if (threadsArray.length === 1) {
return threadsArray[0].getCallStack(this.debugService);
return this.getThreadChildren(threadsArray[0]);
} else {
return TPromise.as(threadsArray);
}
}

private getThreadChildren(thread: debug.IThread): TPromise<any> {
return thread.getCallStack(this.debugService).then((callStack: any[]) => {
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
return callStack.concat([thread.threadId]);
}

return callStack;
});
}

public getParent(tree: tree.ITree, element: any): TPromise<any> {
return TPromise.as(null);
}
Expand All @@ -228,6 +242,10 @@ interface IThreadTemplateData {
name: HTMLElement;
}

interface ILoadMoreTemplateData {
label: HTMLElement;
}

interface IStackFrameTemplateData {
stackFrame: HTMLElement;
label : HTMLElement;
Expand All @@ -240,6 +258,7 @@ export class CallStackRenderer implements tree.IRenderer {

private static THREAD_TEMPLATE_ID = 'thread';
private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
private static LOAD_MORE_TEMPLATE_ID = 'loadMore';

constructor(@IWorkspaceContextService private contextService: IWorkspaceContextService) {
// noop
Expand All @@ -257,10 +276,16 @@ export class CallStackRenderer implements tree.IRenderer {
return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
}

return null;
return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
}

public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): any {
if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
let data: ILoadMoreTemplateData = Object.create(null);
data.label = dom.append(container, $('.load-more'));

return data;
}
if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
let data: IThreadTemplateData = Object.create(null);
data.name = dom.append(container, $('.thread'));
Expand All @@ -281,15 +306,21 @@ export class CallStackRenderer implements tree.IRenderer {
public renderElement(tree: tree.ITree, element: any, templateId: string, templateData: any): void {
if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
this.renderThread(element, templateData);
} else {
} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
this.renderStackFrame(element, templateData);
} else {
this.renderLoadMore(element, templateData);
}
}

private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
data.name.textContent = thread.name;
}

private renderLoadMore(element: any, data: ILoadMoreTemplateData): void {
data.label.textContent = nls.localize('loadMoreStackFrames', "Load More Stack Frames...");
}

private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void {
stackFrame.source.available ? dom.removeClass(data.stackFrame, 'disabled') : dom.addClass(data.stackFrame, 'disabled');
data.file.title = stackFrame.source.uri.fsPath;
Expand Down
33 changes: 20 additions & 13 deletions src/vs/workbench/parts/debug/browser/debugViewlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,24 +240,31 @@ class CallStackView extends viewlet.CollapsibleViewletView {
return;
}
const element = e.selection[0];
if (!(element instanceof model.StackFrame)) {
return;
}

const stackFrame = <debug.IStackFrame> element;
this.debugService.setFocusedStackFrameAndEvaluate(stackFrame);
if (element instanceof model.StackFrame) {
const stackFrame = <debug.IStackFrame> element;
this.debugService.setFocusedStackFrameAndEvaluate(stackFrame);

const isMouse = (e.payload.origin === 'mouse');
let preserveFocus = isMouse;
const isMouse = (e.payload.origin === 'mouse');
let preserveFocus = isMouse;

const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
if (originalEvent && isMouse && originalEvent.detail === 2) {
preserveFocus = false;
originalEvent.preventDefault(); // focus moves to editor, we need to prevent default
const originalEvent:KeyboardEvent|MouseEvent = e && e.payload && e.payload.originalEvent;
if (originalEvent && isMouse && originalEvent.detail === 2) {
preserveFocus = false;
originalEvent.preventDefault(); // focus moves to editor, we need to prevent default
}

const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
this.debugService.openOrRevealEditor(stackFrame.source, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
}

const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
this.debugService.openOrRevealEditor(stackFrame.source, stackFrame.lineNumber, preserveFocus, sideBySide).done(null, errors.onUnexpectedError);
// user clicked on 'Load More Stack Frames', get those stack frames and refresh the tree.
if (typeof element === 'number') {
const thread = this.debugService.getModel().getThreads()[element];
if (thread) {
thread.getCallStack(this.debugService, true).then(() => this.tree.refresh()).done(null, errors.onUnexpectedError);
}
}
}));

this.toDispose.push(this.tree.addListener2(events.EventType.FOCUS, (e: tree.IFocusEvent) => {
Expand Down
5 changes: 4 additions & 1 deletion src/vs/workbench/parts/debug/common/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IRawStoppedDetails {
reason: string;
threadId?: number;
text?: string;
totalFrames?: number;
}

// model
Expand Down Expand Up @@ -60,8 +61,10 @@ export interface IThread extends ITreeElement {
* Queries the debug adapter for the callstack and returns a promise with
* the stack frames of the callstack.
* If the thread is not stopped, it returns a promise to an empty array.
* 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): TPromise<IStackFrame[]>;
getCallStack(debugService: IDebugService, getAdditionalStackFrames?: boolean): TPromise<IStackFrame[]>;

/**
* Gets the callstack if it has already been received from the debug
Expand Down
24 changes: 15 additions & 9 deletions src/vs/workbench/parts/debug/common/debugModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import severity from 'vs/base/common/severity';
import types = require('vs/base/common/types');
import arrays = require('vs/base/common/arrays');
import debug = require('vs/workbench/parts/debug/common/debug');
import errors = require('vs/base/common/errors');
import { Source } from 'vs/workbench/parts/debug/common/debugSource';

const MAX_REPL_LENGTH = 10000;
Expand Down Expand Up @@ -97,7 +96,7 @@ export class Thread implements debug.IThread {
public stoppedDetails: debug.IRawStoppedDetails;
public stopped: boolean;

constructor(public name: string, public threadId) {
constructor(public name: string, public threadId: number) {
this.promisedCallStack = undefined;
this.stoppedDetails = undefined;
this.cachedCallStack = undefined;
Expand All @@ -117,23 +116,30 @@ export class Thread implements debug.IThread {
return this.cachedCallStack;
}

public getCallStack(debugService: debug.IDebugService): TPromise<debug.IStackFrame[]> {
public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise<debug.IStackFrame[]> {
if (!this.stopped) {
return TPromise.as([]);
}

if (!this.promisedCallStack) {
this.promisedCallStack = this.getCallStackImpl(debugService);
this.promisedCallStack.then(result => {
this.cachedCallStack = result;
}, errors.onUnexpectedError);
this.promisedCallStack = this.getCallStackImpl(debugService, 0).then(callStack => {
this.cachedCallStack = callStack;
return callStack;
});
} else if (getAdditionalStackFrames) {
this.promisedCallStack = this.promisedCallStack.then(callStackFirstPart => this.getCallStackImpl(debugService, callStackFirstPart.length).then(callStackSecondPart => {
this.cachedCallStack = callStackFirstPart.concat(callStackSecondPart);
return this.cachedCallStack;
}));
}

return this.promisedCallStack;
}

private getCallStackImpl(debugService: debug.IDebugService): TPromise<debug.IStackFrame[]> {
private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise<debug.IStackFrame[]> {
let session = debugService.getActiveSession();
return session.stackTrace({ threadId: this.threadId, levels: 20 }).then(response => {
return session.stackTrace({ threadId: this.threadId, startFrame, levels: 20 }).then(response => {
this.stoppedDetails.totalFrames = response.body.totalFrames || response.body.stackFrames.length;
return response.body.stackFrames.map((rsf, level) => {
if (!rsf) {
return new StackFrame(this.threadId, 0, new Source({ name: 'unknown' }, false), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
Expand Down

0 comments on commit f4d4639

Please sign in to comment.