From f4d46394d7ce0496642b5de2b54a8461a6dcf8f8 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 31 Mar 2016 11:39:36 +0200 Subject: [PATCH] debug: stack frames paging fixes #4792 --- .../parts/debug/browser/debugViewer.ts | 39 +++++++++++++++++-- .../parts/debug/browser/debugViewlet.ts | 33 +++++++++------- src/vs/workbench/parts/debug/common/debug.ts | 5 ++- .../parts/debug/common/debugModel.ts | 24 +++++++----- 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/parts/debug/browser/debugViewer.ts b/src/vs/workbench/parts/debug/browser/debugViewer.ts index bac115310ceaf..18f0507f98263 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewer.ts @@ -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(); } @@ -203,7 +207,7 @@ export class CallStackDataSource implements tree.IDataSource { public getChildren(tree: tree.ITree, element: any): TPromise { if (element instanceof model.Thread) { - return ( element).getCallStack(this.debugService); + return this.getThreadChildren(element); } const threads = ( element).getThreads(); @@ -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 { + 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 { return TPromise.as(null); } @@ -228,6 +242,10 @@ interface IThreadTemplateData { name: HTMLElement; } +interface ILoadMoreTemplateData { + label: HTMLElement; +} + interface IStackFrameTemplateData { stackFrame: HTMLElement; label : HTMLElement; @@ -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 @@ -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')); @@ -281,8 +306,10 @@ 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); } } @@ -290,6 +317,10 @@ export class CallStackRenderer implements tree.IRenderer { 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; diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index cb23f28893443..7ea0ff64ebc92 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -240,24 +240,31 @@ class CallStackView extends viewlet.CollapsibleViewletView { return; } const element = e.selection[0]; - if (!(element instanceof model.StackFrame)) { - return; - } - const stackFrame = element; - this.debugService.setFocusedStackFrameAndEvaluate(stackFrame); + if (element instanceof model.StackFrame) { + const stackFrame = 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) => { diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 6b7d6c3e1749a..601f2a379125d 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -32,6 +32,7 @@ export interface IRawStoppedDetails { reason: string; threadId?: number; text?: string; + totalFrames?: number; } // model @@ -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; + getCallStack(debugService: IDebugService, getAdditionalStackFrames?: boolean): TPromise; /** * Gets the callstack if it has already been received from the debug diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 743e34de983d8..9e5572cd6b656 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -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; @@ -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; @@ -117,23 +116,30 @@ export class Thread implements debug.IThread { return this.cachedCallStack; } - public getCallStack(debugService: debug.IDebugService): TPromise { + public getCallStack(debugService: debug.IDebugService, getAdditionalStackFrames = false): TPromise { 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 { + private getCallStackImpl(debugService: debug.IDebugService, startFrame: number): TPromise { 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);