From 6ff4290a190a1f8512beaded0d940e2a147ce8fd Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 20 May 2020 17:05:12 +0200 Subject: [PATCH] Add ViewModelEventDispatcher --- .../browser/controller/textAreaHandler.ts | 9 +- src/vs/editor/browser/view/viewImpl.ts | 41 +++----- .../browser/viewParts/minimap/minimap.ts | 7 +- .../browser/viewParts/viewZones/viewZones.ts | 2 - src/vs/editor/common/view/viewContext.ts | 10 +- .../editor/common/view/viewEventDispatcher.ts | 92 ------------------ src/vs/editor/common/view/viewEvents.ts | 53 ++-------- src/vs/editor/common/viewLayout/viewLayout.ts | 3 +- src/vs/editor/common/viewModel/viewModel.ts | 9 +- .../viewModel/viewModelEventDispatcher.ts | 96 +++++++++++++++++++ .../editor/common/viewModel/viewModelImpl.ts | 46 ++++++--- .../common/viewModel/viewModelImpl.test.ts | 10 +- 12 files changed, 173 insertions(+), 205 deletions(-) delete mode 100644 src/vs/editor/common/view/viewEventDispatcher.ts create mode 100644 src/vs/editor/common/viewModel/viewModelEventDispatcher.ts diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index adb33c26620..9eee0dfe476 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -258,14 +258,13 @@ export class TextAreaHandler extends ViewPart { const lineNumber = this._selections[0].startLineNumber; const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0); - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + this._context.model.revealRange( 'keyboard', + true, new Range(lineNumber, column, lineNumber, column), - null, viewEvents.VerticalRevealType.Simple, - true, ScrollType.Immediate - )); + ); // Find range pixel position const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column); @@ -308,12 +307,10 @@ export class TextAreaHandler extends ViewPart { this._register(this._textAreaInput.onFocus(() => { this._context.model.setHasFocus(true); - this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(true)); })); this._register(this._textAreaInput.onBlur(() => { this._context.model.setHasFocus(false); - this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(false)); })); } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 703cd0afae5..1f2252fe582 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -42,7 +42,6 @@ import { Range } from 'vs/editor/common/core/range'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { RenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; -import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; @@ -64,8 +63,6 @@ export interface IOverlayWidgetData { export class View extends ViewEventHandler { - private readonly eventDispatcher: ViewEventDispatcher; - private _scrollbar: EditorScrollbar; private readonly _context: ViewContext; private _selections: Selection[]; @@ -107,18 +104,15 @@ export class View extends ViewEventHandler { const viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate); - // The event dispatcher will always go through _renderOnce before dispatching any events - this.eventDispatcher = new ViewEventDispatcher((callback: () => void) => this._renderOnce(callback)); + // The view context is passed on to most classes (basically to reduce param. counts in ctors) + this._context = new ViewContext(configuration, themeService.getColorTheme(), model); // Ensure the view is the first event handler in order to update the layout - this.eventDispatcher.addEventHandler(this); - - // The view context is passed on to most classes (basically to reduce param. counts in ctors) - this._context = new ViewContext(configuration, themeService.getColorTheme(), model, this.eventDispatcher); + this._context.addEventHandler(this); this._register(themeService.onDidColorThemeChange(theme => { this._context.theme.update(theme); - this.eventDispatcher.emit(new viewEvents.ViewThemeChangedEvent()); + this._context.model.onDidColorThemeChange(); this.render(true, false); })); @@ -224,10 +218,6 @@ export class View extends ViewEventHandler { // Pointer handler this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); - - this._register(model.addViewEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); } private _flushAccumulatedAndRenderNow(): void { @@ -300,7 +290,10 @@ export class View extends ViewEventHandler { } // --- begin event handlers - + public handleEvents(events: viewEvents.ViewEvent[]): void { + super.handleEvents(events); + this._scheduleRender(); + } public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { this.domNode.setClassName(this.getEditorClassName()); this._applyLayout(); @@ -340,7 +333,7 @@ export class View extends ViewEventHandler { this._renderAnimationFrame = null; } - this.eventDispatcher.removeEventHandler(this); + this._context.removeEventHandler(this); this.outgoingEvents.dispose(); this.viewLines.dispose(); @@ -354,12 +347,6 @@ export class View extends ViewEventHandler { super.dispose(); } - private _renderOnce(callback: () => T): T { - const r = safeInvokeNoArg(callback); - this._scheduleRender(); - return r; - } - private _scheduleRender(): void { if (this._renderAnimationFrame === null) { this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100); @@ -477,13 +464,9 @@ export class View extends ViewEventHandler { } public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean { - return this._renderOnce(() => { - const zonesHaveChanged = this.viewZones.changeViewZones(callback); - if (zonesHaveChanged) { - this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent()); - } - return zonesHaveChanged; - }); + const hadAChange = this.viewZones.changeViewZones(callback); + this._scheduleRender(); + return hadAChange; } public render(now: boolean, everything: boolean): void { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 585a9fbfcdf..1be53aad7a3 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1011,14 +1011,13 @@ export class Minimap extends ViewPart implements IMinimapModel { if (this._samplingState) { lineNumber = this._samplingState.minimapLines[lineNumber - 1]; } - this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent( + this._context.model.revealRange( 'mouse', + false, new Range(lineNumber, 1, lineNumber, 1), - null, viewEvents.VerticalRevealType.Center, - false, ScrollType.Smooth - )); + ); } public setScrollTop(scrollTop: number): void { diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index 47a2b6d3e8e..a73026a3f9b 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -222,8 +222,6 @@ export class ViewZones extends ViewPart { changeAccessor.addZone = invalidFunc; changeAccessor.removeZone = invalidFunc; changeAccessor.layoutZone = invalidFunc; - - return zonesHaveChanged; }); return zonesHaveChanged; diff --git a/src/vs/editor/common/view/viewContext.ts b/src/vs/editor/common/view/viewContext.ts index 573b0827f0a..74628ebb857 100644 --- a/src/vs/editor/common/view/viewContext.ts +++ b/src/vs/editor/common/view/viewContext.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IConfiguration } from 'vs/editor/common/editorCommon'; -import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; @@ -37,27 +36,24 @@ export class ViewContext { public readonly configuration: IConfiguration; public readonly model: IViewModel; public readonly viewLayout: IViewLayout; - public readonly privateViewEventBus: ViewEventDispatcher; public readonly theme: EditorTheme; constructor( configuration: IConfiguration, theme: IColorTheme, - model: IViewModel, - privateViewEventBus: ViewEventDispatcher + model: IViewModel ) { this.configuration = configuration; this.theme = new EditorTheme(theme); this.model = model; this.viewLayout = model.viewLayout; - this.privateViewEventBus = privateViewEventBus; } public addEventHandler(eventHandler: ViewEventHandler): void { - this.privateViewEventBus.addEventHandler(eventHandler); + this.model.addViewEventHandler(eventHandler); } public removeEventHandler(eventHandler: ViewEventHandler): void { - this.privateViewEventBus.removeEventHandler(eventHandler); + this.model.removeViewEventHandler(eventHandler); } } diff --git a/src/vs/editor/common/view/viewEventDispatcher.ts b/src/vs/editor/common/view/viewEventDispatcher.ts deleted file mode 100644 index 54bd7a6e986..00000000000 --- a/src/vs/editor/common/view/viewEventDispatcher.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ViewEvent } from 'vs/editor/common/view/viewEvents'; -import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; - -export class ViewEventDispatcher { - - private readonly _eventHandlerGateKeeper: (callback: () => void) => void; - private readonly _eventHandlers: ViewEventHandler[]; - private _eventQueue: ViewEvent[] | null; - private _isConsumingQueue: boolean; - - constructor(eventHandlerGateKeeper: (callback: () => void) => void) { - this._eventHandlerGateKeeper = eventHandlerGateKeeper; - this._eventHandlers = []; - this._eventQueue = null; - this._isConsumingQueue = false; - } - - public addEventHandler(eventHandler: ViewEventHandler): void { - for (let i = 0, len = this._eventHandlers.length; i < len; i++) { - if (this._eventHandlers[i] === eventHandler) { - console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler); - } - } - this._eventHandlers.push(eventHandler); - } - - public removeEventHandler(eventHandler: ViewEventHandler): void { - for (let i = 0; i < this._eventHandlers.length; i++) { - if (this._eventHandlers[i] === eventHandler) { - this._eventHandlers.splice(i, 1); - break; - } - } - } - - public emit(event: ViewEvent): void { - - if (this._eventQueue) { - this._eventQueue.push(event); - } else { - this._eventQueue = [event]; - } - - if (!this._isConsumingQueue) { - this.consumeQueue(); - } - } - - public emitMany(events: ViewEvent[]): void { - if (this._eventQueue) { - this._eventQueue = this._eventQueue.concat(events); - } else { - this._eventQueue = events; - } - - if (!this._isConsumingQueue) { - this.consumeQueue(); - } - } - - private consumeQueue(): void { - this._eventHandlerGateKeeper(() => { - try { - this._isConsumingQueue = true; - - this._doConsumeQueue(); - - } finally { - this._isConsumingQueue = false; - } - }); - } - - private _doConsumeQueue(): void { - while (this._eventQueue) { - // Empty event queue, as events might come in while sending these off - let events = this._eventQueue; - this._eventQueue = null; - - // Use a clone of the event handlers list, as they might remove themselves - let eventHandlers = this._eventHandlers.slice(0); - for (let i = 0, len = eventHandlers.length; i < len; i++) { - eventHandlers[i].handleEvents(events); - } - } - } -} diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 70d7b0577f4..86645dad366 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as errors from 'vs/base/common/errors'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; @@ -330,27 +330,16 @@ export interface IViewEventListener { (events: ViewEvent[]): void; } -export interface IViewEventEmitter { - addViewEventListener(listener: IViewEventListener): IDisposable; -} - -export class ViewEventEmitter extends Disposable implements IViewEventEmitter { - private _listeners: IViewEventListener[]; +export class ViewEventEmitter extends Disposable { private _collector: ViewEventsCollector | null; private _collectorCnt: number; constructor() { super(); - this._listeners = []; this._collector = null; this._collectorCnt = 0; } - public dispose(): void { - this._listeners = []; - super.dispose(); - } - protected _beginEmitViewEvents(): ViewEventsCollector { this._collectorCnt++; if (this._collectorCnt === 1) { @@ -359,45 +348,25 @@ export class ViewEventEmitter extends Disposable implements IViewEventEmitter { return this._collector!; } - protected _endEmitViewEvents(): void { + protected _endEmitViewEvents(eventDispatcher: ViewModelEventDispatcher): void { this._collectorCnt--; if (this._collectorCnt === 0) { const events = this._collector!.finalize(); this._collector = null; if (events.length > 0) { - this._emit(events); + eventDispatcher.emitMany(events); } } } - protected _emitSingleViewEvent(event: ViewEvent): void { + protected _emitSingleViewEvent(eventDispatcher: ViewModelEventDispatcher, event: ViewEvent): void { try { const eventsCollector = this._beginEmitViewEvents(); eventsCollector.emit(event); } finally { - this._endEmitViewEvents(); - } - } - - private _emit(events: ViewEvent[]): void { - const listeners = this._listeners.slice(0); - for (let i = 0, len = listeners.length; i < len; i++) { - safeInvokeListener(listeners[i], events); + this._endEmitViewEvents(eventDispatcher); } } - - public addViewEventListener(listener: IViewEventListener): IDisposable { - this._listeners.push(listener); - return toDisposable(() => { - let listeners = this._listeners; - for (let i = 0, len = listeners.length; i < len; i++) { - if (listeners[i] === listener) { - listeners.splice(i, 1); - break; - } - } - }); - } } export class ViewEventsCollector { @@ -421,11 +390,3 @@ export class ViewEventsCollector { } } - -function safeInvokeListener(listener: IViewEventListener, events: ViewEvent[]): void { - try { - listener(events); - } catch (e) { - errors.onUnexpectedError(e); - } -} diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index cdb142a4152..961eeb314b1 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -353,11 +353,12 @@ export class ViewLayout extends Disposable implements IViewLayout { } // ---- IVerticalLayoutProvider - public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void { + public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): boolean { const hadAChange = this._linesLayout.changeWhitespace(callback); if (hadAChange) { this.onHeightMaybeChanged(); } + return hadAChange; } public getVerticalOffsetForLineNumber(lineNumber: number): number { return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 14d6f7c4a59..91ab8513c4c 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -10,12 +10,13 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions, ITextModel } from 'vs/editor/common/model'; -import { IViewEventEmitter, VerticalRevealType } from 'vs/editor/common/view/viewEvents'; +import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { ICursorSimpleModel, PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; export interface IViewWhitespaceViewportData { readonly id: string; @@ -83,7 +84,7 @@ export interface ICoordinatesConverter { modelPositionIsVisible(modelPosition: Position): boolean; } -export interface IViewModel extends IViewEventEmitter, ICursorSimpleModel { +export interface IViewModel extends ICursorSimpleModel { readonly model: ITextModel; @@ -93,12 +94,16 @@ export interface IViewModel extends IViewEventEmitter, ICursorSimpleModel { readonly cursorConfig: CursorConfiguration; + addViewEventHandler(eventHandler: ViewEventHandler): void; + removeViewEventHandler(eventHandler: ViewEventHandler): void; + /** * Gives a hint that a lot of requests are about to come in for these line numbers. */ setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void; tokenizeViewport(): void; setHasFocus(hasFocus: boolean): void; + onDidColorThemeChange(): void; getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; diff --git a/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts new file mode 100644 index 00000000000..515dfccc389 --- /dev/null +++ b/src/vs/editor/common/viewModel/viewModelEventDispatcher.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; +import { ViewEvent } from 'vs/editor/common/view/viewEvents'; +import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; + +export class ViewModelEventDispatcher { + + private readonly _eventHandlers: ViewEventHandler[]; + private _viewEventQueue: ViewEvent[] | null; + private _isConsumingViewEventQueue: boolean; + // private _outgoingContentSizeChangedEvent: OutgoingContentSizeChangedEvent | null; + + constructor() { + this._eventHandlers = []; + this._viewEventQueue = null; + this._isConsumingViewEventQueue = false; + // this._outgoingContentSizeChangedEvent = null; + } + + public addViewEventHandler(eventHandler: ViewEventHandler): void { + for (let i = 0, len = this._eventHandlers.length; i < len; i++) { + if (this._eventHandlers[i] === eventHandler) { + console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler); + } + } + this._eventHandlers.push(eventHandler); + } + + public removeViewEventHandler(eventHandler: ViewEventHandler): void { + for (let i = 0; i < this._eventHandlers.length; i++) { + if (this._eventHandlers[i] === eventHandler) { + this._eventHandlers.splice(i, 1); + break; + } + } + } + + public emitMany(events: ViewEvent[]): void { + if (this._viewEventQueue) { + this._viewEventQueue = this._viewEventQueue.concat(events); + } else { + this._viewEventQueue = events; + } + + if (!this._isConsumingViewEventQueue) { + this._consumeViewEventQueue(); + } + } + + private _consumeViewEventQueue(): void { + try { + this._isConsumingViewEventQueue = true; + this._doConsumeQueue(); + } finally { + this._isConsumingViewEventQueue = false; + } + } + + private _doConsumeQueue(): void { + while (this._viewEventQueue) { + // Empty event queue, as events might come in while sending these off + const events = this._viewEventQueue; + this._viewEventQueue = null; + + // Use a clone of the event handlers list, as they might remove themselves + const eventHandlers = this._eventHandlers.slice(0); + for (const eventHandler of eventHandlers) { + eventHandler.handleEvents(events); + } + } + } +} + +export class OutgoingContentSizeChangedEvent implements IContentSizeChangedEvent { + + private readonly _oldContentWidth: number; + private readonly _oldContentHeight: number; + + readonly contentWidth: number; + readonly contentHeight: number; + readonly contentWidthChanged: boolean; + readonly contentHeightChanged: boolean; + + constructor(oldContentWidth: number, oldContentHeight: number, contentWidth: number, contentHeight: number) { + this._oldContentWidth = oldContentWidth; + this._oldContentHeight = oldContentHeight; + this.contentWidth = contentWidth; + this.contentHeight = contentHeight; + this.contentWidthChanged = (this._oldContentWidth !== this.contentWidth); + this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight); + } +} diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index cc855612482..655f338c33b 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -29,6 +29,8 @@ import { Cursor } from 'vs/editor/common/controller/cursor'; import { PartialCursorState, CursorState, IColumnSelectData, EditOperationType, CursorConfiguration } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; +import { ViewModelEventDispatcher } from 'vs/editor/common/viewModel/viewModelEventDispatcher'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -37,6 +39,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel private readonly editorId: number; private readonly configuration: IConfiguration; public readonly model: ITextModel; + private readonly _eventDispatcher: ViewModelEventDispatcher; public cursorConfig: CursorConfiguration; private readonly _tokenizeViewportSoon: RunOnceScheduler; private readonly _updateConfigurationViewLineCount: RunOnceScheduler; @@ -63,6 +66,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.editorId = editorId; this.configuration = configuration; this.model = model; + this._eventDispatcher = new ViewModelEventDispatcher(); this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); @@ -104,11 +108,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel if (e.scrollTopChanged) { this._tokenizeViewportSoon.schedule(); } - this._emitSingleViewEvent(new viewEvents.ViewScrollChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewScrollChangedEvent(e)); })); this._register(this.viewLayout.onDidContentSizeChange((e) => { - this._emitSingleViewEvent(new viewEvents.ViewContentSizeChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewContentSizeChangedEvent(e)); })); this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter); @@ -120,12 +124,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); this._onConfigurationChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } })); this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => { - this._emitSingleViewEvent(new viewEvents.ViewTokensColorsChangedEvent()); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensColorsChangedEvent()); })); this._updateConfigurationViewLineCountNow(); @@ -141,6 +145,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); } + public addViewEventHandler(eventHandler: ViewEventHandler): void { + this._eventDispatcher.addViewEventHandler(eventHandler); + } + + public removeViewEventHandler(eventHandler: ViewEventHandler): void { + this._eventDispatcher.removeViewEventHandler(eventHandler); + } + private _updateConfigurationViewLineCountNow(): void { this.configuration.setViewLineCount(this.lines.getViewLineCount()); } @@ -155,6 +167,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel public setHasFocus(hasFocus: boolean): void { this.hasFocus = hasFocus; this.cursor.setHasFocus(hasFocus); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewFocusChangedEvent(hasFocus)); + } + + public onDidColorThemeChange(): void { + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewThemeChangedEvent()); } private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: ConfigurationChangedEvent): void { @@ -308,7 +325,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); } } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } // Update the configuration and reset the centered view line @@ -330,7 +347,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); this.cursor.onModelContentChanged(eventsCollector, e); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } })); @@ -345,7 +362,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel toLineNumber: viewEndLineNumber }; } - this._emitSingleViewEvent(new viewEvents.ViewTokensChangedEvent(viewRanges)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewTokensChangedEvent(viewRanges)); if (e.tokenizationSupportChanged) { this._tokenizeViewportSoon.schedule(); @@ -353,7 +370,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel })); this._register(this.model.onDidChangeLanguageConfiguration((e) => { - this._emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent()); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewLanguageConfigurationEvent()); this.cursorConfig = new CursorConfiguration(this.model.getLanguageIdentifier(), this.model.getOptions(), this.configuration); this.cursor.updateConfiguration(this.cursorConfig); })); @@ -375,7 +392,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.decorations.onLineMappingChanged(); this.viewLayout.onFlushed(this.getLineCount()); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } this._updateConfigurationViewLineCount.schedule(); } @@ -386,7 +403,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this._register(this.model.onDidChangeDecorations((e) => { this.decorations.onModelDecorationsChanged(); - this._emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewDecorationsChangedEvent(e)); })); } @@ -404,7 +421,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewLayout.onHeightMaybeChanged(); } } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } this._updateConfigurationViewLineCount.schedule(); } @@ -951,7 +968,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.viewLayout.deltaScrollNow(deltaScrollLeft, deltaScrollTop); } public changeWhitespace(callback: (accessor: IWhitespaceChangeAccessor) => void): void { - return this.viewLayout.changeWhitespace(callback); + const hadAChange = this.viewLayout.changeWhitespace(callback); + if (hadAChange) { + this._emitSingleViewEvent(this._eventDispatcher, new viewEvents.ViewZonesChangedEvent()); + } } public setMaxLineWidth(maxLineWidth: number): void { this.viewLayout.setMaxLineWidth(maxLineWidth); @@ -963,7 +983,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const eventsCollector = this._beginEmitViewEvents(); callback(eventsCollector); } finally { - this._endEmitViewEvents(); + this._endEmitViewEvents(this._eventDispatcher); } } } diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index 14110739815..6fe30a1284a 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -7,6 +7,8 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence } from 'vs/editor/common/model'; import { testViewModel } from 'vs/editor/test/common/viewModel/testViewModel'; +import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; +import { ViewEvent } from 'vs/editor/common/view/viewEvents'; suite('ViewModel', () => { @@ -63,9 +65,11 @@ suite('ViewModel', () => { let viewLineCount: number[] = []; viewLineCount.push(viewModel.getLineCount()); - viewModel.addViewEventListener((events) => { - // Access the view model - viewLineCount.push(viewModel.getLineCount()); + viewModel.addViewEventHandler(new class extends ViewEventHandler { + handleEvents(events: ViewEvent[]): void { + // Access the view model + viewLineCount.push(viewModel.getLineCount()); + } }); model.undo(); viewLineCount.push(viewModel.getLineCount()); -- GitLab