diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index e20ddef9a15c452b67c4ab90fdc9687b3269c3a1..0d3dcfe7144c53758ebb07215742582d66e656ef 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -9,7 +9,6 @@ import { guessMimeTypes } from 'vs/base/common/mime'; import Event, { Emitter } from 'vs/base/common/event'; import uuid = require('vs/base/common/uuid'); import uri from 'vs/base/common/uri'; -import { RunOnceScheduler } from 'vs/base/common/async'; import { Action } from 'vs/base/common/actions'; import arrays = require('vs/base/common/arrays'); import types = require('vs/base/common/types'); @@ -317,7 +316,7 @@ export class DebugService implements debug.IDebugService { })); this.toDisposeOnSessionEnd[session.getId()].push(session.onDidContinued(event => { - this.lazyTransitionToRunningState(session, event.body.allThreadsContinued ? undefined : event.body.threadId); + this.transitionToRunningState(session, event.body.allThreadsContinued ? undefined : event.body.threadId); })); this.toDisposeOnSessionEnd[session.getId()].push(session.onDidOutput(event => { @@ -665,7 +664,7 @@ export class DebugService implements debug.IDebugService { } this.extensionService.activateByEvent(`onDebug:${configuration.type}`).done(null, errors.onUnexpectedError); this.inDebugMode.set(true); - this.lazyTransitionToRunningState(session); + this.transitionToRunningState(session); this.telemetryService.publicLog('debugSessionStart', { type: configuration.type, @@ -885,19 +884,10 @@ export class DebugService implements debug.IDebugService { return this.configurationManager; } - private lazyTransitionToRunningState(session: RawDebugSession, threadId?: number): void { - let setNewFocusedStackFrameScheduler: RunOnceScheduler; - - const toDispose = session.onDidStop(e => { - if (e.body.threadId === threadId || e.body.allThreadsStopped || !threadId) { - setNewFocusedStackFrameScheduler.cancel(); - } - }); - + private transitionToRunningState(session: RawDebugSession, threadId?: number): void { this.model.clearThreads(session.getId(), false, threadId); // Get a top stack frame of a stopped thread if there is any. - const process = this.model.getProcesses().filter(p => p.getId() === session.getId()).pop(); const stoppedThread = process && process.getAllThreads().filter(t => t.stopped).pop(); const callStack = stoppedThread ? stoppedThread.getCachedCallStack() : null; @@ -907,15 +897,7 @@ export class DebugService implements debug.IDebugService { this.setStateAndEmit(session.getId(), process.session.requestType === debug.SessionRequestType.LAUNCH_NO_DEBUG ? debug.State.RunningNoDebug : debug.State.Running); } - // Do not immediatly set a new focused stack frame since that might cause unnecessery flickering - // of the tree in the debug viewlet. Only set focused stack frame if no stopped event has arrived in 500ms. - setNewFocusedStackFrameScheduler = new RunOnceScheduler(() => { - toDispose.dispose(); - aria.status(nls.localize('debuggingContinued', "Debugging continued.")); - - this.setFocusedStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError); - }, 500); - setNewFocusedStackFrameScheduler.schedule(); + this.setFocusedStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError); } private getDebugStringEditorInput(process: debug.IProcess, source: Source, value: string, mtype: string): DebugStringEditorInput { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts index 0dba31cc6fb936c40dad822047e9477339362161..eef4e01a68099f073c6084fe0fb30d4f1f0c720f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts @@ -48,6 +48,7 @@ const $ = builder.$; export class VariablesView extends viewlet.CollapsibleViewletView { private static MEMENTO = 'variablesview.memento'; + private onFocusStackFrameScheduler: RunOnceScheduler; constructor( actionRunner: actions.IActionRunner, @@ -60,6 +61,22 @@ export class VariablesView extends viewlet.CollapsibleViewletView { @IInstantiationService private instantiationService: IInstantiationService ) { super(actionRunner, !!settings[VariablesView.MEMENTO], nls.localize('variablesSection', "Variables Section"), messageService, keybindingService, contextMenuService); + + // Use schedulre to prevent unnecessary flashing + this.onFocusStackFrameScheduler = new RunOnceScheduler(() => { + // Always clear tree highlight to avoid ending up in a broken state #12203 + this.tree.clearHighlight(); + this.tree.refresh().then(() => { + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + if (stackFrame) { + return stackFrame.getScopes().then(scopes => { + if (scopes.length > 0 && !scopes[0].expensive) { + return this.tree.expand(scopes[0]); + } + }); + } + }).done(null, errors.onUnexpectedError); + }, 700); } public renderHeader(container: HTMLElement): void { @@ -87,7 +104,15 @@ export class VariablesView extends viewlet.CollapsibleViewletView { const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, false, 'explorer-action collapse-explorer'); this.toolBar.setActions(actionbarregistry.prepareActions([collapseAction]))(); - this.toDispose.push(viewModel.onDidFocusStackFrame(sf => this.onFocusStackFrame(sf))); + this.toDispose.push(viewModel.onDidFocusStackFrame(sf => { + // Only delay if the stack frames got cleared and there is no active stack frame + // Otherwise just update immediately + if (sf) { + this.onFocusStackFrameScheduler.schedule(0); + } else if (!this.onFocusStackFrameScheduler.isScheduled()) { + this.onFocusStackFrameScheduler.schedule(); + } + })); this.toDispose.push(this.debugService.onDidChangeState(state => { collapseAction.enabled = state === debug.State.Running || state === debug.State.Stopped; })); @@ -117,20 +142,6 @@ export class VariablesView extends viewlet.CollapsibleViewletView { })); } - private onFocusStackFrame(stackFrame: debug.IStackFrame): void { - // Always clear tree highlight to avoid ending up in a broken state #12203 - this.tree.clearHighlight(); - this.tree.refresh().then(() => { - if (stackFrame) { - return stackFrame.getScopes().then(scopes => { - if (scopes.length > 0 && !scopes[0].expensive) { - return this.tree.expand(scopes[0]); - } - }); - } - }).done(null, errors.onUnexpectedError); - } - public shutdown(): void { this.settings[VariablesView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED); super.shutdown(); @@ -140,6 +151,8 @@ export class VariablesView extends viewlet.CollapsibleViewletView { export class WatchExpressionsView extends viewlet.CollapsibleViewletView { private static MEMENTO = 'watchexpressionsview.memento'; + private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; + private toReveal: debug.IExpression; constructor( actionRunner: actions.IActionRunner, @@ -151,12 +164,19 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView { @IInstantiationService private instantiationService: IInstantiationService ) { super(actionRunner, !!settings[WatchExpressionsView.MEMENTO], nls.localize('expressionsSection', "Expressions Section"), messageService, keybindingService, contextMenuService); + this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { // only expand when a new watch expression is added. if (we instanceof Expression) { this.expand(); } })); + + this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { + this.tree.refresh().done(() => { + return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true); + }, errors.onUnexpectedError); + }, 250); } public renderHeader(container: HTMLElement): void { @@ -185,7 +205,13 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView { const removeAllWatchExpressionsAction = this.instantiationService.createInstance(debugactions.RemoveAllWatchExpressionsAction, debugactions.RemoveAllWatchExpressionsAction.ID, debugactions.RemoveAllWatchExpressionsAction.LABEL); this.toolBar.setActions(actionbarregistry.prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))(); - this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => this.onWatchExpressionsUpdated(we))); + this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { + if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) { + this.onWatchExpressionsUpdatedScheduler.schedule(); + } + this.toReveal = we; + })); + this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => { if (!expression || !(expression instanceof Expression)) { return; @@ -202,12 +228,6 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView { })); } - private onWatchExpressionsUpdated(expression: debug.IExpression): void { - this.tree.refresh().done(() => { - return expression instanceof Expression ? this.tree.reveal(expression) : TPromise.as(true); - }, errors.onUnexpectedError); - } - public shutdown(): void { this.settings[WatchExpressionsView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED); super.shutdown(); @@ -233,10 +253,8 @@ export class CallStackView extends viewlet.CollapsibleViewletView { @IInstantiationService private instantiationService: IInstantiationService ) { super(actionRunner, !!settings[CallStackView.MEMENTO], nls.localize('callstackSection', "Call Stack Section"), messageService, keybindingService, contextMenuService); - this.createSchedulers(); - } - private createSchedulers(): void { + // Create schedulers to prevent unnecessary flashing of tree when reacting to changes this.onStackFrameFocusScheduler = new RunOnceScheduler(() => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (!stackFrame) {