diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index cce20517600ce5386db3d8482c4453a5964f4f25..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'; @@ -89,36 +89,10 @@ class Trait implements IDisposable { return this.indexes; } - add(index: number): void { - if (this.contains(index)) { - return; - } - - this.indexes.push(index); - this._onChange.fire({ indexes: this.indexes }); - } - - remove(index: number): void { - this.indexes = this.indexes.filter(i => i === index); - this._onChange.fire({ indexes: this.indexes }); - } - contains(index: number): boolean { return this.indexes.some(i => i === index); } - next(n: number): void { - let index = this.indexes.length ? this.indexes[0] : 0; - index = Math.min(index + n, this.indexes.length); - this.set(index); - } - - previous(n: number): void { - let index = this.indexes.length ? this.indexes[0] : this.indexes.length - 1; - index = Math.max(index - n, 0); - this.set(index); - } - wrapRenderer(renderer: IRenderer): IRenderer> { return new TraitRenderer(this, renderer); } @@ -154,21 +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 => ({ - elements: e.indexes.map(i => this.view.element(i)), - indexes: e.indexes - })); + return this.eventDelayer.delay(mapEvent(this.focus.onChange, e => this.toListEvent(e))); } get onSelectionChange(): Event> { - return mapEvent(this.selection.onChange, e => ({ - elements: e.indexes.map(i => this.view.element(i)), - indexes: e.indexes - })); + return this.eventDelayer.delay(mapEvent(this.selection.onChange, e => this.toListEvent(e))); } constructor( @@ -178,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); @@ -190,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 { @@ -208,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 { @@ -230,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 { @@ -322,6 +298,10 @@ export class List implements IDisposable { } } + private toListEvent({ indexes }: ITraitChangeEvent) { + return { indexes, elements: indexes.map(i => this.view.element(i)) }; + } + dispose(): void { this.view = dispose(this.view); this.focus = dispose(this.focus); 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