diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 60988cf2774e697fc4c31503c94c93d10b9e9b58..d7f0a3e2e5a03d7e69daa5b039bb0945f8392e89 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -7,7 +7,7 @@ import 'vs/css!./list'; import { IDisposable, dispose, disposeAll } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; import * as DOM from 'vs/base/browser/dom'; -import Event, { Emitter, mapEvent } from 'vs/base/common/event'; +import Event, { Emitter, mapEvent, EventDelayer } from 'vs/base/common/event'; import { IDelegate, IRenderer, IListMouseEvent, IFocusChangeEvent, ISelectionChangeEvent } from './list'; import { ListView } from './listView'; @@ -128,15 +128,16 @@ export class List implements IDisposable { private focus: Trait; private selection: Trait; + private eventDelayer: EventDelayer; private view: ListView; private controller: Controller; get onFocusChange(): Event> { - return mapEvent(this.focus.onChange, e => this.toListEvent(e)); + return this.eventDelayer.delay(mapEvent(this.focus.onChange, e => this.toListEvent(e))); } get onSelectionChange(): Event> { - return mapEvent(this.selection.onChange, e => this.toListEvent(e)); + return this.eventDelayer.delay(mapEvent(this.selection.onChange, e => this.toListEvent(e))); } constructor( @@ -146,6 +147,7 @@ export class List implements IDisposable { ) { this.focus = new Trait('focused'); this.selection = new Trait('selected'); + this.eventDelayer = new EventDelayer(); renderers = renderers.map(r => { r = this.focus.wrapRenderer(r); @@ -158,9 +160,11 @@ export class List implements IDisposable { } splice(start: number, deleteCount: number, ...elements: T[]): void { - this.focus.splice(start, deleteCount, elements.length); - this.selection.splice(start, deleteCount, elements.length); - this.view.splice(start, deleteCount, ...elements); + this.eventDelayer.wrap(() => { + this.focus.splice(start, deleteCount, elements.length); + this.selection.splice(start, deleteCount, elements.length); + this.view.splice(start, deleteCount, ...elements); + }); } get length(): number { @@ -176,8 +180,10 @@ export class List implements IDisposable { } setSelection(...indexes: number[]): void { - indexes = indexes.concat(this.selection.set(...indexes)); - indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); + this.eventDelayer.wrap(() => { + indexes = indexes.concat(this.selection.set(...indexes)); + indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); + }); } selectNext(n = 1, loop = false): void { @@ -198,8 +204,10 @@ export class List implements IDisposable { } setFocus(...indexes: number[]): void { - indexes = indexes.concat(this.focus.set(...indexes)); - indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); + this.eventDelayer.wrap(() => { + indexes = indexes.concat(this.focus.set(...indexes)); + indexes.forEach(i => this.view.splice(i, 1, this.view.element(i))); + }); } focusNext(n = 1, loop = false): void { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 7a575e4057dc2fab0f527d5abcdf171305bd3a36..fcd57c72662825c1d44661f6f7094cd7d0aa8c1e 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -154,4 +154,55 @@ export function fromEventEmitter(emitter: EventEmitter, eventType: string): E export function mapEvent(event: Event, map: (i:I)=>O): Event { return (listener, thisArgs?, disposables?) => event(i => listener(map(i)), thisArgs, disposables); +} + +enum EventDelayerState { + Idle, + Running +} + +/** + * The EventDelayer is useful in situations in which you want + * to delay firing your events during some code. + * You can wrap that code and be sure that the event will not + * be fired during that wrap. + * + * ``` + * const emitter: Emitter; + * const delayer = new EventDelayer(); + * const delayedEvent = delayer.delay(emitter.event); + * + * delayedEvent(console.log); + * + * delayer.wrap(() => { + * emitter.fire(); // event will not be fired yet + * }); + * + * // event will only be fired at this point + * ``` + */ +export class EventDelayer { + + private state = EventDelayerState.Idle; + private buffer: Function[] = []; + + delay(event: Event): Event { + return (listener, thisArgs?, disposables?) => { + return event(i => { + if (this.state === EventDelayerState.Idle) { + listener(i); + } else { + this.buffer.push(() => listener(i)); + } + }, thisArgs, disposables); + }; + } + + wrap(fn: () => void): void { + this.state = EventDelayerState.Running; + fn(); + this.buffer.forEach(flush => flush()); + this.buffer = []; + this.state = EventDelayerState.Idle; + } } \ No newline at end of file